iOS 内购处理方案与流程的探究

前言

内购这种跟实际收益有关系的内容,通常都是每个公司关心的重中之重,也是我们开发者所需要关心的重中之重,所以今天就内购方面的内容,从一个最简单的内购流程通过各个特殊场景的处理实现一步步完善。觉得太长可以直接看结尾部分

基础内购

在文章的最开始,我们模拟一个最基础的内购处理场景,即能保证正常流程到账的情况,再根据不同场景做后续优化。

基础的内购的处理流程为:

客户端流程:

graph LR
用户充值 --> 下单传递userId --> 下单成功 --> 充值 --> 苹果返回成功 --> 上报凭证传递userId --> 收到成功回调 --> 关闭交易事务

服务端流程:

下单:

graph LR
接收客户端下单信息 --> 订单信息入库 --> 返回订单号

上报凭证:

graph LR
接收客户端上报凭证 --> 找苹果校验成功 --> 返回客户端成功 --> 客户端关闭交易事务
找苹果校验成功 --> 通过userId查找对应的订单 --> 充值到账并写入凭证的transactionId 

成功交互图:

交互图由于内容过多,暂时只显示成功流程的交互过程

sequenceDiagram
用户->>客户端: 点击充值
客户端->>服务端: 下单(带userId)
服务端->>服务端: 订单入库
服务端-->>客户端: 下单成功
客户端->>苹果: 调用充值接口
苹果-->>用户: 充值操作
用户->>苹果: 充值完成
苹果-->>客户端: 回调充值成功
客户端->>服务端: 上报凭证(带userId)
服务端->>苹果: 调用凭证验证接口
苹果-->>服务端: 凭证验证合法
服务端-->>客户端: 上报成功
客户端-->>用户: 提示充值成功
服务端->>服务端: 通过userId查询订单信息
服务端->>服务端: 充值到账并写入凭证的transactionId 

问题与分析处理

在使用基础内购的流程下,最容易遇到的问题就是账号切换导致的异常问题

场景一: 充值间切换账号

场景环境:

一台设备,两个登录账号 A 与 B。

异常流程:

A 账号下单充值,充值完成且在苹果未返回的情况下,切换到 B 账号,此时凭证变成由B上报。

扫描二维码关注公众号,回复: 14369978 查看本文章

现实情况:

游戏中,工会的管理人员需要经常切换帐号,或者切换小号进行不同的操作,在正常充值之后,如果用户没注意到账情况,直接切换帐号,那么就有可能出现该情况

异常表现:

  1. 如果此时检查下单记录。也就是会查询之前该 userId 之前的下单,那么发现之前未下过单,无法到账,导致丢单
  2. 如果此时不检查下单记录,直接验证后到账B账号,就会串单现象

解决方案:

使用设备ID(IDFV)加锁的方案,每个设备,每个商品id在完整流程中只能存在一笔订单,如果订单未完成那么就不允许再次发起充值,这样即使用户切换到B帐号,上报凭证时则根据设备ID进行订单的检索并实现到账。

解锁场景更新:

解锁场景
苹果返回成功并上报凭证成功
苹果返回充值失败

流程更新:

客户端流程:


graph LR
用户充值 --> 下单传递IDFV --> 下单成功 --> 充值 --> 苹果返回成功 --> 上报凭证传递IDFV --> 收到成功回调 --> 关闭交易事务
充值 --> 苹果返回失败 --> 上报失败信息(传递IDFV) --> 收到接收回调 --> 关闭交易事务

服务端流程:


下单:

graph LR
接收客户端下单信息 --> 查找订单列表中对应设备id是否存在加锁的订单
查找订单列表中对应设备id是否存在加锁的订单 --> 存在加锁订单 --> 返回锁单中
查找订单列表中对应设备id是否存在加锁的订单 --> 不存在加锁订单 --> 创建订单并入库 --> 订单置为锁单状态 -->  返回订单号

上报凭证:

graph LR
接收客户端上报凭证 --> 找苹果校验成功 --> 返回客户端成功 --> 客户端关闭交易事务
找苹果校验成功 --> 通过设备id查找对应的订单 --> 充值到账
通过设备id查找对应的订单 --> 订单解锁并写入凭证的transactionId 

上报失败:

graph LR
接收客户端上报失败信息 --> 通过设备id查找对应的订单 --> 订单解锁 --> 返回接受成功
接收客户端上报失败信息 --> 失败信息写入日志

