iOS—处理苹果内购(IAP)掉单的坑

她现在已经一岁多了,亲手把她从小带到大的感觉,真是酸甜苦辣五味俱全啊。如果按照人类的年龄来计算,她应该相当于二十四五岁,正直风华正茂的年龄。有时候恨她,没错,她比较特别,很会赚钱,很能干,略污很可爱。

扯远了,说正事吧。苹果手机端软件免费+应用内购买的模式已经被证明是最有效的盈利模式,所以实现内购功能可能是很多开发者必做的工作和必备的技能。相信很多接入了苹果应用内支付IAP(In-App Purchase)功能的应用都可能遇到过「掉单」的问题,「掉单」的意思就是用户扣款了但是平台没有给 TA 充值虚拟货币,这会让用户很苦恼愤,严重影响产品的体验,甚至公司的信誉度,我们偶尔会接到用户的投诉电话。凡是涉及到钱的事,都是很敏感的,所以需要格外的谨慎,掉单不可怕,可怕的是不去处理它。

我司产品(社交软件)在这个问题上困扰已久,一直没有花时间去处理,也腾不出更多的时间,都是在迭代新版本。趁着公司开发管理制度上的改变,每一个新版本都会留出时间去解决一些技术上的问题,为的就是减少产品的 bug ,优化提升性能,让产品更加完美。问题既然存在很久了,现在回头看看以前写的代码逻辑,有种很不可思议的惊讶:这 TMD 是我写的代码?绝逼不可能……这逻辑明显问题很多啊……肯定是哪个二逼写的……,一脸懵逼。好吧,其实都是自己挖过的坑,现在是时候填了。今天,不,就现在,咱们来聊聊如何解决IAP「掉单」的事儿。

首先来整理一下IAP支付的过程:

  1. 调用IAP接口发起支付
  2. 支付成功,获取App Receipt票据,调用充值接口验证
  3. 验证通过,给用户充值虚拟货币并回调给 App

这里需要了解一下IAP支付的机制,每次支付行为或每笔交易被认为是一个SKPaymentTransation,只有当SKPaymentTransationfinishTransaction:,这次支付(交易)行为才算是正常结束了。即使这次支付途中被中断,其实也并没有丢失。假设支付没有完成 App 就退出了(比如崩溃),那么当下次 App 重启之后,只要设置了监听addTransactionObserver:,之前被中断的支付就会接着进行。

再来看看「掉单」可能会出现在哪些环节:

  • 第1步,这个过程中 App 进程因为某种原因被 kill 了,其实支付行为还在系统后台进行着,苹果自己做的,很有可能扣款成功。但是这时候没法为用户充值虚拟货币。
  • 第2步,App 端与自己服务器端通信失败;自己服务器端与 AppStore 服务器之间的通信失败。

在了解了IAP支付机制之后,上面两种情况就有方法去解决了。

针对第一种情况,可以在 App 一启动就设置监听,如果有未完成的支付,则会回调- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;这个方法,在这个方法里调用接口充值。
至于第二种情况,App 端需要做接口重试,设置一个重试的逻辑。

下面来描述一下我的具体做法。

我使用了RMStore,稍作了一些修改。新建一个数据表(字段有:transactionIdentifier、productIdentifier、uid、transactionDate(支付时间)、rechargeDate(充值到账时间)、state(「已支付」和「已充值」)),存每一笔交易。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;这个方法里,首先根据SKPaymentTransactiontransactionIdentifier去本地数据表中判断这条交易:

  • 如果没有,则在数据表中创建一条交易,状态为「已支付」,接着去调用充值接口;
  • 如果有这条交易,并且状态为「已支付」,则不需要创建该条交易,只需要去调用充值接口;
  • 如果有这条交易,并且状态为「已充值」(虚拟货币到了用户账上),则不需要创建,也不需要去充值,直接结束交易(finishTransaction:)。

充值接口的设置,App 端需要传这些参数:交易id、商品id、用户uid、票据,该接口在服务器端需要做一些操作:transactionIdentifier需要入库,和App Receipt对应起来,每次访问接口都要先做去重判断,如果是已经验证通过的transactionIdentifier,也返回成功。只要是访问成功,该接口都需要带上transactionIdentifier返回给 App 端,可能不止一个,因为一个App Receipt票据验证成功后返回的交易列表可能有多个,如下图:

App Receipt Verify Reponse.jpg

在充值成功的回调里需要找出每一个transactionIdentifier,根据这个transactionIdentifier去遍历SKPaymentQueuetransactions,找出对应的SKPaymentTransaction并结束交易finishTransaction:。还需要根据transactionIdentifier去更新数据表中这条交易的状态,改为「已充值」。然后继续遍历SKPaymentQueuetransactions,看有没有未完成的交易需要充值,如果有,都要去判断在本地数据表中是否已有记录。

写到这里,感觉应该可以解决大部分掉单的问题吧,花了几天的时间翻阅了不少资料,希望这个方案是可靠的。等待产品上线看效果~

2017-04-07 效果还不错,几乎没掉单了

参考文章:
苹果IAP开发中的那些坑和掉单问题
iOS Apple内购及掉单问题
iOS内购你看我就够了(一)
真·iOS内购的完整流程
关于AppStore IAP的新旧Receipt
iOS内购你看我就够了(埋坑篇)
2016年最新版App内购买详细指南
iOS内购丢单处理及实现

推荐阅读更多精彩内容