iap内购实现解决方案~FGIAPService

背景

不同于微信/支付宝支付的远程服务器做校验,IAP扣款后的交易验证是App驱动App服务器完成的。但是移动设备所处的网络环境远比服务端复杂;扣款成功后,后续的下发票据 和 App上传票据都面临严峻的考验(网络异常、App服务器异常、Apple服务器异常等)。

市场上已经存在的一些IAP的三方库,譬如RMStore、
IAPHelper等,相对来说也比较成熟,但为什么还需要另外写一个解决方案?

  1. 解决方案,希望能耦合订单业务从而更快更简单地接入项目
  2. 内购的坑,IAP扣款后的交易验证是App驱动App服务器完成的,异常流程如果不能正确处理会存在漏单风险
  3. 票据校验,苹果推荐通过服务器做票据校验
  4. 更好的维护

基本原理

iTunes配置以及创建商品的教程网上很多,就不再描述了,下面简单说说服务器票据校验流程

票据流程分为两种,一种是直接使用Apple的服务器进行购买和验证,另一种就是自己假设服务器进行验证。由于国内网络连接Apple服务器验证非常慢,而且也为了防止黑客伪造购买凭证,通用做法是自己架设服务器进行验证。下面是服务器验证流程

image.png

  • 用户进入购买虚拟物品页面,App从后台服务器获取产品列表然后显示给用户
  • 用户点击购买购买某一个虚拟物品,,APP就发送该虚拟物品的productionIdentifier到Apple服务器和自家服务器,分别生成SKProduct、tradeNo
  • 用户点击确认键购买该物品,并将购买请求发送到Apple服务器
  • Apple服务器完成购买后,返回用户一个完成的票据信息发送给到后台服务器验证
  • 后台服务器把这个凭证发送到Apple验证,Apple返回一个字段给后台服务器表明该凭证是否有效
  • 后台服务器把验证结果在发送到APP,APP根据验证结果做相应的处理

FGIAPService使用

在APP一启动就配置好内购的服务器校验代理对象

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ···
    [[FGIAPManager shared] setConfigureWith: id<FGIAPVerifyTransaction>];

    ···
}
复制代码

通过productId获取苹果商品列表

[[[FGIAPProductsFilter alloc] init] requestProductsWith:[[NSSet alloc] initWithArray:@[product.productId]] completion:^(NSArray<SKProduct *> * _Nonnull products) {

    /// 支付
}];

复制代码

通过商品信息和对应的tradeNo进行支付

[[FGIAPManager shared].FGIAPService buyProduct:product tradeNo:tradeNo onCompletion:^(NSString * _Nonnull message, FGIAPManagerPurchaseRusult result) {
        
}];

复制代码

FGIAPService技术实现

IAP支付的坑太多,这里是收集到的一些常见问题

窜单(订单映射)

订单映射指的是业务订单和IAP订单的映射,本质是将业务订单号 tradeNo绑定到苹果的交易订单(receipt)上

  • 在发起IAP支付后,我们给Apple的是一个SKPayment对象,最后监听到的是SKPaymentTransaction对象(有SKPayment属性对象);我们可以通过利用SKPayment的applicationUsername字段实现订单映射;
  • apple支付成功返回的票据receipt信息含有商品相关的字典信息,通过把tradeNo和receipt提交给服务器可以绑定
  • 通过id协议对外开放服务器校验接口
  • APP上切换账号:验证的时候是根据账号,根据订单来充钱

漏单

对于消耗型和非消耗型商品来说,没有finish的transaction就会出现在updatedTransactions函数里(订阅类型有没有finish都会出现)。常见就是在观察支付队列的函数里,不管什么状态先给finishTransaction,再自己造车轮搞一套本地存储和重发机制。经常在finishTransaction之后,自己造的车轮出了问题,造成丢单。

  • 建设交易验证队列;每笔交易数据持久化成功后,尝试订单验证,验证包含两步:上传票据 和 查询订单状态;只有两步都完成,才能算订单结束,执行 finishTransaction 操作;
  • 失败轮询:苹果支付成功但服务器校验失败的订单,会启动一个定时器做订单轮询
  • APP异常闪退:app一启动就会通过观察支付队列,处理未完成的transaction订单
  • 删除APP:app一启动就会通过观察支付队列,处理未完成的transaction订单

票据的问题

  • iOS 7后(App几乎都是iOS 9起步),从[[NSBundle mainBundle] appStoreReceiptURL]]中获得的receipt(票据)数据;App上传票据信息的话,将其中的数据一起上传;
  • iOS 7后,[[NSBundle mainBundle] appStoreReceiptURL]]中的票据信息是一个receipt list(in_app字段),本身带有“自动修复的特性”,如果用户某次支付没有正确完成,后续也没有被成功恢复;当他产生下一次成功支付后,[[NSBundle mainBundle] appStoreReceiptURL]]中会包含这几次支付的receipt。
  • 票据校验:将票据验证交给服务器去处理,通过请求环境判断是否是沙盒环境

数据异常丢失

  • applicationUsername丢失:通过applicationUsername来保存tradeNo有一定几率取出来为nil,为了保证能tradeNo不丢失,这里会把订单号tradeNo和productIdentifier持久化到keychain。交易数据持久化到keychain有两个好处:
    1. 存储到keychain的数据被加密,安全可靠;
    2. 即使App被卸载,keychain中数据也不会被删除;
  • 票据丢失:如监听到支付交易成功了,但是从[[NSBundle mainBundle] appStoreReceiptURL]]中获得的数据是空,遇到此类问题,可以打标记后,通过SKReceiptRefreshRequest重新获取票据数据。
  • transactionIdentifier丢失:支持流程通过订单映射完成,不需要考虑transactionIdentifier

IAP周边建设

完善埋点

  • 支付环节中,对关键路径埋点,包括但不限于:持久化交易数据操作、上传票据操作、查询操作,结束验单操作等;
  • 监控异常的情况,包括但不限于:持久化交易数据失败、上传失败,applicationUsername获取订单号为空、查询失败等;
  • 开发阶段,尽可能多展示调试日志信息;

响应用户反馈

  • 再好的方案,也无法hold所有的IAP问题,建立用户反馈响应机制,及时响应用户反馈;
  • tradeNO/票据无法正常获取的订单将无法正常finish,所以需要手动帮助帮助用户解决问题后再关闭。
  • 预留ErrorCode:11000007,来删除本地过期订单

小结

由于内购直接涉及到公司盈利,需要加倍小心,所以在方案设计阶段,就调研了许多文章资料,包括三方库:RMStore、IAPHelper。分析其设计优点和缺点。

回顾开发调试过程,理解IAP整个支付链路的流程和实现机制,再到自己实现一个IAPService,文中说到的具体实现节,开发过程中也花费了不少时间去解决。

总体而言,得到了很好的锻炼,值得~ 有需要的朋友,赶快使用起来吧:项目地址

参考文章

iOS 内购(In-App Purchase)总结
谈谈苹果应用内支付(IAP)的坑
Apple IAP 二三事
苹果IAP开发中的那些坑和掉单问题

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享