充值成功交互图:


sequenceDiagram
用户->>客户端: 点击充值
客户端->>服务端: 下单(带IDFV)
服务端->>服务端: 订单入库并加锁
服务端-->>客户端: 下单成功
客户端->>苹果: 调用充值接口
苹果-->>用户: 充值操作
用户->>苹果: 充值完成
苹果-->>客户端: 回调充值成功
客户端->>服务端: 上报凭证(带IDFV)
服务端->>苹果: 调用凭证验证接口
苹果-->>服务端: 凭证验证合法
服务端-->>客户端: 上报成功
客户端-->>用户: 提示充值成功
服务端->>服务端: 通过IDFV查询订单信息
服务端->>服务端: 订单解锁并写入凭证的transactionId 
服务端->>服务端: 充值到账

场景二:解锁异常

场景环境:

充值过程中,关闭应用并取消支付

异常流程:

用户下单充值过程之后,关闭应用并取消支付。此时苹果不再返回任何支付信息。

异常表现:

由于苹果未返回任何信息,导致服务端一直未解锁订单,从而在卸载重装之前无法充值。

现实情况:

用户可能只是误点击充值,为了避免充值成功,则可能会直接关闭应用,并取消充值,此时就会出现该情况

解决方案:

在唤起充值之后,如果未返回过充值成功,则在下次服务端返回充值失败时,可以假定用户充值失败,则直接进行解锁操作。

解锁场景更新:

解锁场景
苹果返回成功并上报凭证成功
苹果返回充值失败
重启应用后的首次下单返回锁定,且苹果未返回充值信息

流程更新:

客户端流程:


graph LR
用户充值 --> 下单传递IDFV --> 下单成功 --> 充值 --> 苹果返回成功 --> 上报凭证传递IDFV --> 收到成功回调 --> 关闭交易事务
充值 --> 苹果返回失败 --> 上报失败信息(传递IDFV) --> 收到接收回调 --> 关闭交易事务
下单传递IDFV --> 返回锁单中 --> 判断为打开应用后首次下单 --> 上报苹果未返回  --> 收到上报回调 --> 下单传递IDFV

其余流程无需更新

场景三:卸载重装无法到账

场景环境:

一台设备,充值过程中卸载重装应用

异常流程:

账号下单充值,充值完成且在苹果未返回的情况下,卸载重装应用,此时凭证上报时候的设备id发生了变化,导致无法找到设备ID对应的订单。(如果使用保存在 keychain 中的 idfv,则恢复出厂设置时也将无法获取到之前保存的 idfv)

现实场景:

用户在充值之后,可能由于网络问题,暂时无法到账,瞬间弃坑删除应用,再过一段时间的冷静期后,又重新继续下载应用,此时就会出现该情况

解决方案:

上报凭证时候有两种地方获取,一个是旧的直接通过 transactionData 获取, 一种是官方推荐的 NSBundle 中获取。我们需要优化的点在于两处内容同时上报,解析 transactionData 时,可以获取到 UniqueVendorIdentifier ,这个与发起充值时候的 IDFV 的值是相同的,我们就可以针对这点来做订单的关联,并使用bundle中的凭证信息做安全相关的检验。(实际上,直接使用 transactionData 中的数据也是可以的,目前通过苹果验证后的暂未发现异常情况)

流程更新:

服务端流程:


上报凭证:

graph LR
接收客户端上报凭证 --> 找苹果校验成功 --> 返回客户端成功 --> 客户端关闭交易事务
找苹果校验成功 --> 提取凭证中的UniqueVendorIdentifier --> 查找对应的订单 --> 充值到账并写入凭证的transactionId 
查找对应的订单 --> 订单解锁

其余流程无需更新

场景四:充值成功但是到账慢

情况有多种,但是表现类似,就放一起讨论 环境场景:

  1. 设备本身网络较慢
  2. 服务器与苹果链接慢
  3. 服务端异常导致入库失败

异常流程:

  1. 玩家本身网络慢,导致苹果返回充值成功之后,上报凭证服务端超时,按照正常流程则需要苹果再次下发充值成功才会再次上报,间隔时间长。
  2. 服务器找苹果校验凭证信息,接口调用时间过长导致超时,返回客户端失败,从而等待下次上报
  3. 服务器本身出现异常(内存满了、发布中等)导致入库异常,返回客户端失败等待重新上报

