苹果内购(IAP)从入门到精通(6)- 实际业务结合&线上异常情况处理

该系列的其他文章:

【1】苹果内购(IAP)从入门到精通(1)-内购商品类型与配置

【2】苹果内购(IAP)从入门到精通(2)-银行卡与税务信息配置

【3】苹果内购(IAP)从入门到精通(3)- 商品充值流程(非订阅型)

【4】苹果内购(IAP)从入门到精通(4)- 订阅、续订、退订、恢复订阅

【5】苹果内购(IAP)从入门到精通(5)- 掉单处理、防hook以及一些坑

前言

在之前写的 苹果内购(IAP)从入门到精通(3)- 商品充值流程(非订阅型)中,已经简单介绍了整个消耗型商品(非订阅型的商品还有其他类型,但消耗型商品最常见)的充值流程。大致流程为:

添加监听(addTransactionObserver)-> 添加新的交易(addPayment)-> 付款 -> 成功返回Purchased状态(SKPaymentTransactionStatePurchased)-> 校验支付票据 -> 校验成功则下发道具 -> 结束交易(finishTransaction)

但结合实际业务并且线上可以稳定使用,还有很多地方需要处理,以及一些异常情况需要注意。以下我将结合我个人的业务经验,阐释一些实际业务场景与IAP的结合,以及一些线上大量数据验证出来的一些异常情况

1. App启动时以及发起新的充值之前,需要检查是否有未完成的交易

在之前写的 # 苹果内购(IAP)从入门到精通(5)- 掉单处理、防hook以及一些坑 的4.(4)点,我简单说明了需要在 App启动时发起新的支付之前 开启支付队列监听。

这两个场景处理后,若有已付款但未完成(没有调用finishTransaction)的交易,则会在交易队列(SKPaymentQueue)返回给开发者,开发者先处理这笔交易的票据校验;若返回空的交易数组,那么再继续创建新的交易。

需要注意的是,addTransactionObserver添加多次同一个监听对象,实际只是添加的一个。所以在这两个场景里,要想每次调用后都能收到交易队列的返回结果,需按照 启动时调用addTransactionObserver,发起新的支付之前调用restoreCompletedTransactions 来检查交易队列。

有一些团队觉得可以不用检查交易队列,认为掉单的概率很低。但我们结合线上经验发现,因为苹果的接口请求失败,或者用户主动在付款过程中杀死进程(比如loading卡死了),很容易导致掉单。所以这里建议一定要在这两个位置都检查一遍。

那么为什么是 “先调用addTransactionObserver,后调用estoreCompletedTransactions” 呢?以下是几种调用方式分别会造成怎样的场景的举例:

  • 场景一:无未完成的交易 --> 启动调用addTransactionObserver --> 不会回调任何消息 --> 支付前调用addTransactionObserver --> 不会回调任何信息 --> 流程卡死

  • 场景二:无未完成的交易 --> 启动时不做操作 --> 支付前调用restoreCompletedTransactions --> 不会回调任何消息 --> 流程卡死

  • 场景三:无未完成的交易 --> 启动时不做操作 --> 支付前调用addTransactionObserver --> 不会回调任何消息 --> 流程卡死

  • 场景四:无未完成的交易 --> 启动addTransactionObserver --> 不会回调任何消息 --> 支付前不判断是否有未完成交易,发起一笔新的交易 --> 正常进行

    这个场景看似没啥问题,但如果结合实际APP业务中的订单号匹配,可能会导致订单号与TransactionId不匹配的问题。比如:

    • “订单A-6元商品-TransactionA”购买成功 --> 校验票据前网络中断,TransactionA暂时搁置 --> 再次创建新订单“订单B-6元商品” --> 交易队列此时发起订单B的支付,会优先回调Purchased状态的TransactionA --> 校验票据(若此时用的订单B的订单号去和TransctionA匹配,业务层可能会匹配错误)--> (假设成功)下一次购买再次发起“订单C-6元商品” --> 恢复正常充值流程

    • “订单A-6元商品-TransactionA”购买成功 --> 校验票据前网络中断,TransactionA暂时搁置 --> 再次创建新订单“订单B-12元商品” --> 正常充值付费12元并校验票据成功 --> 再次创建新订单“订单C-6元商品” --> 走到上一条的流程,即回调交易队列优先回调Purchased状态的TransactionA

  • 场景五:无未完成的交易 --> 启动addTransactionObserver --> 不会回调任何消息 --> 支付前调用addTransactionObserver --> 回调空的交易数组,判断没有可恢复的交易 --> 发起一笔新的交易 --> 正常进行

  • 场景六:有未完成的交易-->启动addTransactionObserver --> 回调未完成的交易,重新进行票据校验 --> 支付前调用addTransactionObserver --> 回调空的交易数组,判断没有可恢复的交易 --> 发起一笔新的交易 --> 正常进行

