ApplePay 服务端单据验证
前言
记录一下ApplePay 非订阅商品的服务端单据验证流程,以及在其中遇到的问题。总的来说苹果的这套单据验证流程还是挺反人类的。
苹果应用内支付流程 iap(in app purchase)
这个是我正在使用的苹果支付大致支付成功的流程图。
- 首先获取商品列表,这里面包含苹果的
product_id
,这个非常重要。 - 之后客户端直接唤醒支付并支付,这里并没有提前找商户服务创建订单,主要是为了减少流程。
- 支付成功后,苹果会返回一份单据,这份单据客户端也可以核实和解析,但是大部分情况下都是发送给服务端做。
- 商户服务端接受到单据会通过苹果提供的接口进行核实和解析。
- 核实成功拿到解析后的数据后就可以进行业务上的验证了,重点:单据核实有效并不到比代表这笔支付有效!!!! 下面会详述
支付单据 receipt 说明
支付成功后,苹果会返回一份单据,这份单据中包含非常多的信息。原始单据大概长下面这样,是一个加密编码后的文本。需要通过苹果提供的接口去核实单据的有效性。其次这个单据在某些情况下会非常非常长。
1 |
|
苹果提了一个verifyReceipt接口进行核实解析,这个接口是全球通用的,不需要任何授权信息,下面是是一个解析后的单据。
1 |
|
- status 是单据验证结果的表示。常见的有以下几种
1
2
3
4
5
6
7
8
9
10
1121000 App Store无法读取你提供的JSON数据
21002 单据数据不符合格式
21003 单据无法被验证
21004 提供的共享密钥和账户的共享密钥不一致
21005 单据服务器当前不可用
21006 单据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 单据信息是测试用(Sandbox),但却被发送到产品环境中验证
21008 单据信息是产品环境中使用,但却被发送到测试环境中验证
21009 内部数据访问错误。稍后再试
21010 系统找不到用户帐户或用户帐户已被删除。 - environment 表示支付交易发生的环境类型。它表示支付是在实际的生产环境还是在测试环境进行的,Production(生产环境)Sandbox(沙盒环境)。
receipt 里面就是你单据解析后的数据。
名称 | 描述 |
receipt_type | 苹果支付单据的类型,Production, ProductionVPP, ProductionSandbox, ProductionVPPSandbox。) |
adam_id | 应用程序的唯一标识符。 |
app_item_id | 唯一标识所购买的应用程序。仅在生产中有 |
bundle_id | 应用程序唯一包名。 |
application_version | 应用程序的版本号,每个版本都是唯一的 |
download_id | 用于标识应用程序下载的唯一标识符。 |
version_external_identifier | 用于标识应用程序版本的唯一标识符,用于表示用户购买或订阅的应用程序版本 |
receipt_creation_date | 支付单据的创建日期和时间,时区 utc |
receipt_creation_date_ms | 支付单据的创建的毫秒时间戳 |
receipt_creation_date_pst | 支付单据的创建日期时间,时区 utc-8 |
request_date | 请求并生成响应的时间 时区utc |
request_date_ms | 请求并生成响应的时间毫秒时间戳 |
request_date_pst | 请求并生成响应的时间 时区 utc-8 |
original_purchase_date | 支付单据的原始购买日期和时间 时区 utc |
original_purchase_date_ms | 支付单据的原始购买日期和时间时间戳 |
original_purchase_date_pst | 支付单据的原始购买日期和时间 时区 utc-8 |
original_application_version | 当前账号首次购买的应用程序的版本。值不会改变 |
in_app 中是应用内购买的相关信息的集合。
quantity | 购买的消耗品数量,基本情况下都会是1 |
product_id | 所购买产品的唯一标识符,这个唯一仅限于你自己的应用 |
transaction_id | 支付交易的唯一标识符,这个标识符是全局唯一的 |
original_transaction_id | 原始交易的唯一标识符,主要用于订阅商品,非订阅商品可以忽略 |
purchase_date | 向用户帐户收取购买或恢复产品费用日期时间 时区 utc |
purchase_date_ms | 向用户帐户收取购买或恢复产品费用的毫秒时间戳 |
purchase_date_pst | 向用户帐户收取购买或恢复产品费用日期时间 时区 utc-8 |
original_purchase_date | 应用内购买该项目的原始购买日期和时间 时区 utc |
original_purchase_date_ms | 应用内购买该项目的原始购买日期和时间时间戳 |
original_purchase_date_pst | 应用内购买该项目的原始购买日期和时间 时区 utc-8 |
is_trial_period | 否处于试用期 |
in_app_ownership_type | 应用内购买项目所有权类型的字段 FAMILY_SHARED(可以与家庭共享) PURCHASED(只属于买方) |
服务端校验以及风险点
苹果单据中的数据还是很全,有一些数据拿到后是必须在服务端进行业务上的判断的。如果疏忽了就会造成一些支付上的漏洞。下面大致是我的单据判断流程。
- 判断
status
如果非0,说明单据存在问题,但是也可能根据情况去放开沙盒支付,可以做一个沙盒支付的白名单,值允许白名单内的用户进行沙盒支付。 - 判断
bundle_id
和adam_id
是否是自己应用的标示。 - 判断
in_app
中是否有用户此次购买的product_id
记录,并取出根据purchase_date_ms
排序得到的最新一条。 - 判断取出数据中
transaction_id
是否发送过退款,或者是否已经发货。 - 没有发生退款和发货,就退当前订单进行发货,并记录、
伪造苹果服务器
如果单据是在客户端去验证的话,可以伪造苹果的服务器,让客户端的单据验证请求走到假的服务并返回一些假的成功信息,这种方式基本已经不存在了,因为基本没有单据是在客户端去验证的
单据重复验证
单据可能会被重复发到服务端,如果服务端不记录transaction_id
状态,就可能出现重复发货的情况,所以服务端不仅需要核实单据的有效性,还需要判断in_app
中的哪些 transaction_id
是已经发货的,哪些是没有发货的。
跨应用单据认证
通过重新打包应用,替换应用包名等信息,然后在替换的应用中上架product_id
一样的商品。这样就可以通过他的应用支付单据去我们自己的服务器进行验证,如果服务端没有判断单据的bundle_id
和 adam_id
这类单据的验证流程就会这个问题。
下面是大致单据校验流程伪代码。成功就返回 transaction_id
,之后就直接判断是否有过退款和发货就可以了。 可以适当的增加一些超时重试,这个接口经常会超时严重。
1 |
|
最后
前段时间苹果已经弃用了这种单据的验证方式。转而采用了App Store Server API 的方式去验证,可以提供更好的支付体验和更加快速的支付流程。