内购接入的坑与核心流程|内购接入的坑与核心流程 - Swift
通过苹果开发文档、搜索与挣扎摸索,我已经在App上实现了【消耗型商品】的内购。总结流程如下:
- 完成必须的准备工作
- 用有效的商品id创建SKProductRequest请求苹果服务器返回商品(SKProduct)。
- 用返回的有效的商品创建payment,并将payment加入苹果的支付队列。
- 监听队列,并根据监听到的交易状态进行适当的处理,尤其需要在交易完毕时关闭交易。
我实际遇到的坑
- 必须先签署协议才能进行内购开发,否则无法返回商品。
- 创建沙箱测试员时,密码必须是强密码,即同时包含大写字母、小写字母和数字。否则,会报错:Unknown Errors while creating Sandbox Tester, Please check Error Log, email=xxx。
- 请求商品的
SKProductRequest
实例的delegate
必须在离开页面时viewDidDisappear
设置为nil
,因为即使你已经离开了页面,苹果依然会尝试向SKProductRequest
的代理发送消息,然后App就崩溃了。 - 必须在
AppDelegate
中声明遵守SKPaymentTransactionObserver
协议、实现协议方法updatedTransactions
、并在didFinishLaunching
方法中加入如下代码SKPaymentQueue.default().add(self)
。
如果不在这里这么做,可能的报错有:
This In-App purchase has already been bought. It will be restored for free.
【内购接入的坑与核心流程|内购接入的坑与核心流程 - Swift】如果需要在内购页面上实现交互,可以也在内购界面上添加监听,并进行交互上面适当的处理。但无路如何,不能省掉
AppDelegate
中的那部分。内购接入核心流程 以下是我实际接入中使用的代码。
请求商品
获取商品id 获取商品id,这个可以请求服务器,也可以保存在App上。我的App是用的后者。
首先创建一个名为
ProductID
的plist
文件。文件内容是一个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
- 在AppDelegate声明遵循
SKPaymentTransactionObserver
协议。 - 实现协议方法,实现后实现者就可以作为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)
}
}
}
- 在
didFinishLaunchingWithOptions
方法中添加观察者:
SKPaymentQueue.default().add(self)
SKPaymentTransactionObserver
添加到队列中。这样,内购界面也能收到通知了。当内购界面可用的时候,就可以让内购界面去处理一些前端交互,比如弹出窗口,关闭当前界面等。
本人初学,有不当或错漏之处,感谢斧正!
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量