现实情况:

电梯中或者高铁上玩手机进行充值,时常会有信号不好的时候,此时即为该情况

解决方案:

  1. 客户端收到苹果下发的充值成功信息后,保存凭证到内存中再进行上报凭证到服务端的操作,上报成功后移除对应凭证信息,上报失败则进行延迟重试处理
  2. 服务端收到上报的凭证时,入库成功后则直接返回成功给客户端,然后再调用苹果接口校验凭证是否合法,如果发生超时情况,则延后进行重试。

更新的流程放在结尾处

结尾

以上为旧版 storekit 所使用的方案,在目前已知情况下除了苹果自身多次扣款,其余情况都能保证充值的正常到账。新版的 storeKit 可以直接使用 appAccountToken 进行关联。如果有其他情况,欢迎一起讨论

完整解锁场景

解锁场景
苹果返回成功并上报凭证成功
苹果返回充值失败
重启应用后的首次下单返回锁定,且苹果未返回充值信息

完整流程

客户端流程:


graph LR
用户充值 --> 下单传递IDFV --> 下单成功 --> 充值 --> 苹果返回成功 --> 保存凭证信息 --> 上报凭证 --> 收到成功回调 --> 关闭交易事务 --> 移除保存的凭证
上报凭证 --> 收到异常回调 --> 延时30s --> 上报凭证
充值 --> 苹果返回失败 --> 上报失败信息(传递IDFV) --> 收到接收回调 --> 关闭交易事务
下单传递IDFV --> 返回锁单中 --> 判断为打开应用后首次下单 --> 上报苹果未返回  --> 收到上报回调 --> 下单传递IDFV

服务端流程:


下单:

graph LR
接收客户端下单信息 --> 查找订单列表中对应设备id是否存在加锁的订单
查找订单列表中对应设备id是否存在加锁的订单 --> 存在加锁订单 --> 返回锁单中
查找订单列表中对应设备id是否存在加锁的订单 --> 不存在加锁订单 --> 创建订单并入库 --> 订单置为锁单状态 -->  返回订单号

上报凭证:

graph LR
接收客户端上报凭证 --> 请求苹果凭证校验接口 --> 苹果校验返回成功 --> 返回客户端成功 --> 客户端关闭交易事务
苹果校验返回成功 --> 提取凭证中的UniqueVendorIdentifier --> 查找对应的订单 --> 充值到账并写入凭证的transactionId 
查找对应的订单 --> 订单解锁
请求苹果凭证校验接口 --> 找苹果校验超时 --> 延时30s --> 请求苹果凭证校验接口

上报失败:

graph LR
接收客户端上报失败信息 --> 通过设备id查找对应的订单 --> 订单解锁 --> 返回接受成功
接收客户端上报失败信息 --> 失败信息写入日志

充值成功交互图:


sequenceDiagram
用户->>客户端: 点击充值
客户端->>服务端: 下单(带 IDFV)
服务端->>服务端: 订单入库并加锁
服务端-->>客户端: 下单成功
客户端->>苹果: 调用充值接口
苹果-->>用户: 充值操作
用户->>苹果: 充值完成
苹果-->>客户端: 回调充值成功
客户端->>服务端: 上报凭证
服务端->>苹果: 调用凭证验证接口
苹果-->>服务端: 凭证验证合法
服务端-->>客户端: 上报成功
客户端-->>用户: 提示充值成功
服务端->>服务端: 通过凭证获取的UniqueVendorIdentifier查询订单信息
服务端->>服务端: 订单解锁并写入凭证的transactionId 
服务端->>服务端: 充值到账

FAQ

  1. 多次扣款时如何补发给用户?

这种情况由于是极少数情况,只能定位到多次扣款的那个订单信息,并定位确认最近的订单,并跟用户确认后手动补发

  1. 如何验证重复上报凭证

上报凭证时候,先判断凭证的 transactionId 是否已存在,如果已存在则说明已上报过,直接返回成功即可

  1. 是否要做其他的限制

凭证验证成功后,查询订单时,查询凭证充值时间前的订单即可,并确认解析后的包名是否正确,如果需要的话,可以考虑查询到过早的订单时,不进行到账处理并预警该订单


猜你喜欢

转载自juejin.im/post/7118770506702012453