ios 内购订阅流程

2,078 阅读3分钟

年前由于需求的原因做了一次苹果的内购:实现了普通购买 和 订阅功能; 订阅功能有个首月特惠的需求,当时并不知道是通过商品的 推介促销优惠 去设置首月特惠;使用了促销优惠,然后让服务器配合做了很多没必要的工作,做出来的效果也不是首月特惠,最后才发现只需要在 推介促销优惠 里面设置第一个月的价格就可以实现首月特惠;为了避免以后再走弯路,在这里简单记录一下。

一、内购准备

  在iTunes Connect 填写银行账号信息;申请沙盒环境的测试账号,用于测试使用。

二、配置商品

 1、配置普通商品没有什么要注意的,按流程走就是。

  2、订阅商品的配置需注意:如果验证、续费都是在服务器完成,需要在iTunes Connect App信息中配置用于接收App store 通知的URL,沙盒环境和生成环境各一个

截屏2022-02-11 下午3.43.59.png

三 、执行购买

1、向苹果服务器请求商品

    /// 请求对应的商品
    /// - Parameter ProductId: 商品id

    func reuqestProduct(ProductId:String){

        if SKPaymentQueue.canMakePayments() {

            self.ProductId = ProductId

            reuqestProductData(type: ProductId)

        }
    }
    private func reuqestProductData(type: String) {

        let product = [type]

        let set = NSSet(array: product as [AnyObject])

        let request = SKProductsRequest(productIdentifiers: set as! Set<String>)

        request.delegate = self

        request.start()

    }

2、在SKProductsRequestDelegate代理中拿到商品

///SKProductsRequestDelegate商品请求回调

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {

        if response.products.count == 0 {

            if let dlg = delegate {

                dlg.buyFailed(type: .notProduct)

            }
        }

        var prod: SKProduct?

        for pro in response.products {

            if pro.productIdentifier == self.ProductId {

                prod = pro

            }

        }
        if let produ = prod {

            ///发起下单请求

            creteOrder(product: produ)
        }

    }

3、向服务器下单,生成orderID并存储到本地,向苹果发起内购

/// 向服务器下单 (网络请求的代码就不拿出来了)

    func creteOrder(product : SKProduct){

        orderId = ""

//        ApiProvider.requestData(SSApi.mood_buySkin(),model:orderModel.self,alert: false) { [self] (type, msg, res) in

//            if type == .success {

                ///发起内购之前存储商品id  用于丢单情况下的二次验证

                let orderid = "请求得到的orderId"

                UserDefaults.setValue(orderid, forKey: orderIdMark)

                orderId = orderid

                ///向苹果服务器发起内购

                let payment = SKMutablePayment(product: product)

                SKPaymentQueue.default().add(payment)

//            } else {

//            }

//        }
    }

4、内购成功,向自己服务器验证支付是否成功

///SKPaymentTransactionObserver购买回调

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

        for tran in transactions {

            switch tran.transactionState {

            case .purchased:

                ///购买成功

               prPayverifyPurchase(transaction: tran)
            case .purchasing:

                /////商品添加进列表

                break

            case .restored:

                ///已经购买了

                payFailed(transaction: tran)

            case .failed:

                payFailed(transaction: tran)

            default:

                payFailed(transaction: tran)

                break

            }

        }

    }
    
    
    /// 支付服务器验证1

    /// - Parameters:

    ///   - transactionId: 凭证

    func prPayverifyPurchase(transaction:SKPaymentTransaction){

        if orderId.count <= 1{

            orderId =  (UserDefaults.value(forKey: orderIdMark)) as! String

        }
        if orderId.count <= 1 {

            payFailed(transaction: transaction)
            return
        }

        /// 获取收据
        let receiptUrl = Bundle.main.appStoreReceiptURL

        let data = NSData(contentsOf: receiptUrl!)

        if let _ = transaction.transactionIdentifier,let str = data?.base64EncodedString(options: .endLineWithLineFeed){

            validationOrder(orderId: orderId, receipt: str, transaction: transaction)

        } else {

            payFailed(transaction:transaction,finishTransaction: true)

        }

    }
    /// 支付完成向自己服务器验证2

    /// - Parameters:

    ///   - orderId: 自己服务器生成的订单

    ///   - receipt : 收据

    ///   - transaction:SKPaymentTransaction

    func validationOrder(orderId:String,receipt:String,transaction:SKPaymentTransaction){
        ///发起验证网络请求 transactionId 为普通商品购买的凭证id; originalTransactionId 为订阅商品购买的凭证id
//        ApiProvider.requestNotDolls(SSApi.pay_vildSerives(orderCode:orderId, receiptData: receipt, transactionId:transaction.transactionIdentifier,originalTransactionId: transaction.original?.transactionIdentifier),alert: false) {[self](type, msg, res) in
            if true {
                if let dlg = delegate {

                    dlg.buySuccess()

                }
                ///验证成功 移除凭证 和临时存储的商品

                SKPaymentQueue.default().finishTransaction(transaction)

                UserDefaults.setValue(nil, forKey: orderId)

            } else {

                payFailed(transaction:nil,finishTransaction: false)

            }

//        }

    }

