苹果应用内购(IAP)服务端开发心得

内容纲要

前言

在苹果应用内购(后文以 IAP 代替)的开发中,踩过很多坑,所以写个文章分享一下经验教训,也吐槽一下苹果相当拙劣的接口

一些基本概念

  • 商品类型,IAP 支持 4 种类型,本文只涉及以下三种
    • CONSUMABLE:消耗品,比如游戏内的道具,游戏币也属于消耗品
    • NON_RENEWING:非自动续费的订阅,比如会员资格,按月购买的,到期后再买一个月,如果不买,就不再是会员了,这就是非自动续费的订阅类商品
    • AUTO_RENEWABLE:自动续费的订阅,还是以会员资格来举例,可以每次买一个月,到期后再买一个月;也可以开启自动订阅,这样每次会员将要到期时,苹果会自动发起续期交易,就不需要用户打开 App 来手工购买了,这就是自动续费的订阅
  • receipt:收据,客户端完成购买后,会发送一个收据给服务端,该收据包含了交易的一些信息;如果是自动续费的订阅,在自动续费完成后,也会产生收据
  • transaction:交易,这对应着一次购买,或者是一次自动续费

苹果的接口

最基本的就是收据的验证接口,服务端在拿到客户端提交的收据后,调用收据验证接口验证该收据,如果收据有效,会返回该收据关联的交易信息

这个接口的设计我觉得是有问题的,首先是返回了太多无意义的字段,其次是跟交易相关的两个字段 in_app 和 lastest_receipt_info,我觉得完全应该合并成一个

理论上来说,一个用户的收据,只应返回该用户的交易,但实际上我遇到过收据校验成功,但是不返回任何交易,也遇到过返回了其他用户(甚至不止一个其他用户)的交易

实际上问题还不止这些,这涉及到我们对苹果的定位,我们希望它仅仅是个支付工具,就像支付宝一样,但苹果自己的定位却不是一个支付工具,而是一个订单系统,我想这也是 IAP 对接很容易掉坑里的主要原因

此外,苹果还有服务端的通知,这也是本文将会涉及的内容

IAP 交易和验证

基本流程

  • 客户端从 AppStore 获取商品信息
  • 用户选择商品并完成购买
  • 客户端从 AppStore 获得一个收据
  • 客户端把收据发送到服务端
  • 服务端调用 AppStore 的收据验证接口进行验证
  • 验证通过,服务端根据收据里的交易信息,进行订单的发货处理
  • 服务端向客户端返回收据处理结果
  • 客户端调用 AppStore 的接口通知收据已处理

这是一次购买的基本处理流程,如果是自动续费成功,客户端会从 AppStore 获得一个收据,直接将该收据提交给服务端进行验证即可

这里建议客户端同时提交收据和交易 id,即 transaction_id 到服务端,这样可以简化服务端处理逻辑

如果客户端未提交交易 id,服务端需要自行判断需要处理的是哪一笔交易,我建议如下处理

  • 将 in_app 和 lastest_receipt_info 里的交易合并去重,去重的逻辑是相同的 product_id+original_transaction_id+transaction_id 则视为同一笔交易
  • 按商品类型分组
  • 取每一组的购买时间最近的一笔交易

按苹果的文档,我们应该对所有交易进行处理,但实际业务中遇到过收据关联了其他用户的交易的情况,所以我建议还是找到最新的交易进行处理

要注意的是,不要认为 in_app 和 lastest_receipt_info 里的交易是有序的,应该按 purchase_date_ms 字段进行排序

订阅类商品

基本上 IAP 的坑都是订阅类商品的坑,较普遍的情况是自动续费到哪个用户账户的问题

从前面的流程里我们可以发现,只有交易完成,服务器才能接收到客户端发送的收据,这时,收据是否一定归属于当前提交收据的用户呢?答案是不一定

比如以下场景

  • 用户 A 购买了自动续费类订阅,比如视频网站的按月付费的 Vip
  • 在 Vip 到期前 1 天,苹果自动从该用户绑定的信用卡扣费成功
  • A 用户这段时间一直未登陆帐号
  • A 的 Vip 自动续费成功后,其使用同一部手机,登陆了 B 帐号
  • 此时,App 端获取到 AppStore 的收据并以 B 的身份提交收据到服务端,服务端到底该给 A 增加 Vip 有效期还是给 B 增加?

又比如

  • A 购买了自动续费的月付 Vip
  • 第二个月 Vip 还未到期时,A 取消了自动续费
  • 该用户在同一部手机登录了 B 帐号,并用 B 帐号购买了自动续费的 季付 Vip
  • 在 该 季付的 Vip 到期前一天,苹果扣费成功
  • 此时该用户登录了 A 帐号,客户端获取了季付 Vip 成功的收据并提交服务端

可见,这里有一个关键信息是服务端不具备的,就是一笔已发生的交易,到底是归属那个帐号?

我想,这也是 IAP 容易掉坑的主要原因

苹果的处理逻辑

苹果并没有把自己当做一个第三方支付服务提供商,它实际上既做了支付服务,也实现了订单服务,我们可以在苹果查询到自己购买过的所有 App,以及 App 内交易

本来这样也没有问题,但问题是苹果的交易是基于 Apple Id 来的,同一个 Apple Id,在同一个应用就是一个用户,这样就有问题了

大多数 App,其实都允许同一个 Apple Id 注册多个帐号,这样在苹果的订单系统看来是同一个用户的订单,在我们的 App 里其实是不同用户的订单,而苹果的订单信息里是不包含 App 内帐号信息的

如果是购买消耗品类型商品,很少能触发收据被其他登录帐号提交的场景;但是在自动续费类商品上就很容易发生,如何确定交易的归属,其实是个复杂的问题

我的解决方案:根据交易的 original_transaction_id 找到该收据对应的用户账号,如果该账号还在自动续费中,则该交易归属此用户;如果找不到或者不在自动续费中,则交易属于当前提交收据的用户

苹果后台通知

在接入苹果后台通知前,自动续费类商品续期成功,我们有两种方式来触发服务端的处理

  • 客户端从 AppStore 获得交易成功的收据,并发送到服务端
  • 服务端定时轮询收据验证接口,将快要到期的收据拿去验证
    这样的缺点就是不够及时,影响用户体验

此外,如果用户退款,是不能依赖服务端轮询的,此时苹果后台通知可以说是唯一的触发手段。

那么,如何判断交易已退款?可以根据 cancellation_date_ms 字段是否有值来判断

苹果应用内购(IAP)服务端开发心得

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Scroll to top
粤ICP备2020114259号 粤公网安备44030402004258