苹果规定,凡是虚拟的物品(例如:QQ音乐的乐币)进行交易时,都必须走苹果的内购通道,苹果要收取大约30%的抽成,所以不允许接入第三方的支付方式(微信、支付宝等),当然开发者可以设置后门,在审核时避开审核人员。这个是有风险的,一旦发现,app会被立即下架,还是老老实实接入内购吧。
本文的设计是基于二次架构改版后整理的技术方案(原创)。在经过线上业务的一段时间的数据跟踪、客服反馈及细节点整理后,针对更多场景而设计的支付架构方案。
内购类型:
- 消耗型项目:只可使用一次的产品,使用之后即失效,必须再次购买,如:游戏币、一次性虚拟道具等
- 非消耗型项目:只需购买一次,不会过期或随着使用而减少的产品。如:电子书
- 自动续期订阅:允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期,如:Apple Music这类按月订阅的商品(有些鸡贼的开发者以此收割对IAP商品不熟悉的用户,参考App Store“流氓”软件)
- 非续期订阅:允许用户购买有时限性服务的产品,此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期
一、交互方案
客户端支付步骤:发起订单创建(向服务端)→ 调用苹果支付(通过IAP)→ 拉取支付票据(IAP)→ 通过服务端验证票据并开通权益
1、创建订单
考虑点:创建订单时有可能存在上一笔实际已支付,但服务端票据未验证完的情况(这边定义为支付处理中),所以此时也会做对应的逻辑处理
这是一张图。。。。
2、支付订单
待完善
3、查询支付结果
待完善
4、恢复购买
待完善
5、订单矫正
待完善
二、实现前细节说明
1、交互细节
待完善
2、票据JSON解析
常用的字段说明:此票据内含有续期型和消耗型型购买记录
产品编号:wdqrwei2222 为自动需求型
产品编号:21337yui222 为消耗型
{
"environment": "Production", // 收据生成的环境 Sandbox,沙盒环境;Production,生产环境
"receipt": {
"receipt_type": "Production", // ProductionSandbox,沙盒环境;Production,生产环境
"adam_id": 1578996855,
"app_item_id": 1578996855,
"bundle_id": "com.keyi.xxxxx", // APP的bundle_id
"application_version": "12",
"download_id": 501384164329443772,
"version_external_identifier": 845585564,
"receipt_creation_date": "2022-05-23 10:47:38 Etc/GMT",
"receipt_creation_date_ms": "1653302858000",
"receipt_creation_date_pst": "2022-05-23 03:47:38 America/Los_Angeles",
"request_date": "2022-05-24 08:23:42 Etc/GMT", // 请求处理时间(格林威治时间)
"request_date_ms": "1653380622275", // 请求处理时间毫秒值(建议使用,因为其它可能会存在时区问题)
"request_date_pst": "2022-05-24 01:23:42 America/Los_Angeles", // 请求处理时间(洛杉矶时间)
"original_purchase_date": "2022-05-09 02:42:50 Etc/GMT",
"original_purchase_date_ms": "1652064170000", // 原始购买时间毫秒值
"original_purchase_date_pst": "2022-05-08 19:42:50 America/Los_Angeles",
"original_application_version": "12",
"in_app": [ // 数组,包含非消耗型、非续期订阅,以及客户之前购买的自动续期订阅
{
"quantity": "1",
"product_id": "wdqrwei2222", // 说明:此json的这个是自动订阅
"transaction_id": "530001050393511",
"original_transaction_id": "530001050393511",
"purchase_date": "2022-05-09 02:54:41 Etc/GMT",
"purchase_date_ms": "1652064881000",
"purchase_date_pst": "2022-05-08 19:54:41 America/Los_Angeles",
"original_purchase_date": "2022-05-09 02:54:42 Etc/GMT",
"original_purchase_date_ms": "1652064882000",
"original_purchase_date_pst": "2022-05-08 19:54:42 America/Los_Angeles",
"expires_date": "2022-05-16 02:54:41 Etc/GMT",
"expires_date_ms": "1652669681000",
"expires_date_pst": "2022-05-15 19:54:41 America/Los_Angeles",
"web_order_line_item_id": "530000464995051",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED"
},
{
"quantity": "1", // 购买数量,通常是1,除非可以通过可变付款进行修改。最大值为10。
"product_id": "wdqrwei2222", // 说明:此json的这个是自动订阅 // 商品id
"transaction_id": "530001055605613", // 交易标识(自动订阅中,不同的交易标识会对应同一个原始交易标识)
"original_transaction_id": "530001050393511", // 原始交易标识
"purchase_date": "2022-05-16 02:54:41 Etc/GMT",
"purchase_date_ms": "1652669681000", // 购买时间毫秒值
"purchase_date_pst": "2022-05-15 19:54:41 America/Los_Angeles",
"original_purchase_date": "2022-05-09 02:54:42 Etc/GMT",
"original_purchase_date_ms": "1652064882000", // 原始购买时间毫秒值
"original_purchase_date_pst": "2022-05-08 19:54:42 America/Los_Angeles",
"expires_date": "2022-05-23 02:54:41 Etc/GMT",
"expires_date_ms": "1653274481000", // 过期时间毫秒值
"expires_date_pst": "2022-05-22 19:54:41 America/Los_Angeles",
"cancellation_date": "2020-12-19 07:40:40 Etc/GMT",
"cancellation_date_ms": "1608363640000", // (不一定有此字段)取消交易的时间毫秒值,有退款交易才显示
"cancellation_date_pst": "2020-12-10 23:33:02 America/Los_Angeles",
"web_order_line_item_id": "530000464995052",
"is_trial_period": "false", // 是否在免费试用期间 是:"true";否:"false"
"is_in_intro_offer_period": "false", // 订阅是否在促销价格期间 是:"true";否:"false"
"promotional_offer_id": "12345678", // (不一定有此字段)客户兑换的自动续订订阅的促销优惠的标识符,is_in_intro_offer_period为true才显示
"in_app_ownership_type": "PURCHASED" // PURCHASED:客户是产品的购买者;FAMILY_SHARED:客户是通过“家庭共享”访问该产品的家庭成员
}
]
},
"latest_receipt_info": [ // 包含订阅的所有交易,其中包括初次购买和后续续期,但不包括任何恢复购买。要验证自动订阅,则关注这个数组内容
{
"quantity": "1",
"product_id": "21337yui222", // 说明:此json的这个是非自动订阅
"transaction_id": "1530000000172508",
"original_transaction_id": "1530000000172508",
"purchase_date": "2022-05-23 10:47:46 Etc/GMT",
"purchase_date_ms": "1653302866000",
"purchase_date_pst": "2022-05-23 03:47:46 America/Los_Angeles",
"original_purchase_date": "2022-05-23 10:47:46 Etc/GMT",
"original_purchase_date_ms": "1653302866000",
"original_purchase_date_pst": "2022-05-23 03:47:46 America/Los_Angeles",
"is_trial_period": "false",
"in_app_ownership_type": "PURCHASED"
},
{
"quantity": "1",
"product_id": "wdqrwei2222", // 说明:此json的这个是自动订阅
"transaction_id": "530001055605613",
"original_transaction_id": "530001050393511",
"purchase_date": "2022-05-16 02:54:41 Etc/GMT",
"purchase_date_ms": "1652669681000",
"purchase_date_pst": "2022-05-15 19:54:41 America/Los_Angeles",
"original_purchase_date": "2022-05-09 02:54:42 Etc/GMT",
"original_purchase_date_ms": "1652064882000",
"original_purchase_date_pst": "2022-05-08 19:54:42 America/Los_Angeles",
"expires_date": "2022-05-23 02:54:41 Etc/GMT",
"expires_date_ms": "1653274481000",
"expires_date_pst": "2022-05-22 19:54:41 America/Los_Angeles",
"web_order_line_item_id": "530000464995052",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "20858308"
},
{
"quantity": "1",
"product_id": "wdqrwei2222", // 说明:此json的这个是自动订阅
"transaction_id": "530001050393511",
"original_transaction_id": "530001050393511",
"purchase_date": "2022-05-09 02:54:41 Etc/GMT",
"purchase_date_ms": "1652064881000",
"purchase_date_pst": "2022-05-08 19:54:41 America/Los_Angeles",
"original_purchase_date": "2022-05-09 02:54:42 Etc/GMT",
"original_purchase_date_ms": "1652064882000",
"original_purchase_date_pst": "2022-05-08 19:54:42 America/Los_Angeles",
"expires_date": "2022-05-16 02:54:41 Etc/GMT",
"expires_date_ms": "1652669681000",
"expires_date_pst": "2022-05-15 19:54:41 America/Los_Angeles",
"web_order_line_item_id": "530000464995051",
"is_trial_period": "false",
"is_in_intro_offer_period": "false",
"in_app_ownership_type": "PURCHASED",
"subscription_group_identifier": "20858308", // 订阅所属的订阅组的标识符
"is_upgraded": "true" // (不一定有此字段)是否同意订阅价格升级
}
],
"latest_receipt": "这是一个很长的最新票据字符串。。。。。。。。。", // 最新的收据,替换旧收据
"pending_renewal_info": [ // 仅针对包含自动续订的应用收据返回
{
"auto_renew_product_id": "wdqrwei2222",
"is_in_billing_retry_period": "0", // 订阅状态 1:有效;0:已取消订阅
"product_id": "wdqrwei2222", // 订阅的商品id
"original_transaction_id": "530001050393511", // 原始订单号
"auto_renew_status": "0",
"is_in_billing_retry_period":"1", // (不一定有此字段)没有这个字段:正常;1:用户支付信息有问题,苹果正在尝试重新扣款(最长持续60天);0:苹果停止重新扣款
"expiration_intent": "1", // (不一定有此字段)订阅过期的原因,当包含已过期的自动续订订阅才显示 1:客户自愿取消订阅;2:帐单错误;3:客户不同意最近的提价;4:续订时无法购买该产品;5:未知错误;
"grace_period_expires_date": "2020-12-19 07:35:40 Etc/GMT",
"grace_period_expires_date_ms": "1607671982000", // (不一定有此字段)订阅续订宽限期的到期时间毫秒值,仅当启用了“计费宽限期”的应用程序以及客户在续订时遇到计费错误时才存在
"price_consent_status": "1" // (不一定有此字段)订阅价格上涨的价格同意状态 1:同意。当通知客户提价后,才显示此字段
}
],
"status": 0
}
status状态值说明:
状态码 | 说明 |
---|---|
0 | 成功 |
21000 | 无法解析JSON请求 |
21002 | 数据类型错误或丢失数据 |
21003 | 收据无法验证 |
21004 | 提供的共享密码与帐户的文件共享密码不匹配 |
21005 | 验证服务器当前不可用 |
21006 | iOS6自动订阅 |
21007 | 此收据来自测试环境,但已发送到生产环境进行验证。将其发送到测试环境 |
21008 | 此收据来自生产环境,但已发送到测试环境进行验证。将其发送到生产环境 |
21010 | 收据不合法 |
21100-21199 | 内部数据访问错误 |
三、客户端实现
待完善
四、服务端实现
待完善
内购的注意事项(本文已考虑)
1、漏单处理:未能提交票据给服务端
这个是一定会存在的,因为用户的一些误操作,造成的漏单基本无法避免,针对这种情况,最终的处理方式就是人工客服。当然,这个过程是可以优化的,开发者可以进行存储订单票据,server存储订单号,本地存储票据。如果用户启动app后,检测到用户上次付了款,但是需要的商品没有给到用户,此时可以自动进行验证并处理,验证通过,就将商品补给用户。
2、二次验证:正式/沙箱
这个步骤必不可少,首先正式环境验证,如果验证通过,说明是线上环境,可以正常操作。如果验证不通过,说明是沙盒环境,需要在沙盒环境下再次验证,沙盒环境下的验证结果会有一个统一的弹框标识[Environment : Sandbox],只要内购没有上线,验证时都是沙盒环境弹框。 二次验证的这个过程可以避免在审核app时,因为没有验证通过直接被拒的风险。二次验证放在server端实现,更加安全。
3、客户端关单:关闭苹果订单
用户再次交易时,如果上次的交易没有被移除,那么此次的交易会一直在队列中等候,无法被提交,所以一定要在上次交易完成时移除交易。但是也不能随意移除交易,提前移除交易有可能导致服务端无法获取到支付完成的票据。
4、游客模式:需要支持游客购买
如果我们的app支持游客使用,那么这个内购就必须要求对游客进行开放,否则审核会被拒绝。
5、服务端票据查询细:共享密钥
服务端通过票据查询订单信息时,如果查询的是订阅型,需要传共享密钥,反之非订阅型则不需要。
6、苹果的回调接口功能
续费成功、退款、取消订阅、订单状态变更
7、同AppleId多设备
用户可以在多个设备用同一个苹果账号进行支付,以及其它逻辑操作
8、不在app内做内购切换
用户可以在appstore订阅中直接切换订阅内容,这情况需要考虑处理
评论区