内购接入的坑与核心流程|内购接入的坑与核心流程 - Swift

通过苹果开发文档、搜索与挣扎摸索,我已经在App上实现了【消耗型商品】的内购。总结流程如下:

  1. 完成必须的准备工作
  2. 用有效的商品id创建SKProductRequest请求苹果服务器返回商品(SKProduct)。
  3. 用返回的有效的商品创建payment,并将payment加入苹果的支付队列。
  4. 监听队列,并根据监听到的交易状态进行适当的处理,尤其需要在交易完毕时关闭交易。
这方面的文档,我觉得苹果官方的Programming Guide就写的很好。可以仔细读一下。
我实际遇到的坑
  1. 必须先签署协议才能进行内购开发,否则无法返回商品。
  2. 创建沙箱测试员时,密码必须是强密码,即同时包含大写字母、小写字母和数字。否则,会报错:Unknown Errors while creating Sandbox Tester, Please check Error Log, email=xxx。
  3. 请求商品的SKProductRequest实例的delegate必须在离开页面时viewDidDisappear设置为nil,因为即使你已经离开了页面,苹果依然会尝试向SKProductRequest的代理发送消息,然后App就崩溃了。
  4. 必须在AppDelegate中声明遵守SKPaymentTransactionObserver协议、实现协议方法updatedTransactions、并在didFinishLaunching方法中加入如下代码SKPaymentQueue.default().add(self)
这一行代码是将AppDelegate作为内购队列的监听者。这样,即使你离开了内购页面、即使上次支付未完成等情况,都能够在这里得到及时的处理。
如果不在这里这么做,可能的报错有:
This In-App purchase has already been bought. It will be restored for free.
【内购接入的坑与核心流程|内购接入的坑与核心流程 - Swift】如果需要在内购页面上实现交互,可以也在内购界面上添加监听,并进行交互上面适当的处理。但无路如何,不能省掉AppDelegate中的那部分。
内购接入核心流程 以下是我实际接入中使用的代码。
请求商品
获取商品id 获取商品id,这个可以请求服务器,也可以保存在App上。我的App是用的后者。
首先创建一个名为ProductIDplist文件。文件内容是一个Array,里面是保存的商品id字符串。
这里需要说明的是,商品id,即product identifier,就是你在iTunes Connect 内购列表页面上看到的商品id,不需要再拼接包名bundle identifier。
// Get product id from plist. func predefinedProductIdentifiers() -> [String]? { guard let url = Bundle.main.url(forResource: "ProductID", withExtension: "plist") else { return nil } guard let productIdentifiers = NSArray(contentsOf: url) as? [String] else { return nil } return productIdentifiers }

验证商品id 请求苹果服务器验证我们提供的商品id是否有效。不过记得要把内购Controller设置为遵循SKProductsRequestDelegate协议,否则无法收到通知。
func validateProductIdentifers(with productIdentifiers: [String]) { guard let set = NSSet(array: productIdentifiers) as? Set else { return } let productRequest = SKProductsRequest(productIdentifiers: set) request = productRequest// 将request强引用,避免在完成前被提前释放;不过也务必在离开页面后,将request.delegate设置为nil productRequest.delegate = self productRequest.start() SVProgressHUD.setDefaultStyle(.dark) }

在协议中处理商品
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { print("received product response") products = response.products// 将获取到的商品存储在界面实例变量,再次购买时不需要重复请求商品// 下面是继续进行支付请求

请求支付
在获取了有效商品后,就可以用商品创建SKPayment,并将payment实例加入苹果支付队列中。
func requestPayment(with product: SKProduct) { let payment = SKPayment(product: product) SKPaymentQueue.default().add(payment) }

处理支付
这个步骤核心的点就是创建并添加SKPaymentTransactionObserver
AppDelegate添加observer
  1. 在AppDelegate声明遵循SKPaymentTransactionObserver协议。
  2. 实现协议方法,实现后实现者就可以作为observer。
*// MARK: - SK Product Request Delegate* func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchasing: DispatchQueue.main.async { SVProgressHUD.show(withStatus: NSLocalizedString("订单处理中", comment: "请求支付提示" )) } case .deferred: SVProgressHUD.show(withStatus: NSLocalizedString("订单处理中", comment: "请求支付提示" )) case .purchased: // 购买成功,此处需要发送玩家道具、保存凭证等动作。// 当然交易完毕后,一定要手动关闭 SKPaymentQueue.default().finishTransaction(transaction)case .failed: // 交易失败,也要关闭交易 SKPaymentQueue.default().finishTransaction(transaction)case .restored: // 恢复订单 SKPaymentQueue.default().finishTransaction(transaction) default: print("other situation") SKPaymentQueue.default().finishTransaction(transaction) } } }

  1. didFinishLaunchingWithOptions方法中添加观察者:
    SKPaymentQueue.default().add(self)
其他界面添加observer 假如用户一直呆在内购界面等待结束,我们很可能需要在内购界面也要一套前端的展示机制。跟AppDelegate的流程一样,我们将内购界面也作为SKPaymentTransactionObserver添加到队列中。这样,内购界面也能收到通知了。
当内购界面可用的时候,就可以让内购界面去处理一些前端交互,比如弹出窗口,关闭当前界面等。
本人初学,有不当或错漏之处,感谢斧正!

    推荐阅读