综上所述,按照 启动时调用addTransactionObserver,发起新的支付之前调用restoreCompletedTransactions 来检查交易队列,最能满足业务层的多种场景,且不会有bug。

Tips: 对于 restoreCompletedTransactions 这个方法,按照苹果官方文档上说的:

Use this method to restore finished transactions—that is, transactions for which you have already called finishTransaction:(使用此方法恢复已完成的事务,即您已为其调用 finishTransaction: 的事务。).

即这个一般用于恢复订阅型商品(的确恢复订阅型商品是用到这个方法)。但经过我这边测试,没有finish成功的消耗型商品的Transaction,也能够在调用这个方法后,通过交易队列返回。这里若其他人有经验,我们可以在评论区交♂流一下。

2.缓存订单号的时机和位置

除了苹果自己的交易流水号TransactionId以外,大多数开发者都有属于自己(或公司)业务的订单号(下面以orderId代替)。若支付成功后,向苹果校验票据时,一般会通过将orderId和transaction去比对,判断票据里的这笔交易(Transaction),是否就是这个orderId对应的商品,若匹配成功,才会向用户下发实际购买的道具。

但因为支付完成的回调是异步的,所以一般而言,大家都会将orderId做缓存,以尽量做到精准匹配。大多数人会通过applicationUserName来透传这个订单号,或者在本地缓存一个订单号。在之前写的 # 苹果内购(IAP)从入门到精通(5)- 掉单处理、防hook以及一些坑 的第2点中,我说明了applicationUserName在实际应用的某些场景中,很容易丢失导致为空的情况。这个其实在沙盒环境下很好复现:你输入沙盒密码后,立马杀死app,等待几秒钟,系统层级会弹出“已完成支付”的弹窗,这个时候就表明你是实际付款成功了的。再次启动app,启动时检查交易队列,这时这笔交易因为你刚刚的操作没有调用finishTransction,所以交易队列会返回给你,但此时这个Transction对象里的applicationUserName就变为空了。

这里直接说结论:建议在创建orderId的时候,就缓存orderId到Keychain里。

有些人习惯在Purchased状态回调后再缓存,因为这时候Transction对象已经有TransactionId了,可以一对一匹配。但如果是我上面说的那种场景,就无法缓存到;然后applicationUserName又为空了,就导致orderId丢失了,导致校验票据不通过而掉单。

3. 调用了finishTransaciton:但交易队列中依然存在的现象

通过抓包软件能发现,当代码调用finishTransaciton:时,实际上并不只是本地结束交易,而是会有一个苹果的网络请求,请求成功才算真正finished。而在线上环境下,苹果的接口很容易请求异常,比如网络不好的时候会请求失败。这时候即使调用了finishTransaciton:,但交易队列中依然存在这笔交易。

官方建议的调用finishTransaciton有两种时机:交易状态为 SKPaymentTransactionStatePurchased 或SKPaymentTransactionStateFailed 的时候。

这种情况下,Purchased状态的交易,票据校验成功后,调用了finishTransaciton但却没有fished。那么在下次调用restoreCompletedTransactions检查支付队列时,很可能会返回两次Purchased状态的交易(都是同一个交易ID),这样就会进行两次票据校验。但因为上一次调用finish之前,票据已经校验通过了,所以这两次票据校验都是重复校验,这并不影响商品的下发,也不会导致掉单。前端只需要针对这种状态做一下界面提示即可。

Failed状态的交易,一般情况下(下面第4点是特例,待会儿会说到)还没有成功付款,所以即使finish失败,下一次检查交易队列时,这笔交易依然会返回Failed状态,此时待网络正常时成功finished即可。

4. 扣款成功但回调SKPaymentTransactionStateFailed状态

因为苹果接口不稳定的原因,线上环境下有一定概率出现“扣款成功了,但返回SKPaymentTransactionStateFailed状态”的情况,而非返回Purchased状态的情况。而这种情况,一般也会伴随着上面所说的finishTransaciton失败的情况。网络恢复正常后,下一次启动app或者发起充值之前,去检查未完成交易,这时交易队列会返回一条Purchased状态的交易,交易流水号(TransactionIdentifier)即为之前返回Failed状态的这条交易流水号,这时再重新发起票据校验即可。

再考虑业务层的orderId的情况。有些开发者不仅会在完成交易后删除掉缓存的orderId,也会在交易返回失败后,结束掉orderId。非以上特殊情况其实并不会出现问题。但若是当前描述的这个特殊情况的话,Failed时删除了orderId,并且重启后applicationUserName的透传参数也没了,那么当再次返回Purchased状态的这笔交易时,会因为orderId丢失而导致票据校验失败,从而导致掉单的情况。

因此,这里建议:缓存的orderId只在票据校验成功的时候清除,其他地方不要操作。如果发起新的充值,旧的orderId也被新的覆盖掉了。

Supongo que te gusta

Origin juejin.im/post/7068899869527638030
Recomendado
Clasificación