5、内购成功并且验证成功 需要关闭支付事务finishTransaction;内购失败也需要finishTransaction;内购成功并且验证失败不要关闭等下次验证成功再关闭

SKPaymentQueue.default().finishTransaction(tran)

四、 漏单处理

漏单主要处理 钱扣了 ,服务器没有发货的情况,也就是内购成功,向自己服务器验证失败的情况。

1、 存储交易订单的transactionIdentifier和自己服务器生成的orderId.

2、app启动会重新执行func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]),在这个方法里面检测orderid 是否与 transactions的transactionIdentifier 对应,对应说明有漏单的情况 ,需要向自己的服务器发起验证.

3、漏单并未重启app,在进入支付页面的时候发起检测,检测SKPaymentQueue.default().transactions是否有订单与存储的orderId匹配,有的话就发起验证。

注意:为什么需要orderid与transactionIdentifier 对应?

订阅商品会在续期的时候触发客服端的func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]的方法,如果不做绑定处理,很有可能将订阅续期通知当做漏单处理,又从新购买一次了。

对于订阅这种通知的处理我是直接finishTransaction了,如果大家有更好的处理可以留言讨论一下。

五、 测试

在测试环境下用最开始申请的沙盒账号购买即可。

1、沙盒环境下 自动续费时间缩短了,1周对应3分钟,1月对于5分钟;续费时间到了客服端会执行func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]方法

2、沙盒环境购买订阅商品后,可以在手机的 设置 -> APPStore ->  沙盒账号 -> 管理 -> 取消订阅  进行退订,退订的同时 服务器会收到退订通知。

六、 内购审核

1、普通商品的审核

补全商品的审核信息, 上传一张商品的截屏,和app一起提交给苹果审核

2、订阅商品的审核

额外需要在 APP描述里面添加一些连续包月会员说明;按照苹果给出的规则需要指明商品名、价格、取消地方等;可以参考其它APP怎么写的。

这里贴出一个模板

【连续包月会员说明】

1、服务名称:连续包月会员

2、价格:6元/月

3、购买连续包月会员的账号,会在每个月到期前24小时,自动在iTunes账号扣费并延长1个月的会员有效期。

4、如需取消订阅,请手动打开苹果手机”设置“ --> 进入"iTunes Store 与 App Store" --> 点击"Apple ID",进入”账户设置“页面,点击”订阅“,选择连续包月会员取消订阅即可。如未在订阅期结束的至少24小时前关闭订阅,此订阅将会自动续订。

5、自动续费服务协议:vctone.com/automaticRe…

6、隐私协议:vctone.com/userAgree.h…

截屏2022-02-11 下午5.52.04.png

末尾:

每一步没有详细的写出来,但是需要注意的地方都写下来了,希望让看到的人少走一点弯路。当然订阅不止这么简单,比如 自动订阅的归属问题;自动订阅更改时长问题;还有优惠卷、促销商品的使用都等着我们一步步去摸索。

贴一个简单的dome:github.com/CodePerryH/…