IAP是In-App Purchase的缩写,是指应用内购买,是苹果为App内购买虚拟商品或服务提供的一套交易系统。
1.接入内购前准备
再开发账号的Itunes Connect中进行:
填写银行账户信息
配置商品信息,包括产品ID,产品价格等
配置用于测试IAP支付功能的沙箱账户。
- 沙箱账户的邮箱不能是已经注册过AppleID的 - 可以不是真实的邮箱,但是必须符合邮箱格式 - App Store 地区的选择,测试的时候弹出的提示框以及结算的价格会按照沙箱账号选择的地区货币来
2.配置内购商品
IAP是一套商品交易系统,非简单的支付系统,每一个购买项目都需要在开发者后台的Itunes Connect后台为App创建一个对应的商品,提交给苹果审核通过后,购买项目才会生效。
2.1 商品有四种类型:
- 消耗型项目:只可使用一次的产品,使用之后即失效,必须再次购买,如:游戏币、一次性虚拟道具等。
- 非消耗型项目:只需购买一次,不会过期或随着使用而减少的产品。如:电子书。
- 自动续期订阅:允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期,如:Apple Music这类按月订阅的商品。
- 非续期订阅:允许用户购买有时限性服务的产品,此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期。
2.2 配置商品信息需要注意产品ID和产品价格
-
产品 ID 具有唯一性,这里有个坑:一旦新建一个内购商品,它的产品ID将永远被占用,即使该商品已经被删除,已创建的内购商品除了产品 ID 之外的所有信息都可以修改,如果删除了一个内购商品,将无法再创建一个相同产品 ID 的商品。
-
在创建IAP项目的时候,需要设定价格,产品价格只能从苹果提供的价格等级去选择,这个价格等级是固定的,同一价格等级会对应各个国家的货币。详情可看苹果官方定价文档
3.内购流程
IAP的支付流程分为客户端和服务端
3.1客户端的工作
- 获取内购产品列表(从App内读取或从自己服务器读取),向用户展示内购列表。
- 用户选择某个内购产品后,先请求可用的内购产品的本地化信息列表,此次调用Apple的StoreKit库的代码。
- 得到内购产品的本地化信息后,根据用户选择的内购产品的ID得到内购产品。
- 根据内购产品发起IAP购买请求,收到购买完成的回调。
- 购买流程结束后, 向服务器发起验证凭证以及支付结果的请求。
- 自己的服务器将支付结果信息返回给前端并发放虚拟产品【后端做】。
3.2 服务端的工作
- 接收iOS端发过来的购买凭证,判断凭证是否已经存在或验证过,然后存储该凭证。
- 将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
3.3 恢复购买
非消耗型 和 自动续期订阅 需要提供恢复购买的功能,例如创建一个恢复按钮,不然审核很可能会被拒绝。
消耗型项目 和 非续期订阅 苹果不会提供恢复的接口,不要调用上述方法去恢复,否则也可能被拒。
//调起苹果内购恢复接口
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
3.4 掉单
掉单是指用户付完款购买商品,钱扣了,商品却没到账。
IAP支付流程:
- 发起支付
- 扣费成功
- 得到支付凭据receipt,并且存储到本地
- 拿receipt去后台验证凭据,获取商品交易状态
- 验证成功,回调数据给前端,刷新页面,并且删除存储的receipt
漏单情况1:
3 -> 4 出问题,比如断网。此时前端会把支付凭据持久化存储下来,如果期间用户卸载APP此单在前端就真漏了,如果没有卸载,做以下处理:
根据业务情况,在以下几个阶段,插入 监测app存储的凭据receipt,如果监测到了,重新走 4 -> 5的流程。
- 监听app的网络状态发生变化
- app初始化
- app从后台进入前台时
- app账号登录成功后
漏单情况2:
4 -> 5 出问题。此时后台其实已经成功,只是前端没获取到数据,当漏单处理,重新进入页面刷新数据即可。
漏单情况3:
2 -> 3 环节出问题,属于苹果的问题,目前没做处理。
4.注意事项
4.1 交易凭据receipt判重
验证支付凭据(receipt)是否有效放后台去做,如果后台不做判重,同一个凭据就可以无数次验证通过,因为苹果也不判重,这就会导致前端可以凭此取到的一个支付凭据可以去后台无数次做校验,后台就会给前端发放无数次商品,安全的做法是后台把验证通过的支付凭据做个记录,每次来新收到的凭据先判断是否已使用过,防止多次发放商品。
4.2 商品类型的选择
在创建商品的时候,一定要注意区分商品类型,尤其是消耗型项目和非消耗型项目,因为receipt解析之后,有一个 in_app 数组,里面存储购买的商品的信息,对于消耗型项目来说,in_app 数组中只有本次购买的商品,对于非消耗型项目来说,以往购买的商品都会存在。这个后端在进行receipt验证的时候就需要做区别处理。
具体来说就是,在验证消耗型项目的receipt时候,可以直接取in_app数组的第一条数据,因为in_app数组中只有一条数据。
在验证 非消耗型项目 的receipt时候,需要遍历每条记录,找到product_id跟本次购买的相同的那一条数据的购买时间。
4.3 receipt验证
receipt验证工具:getman.cn/
Content-Type记得选择Application/Json
报错信息
/*
* 21000 App Store不能读取你提供的JSON对象
* 21002 receipt-data域的数据有问题
* 21003 receipt无法通过验证
* 21004 提供的shared secret不匹配你账号中的shared secret
* 21005 receipt服务器当前不可用
* 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
* 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
* 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
* $receipt_data 苹果返回的支付凭证
*/