如果 APP 里需要购买虚拟商品,只能通过苹果的 IAP,否则审核会被拒绝。
接入 IAP 不复杂,只要遵守以下流程,就不会出现“掉单”的情况。
全局监听支付通知
接入 IAP 需要引用的库是 StoreKit
。为了比较方便的处理逻辑,建议定制一个单例来处理 IAP 相关的业务。
发起支付后不管成功或者失败,系统都会给 APP 发送通知告知支付的状态,要接收这个通知首先需要注册。
我定制的单例类是 IAPService
:
import StoreKit
class IAPService: NSObject, SKPaymentTransactionObserver {
static let shared = IAPService()
func addObserver() {
SKPaymentQueue.default().add(self)
}
// 这里接收所有支付事务的状态:成功、失败或者正在支付
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
}
}
需要 APP 启动的时候就进行监听,以便及时处理业务。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IAPService.shared.addObserver()
}
处理支付事务
需要关注的状态只有两个,成功和失败。
如果状态是失败,可以直接完成这次事务,不会有任何问题,如果不关闭,下次启动 APP 时又会收到回调。
如果成功了,又分为两种情况:已购买和已恢复,已购买是指新的购买,已恢复是用于非消耗物品或者订阅制的服务。
对于状态是已购买和已恢复的交易事务,需要缓存起来稍后在合适的地方进行凭证校验。
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
handleTransaction(transaction)
}
}
func handleTransaction(_ transaction: SKPaymentTransaction) {
switch transaction.transactionState {
// 购买成功,缓存这个事务
case .purchased:
unfinishedTransactions.append(transaction)
break
// 成功恢复购买,缓存这个事务
case .restored:
unfinishedTransactions.append(transaction.original!)
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction)
case .purchasing, .deferred:
fallthrough
@unknown default:
break
}
}
校验凭证
购买成功以后,系统就会把凭证存储在 APP 的沙盒里,路径需要通过以下方法获取:
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
return
}
不管购买多少次,都是用这个地址获取,凭证就相当于商场里的购物小票,里面记录了所有购买的物品以及状态。
校验凭证就是把这个文件经过 base64
编码后发送到苹果服务器,苹果服务器会返回 json
格式的信息,里面记录的是所购买的物品详情。
这个验证过程只能放在业务服务器来做,由 APP 来验证会有被篡改的风险。
把凭证 base64
编码的代码如下:
var receiptBase64String: String? {
get {
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
return nil
}
guard let receiptData = try? Data(contentsOf: receiptURL) else {
return nil
}
return receiptData.base64EncodedString()
}
}
开发环境和 TestFlight 测试的版本,只能通过沙盒环境验证:
https://sandbox.itunes.apple.com/verifyReceipt
只有上架到 App Store 了,才能使用正式环境验证成功:
https://buy.itunes.apple.com/verifyReceipt
关闭交易事务
验证凭证成功后,业务服务器就可以给 APP 进行充值虚拟货币或者虚拟物品了。
APP 收到业务服务器返回的充值成功回应,才可以关闭交易事务,否则就会发生掉单的情况。
所以避免掉单很简单,只有在业务服务器返回了成功的时候才关闭交易事务就可以了。 只要不关闭事务,每次 APP 启动都能收到通知,都还有机会进行补单操作。 而且不关闭交易事务,用户就不能购买同一个产品ID的商品,避免了用户会被重复扣费的问题。
一些经验
- 如果交易事务还没有结束,APP 被卸载,在安装的时候获取凭证路径会不存在,需要重启一次 APP。
- 如果 AppleID 未绑定支付方式,购买的时候会先取消 APP 里的交易然后跳到 App Store 内添加购买方式后才发起购买,这个时候的交易事务是由系统创建的而不是 APP。购买成功后 APP 还是可以收到通知。
- 可以使用
applicationUsername
绑定用户 ID 的方式防止窜单,但是如果交易是由系统发起的(比如上面提到的情况),这个字段会是空的,所以只有这个字段长度大于 0 的时候才需要判断是否窜单。