iOS内购之架构设计(生产版方案)- 订单支付、订单矫正、恢复购买、掉单补单

太白
2022-05-24 / 0 评论 / 0 点赞 / 2,240 阅读 / 10,109 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-21,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

苹果规定,凡是虚拟的物品(例如: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 内部数据访问错误

官方字段说明文档:https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1

 

三、客户端实现

待完善

 

四、服务端实现

待完善


 

内购的注意事项(本文已考虑)

1、漏单处理:未能提交票据给服务端

这个是一定会存在的,因为用户的一些误操作,造成的漏单基本无法避免,针对这种情况,最终的处理方式就是人工客服。当然,这个过程是可以优化的,开发者可以进行存储订单票据,server存储订单号,本地存储票据。如果用户启动app后,检测到用户上次付了款,但是需要的商品没有给到用户,此时可以自动进行验证并处理,验证通过,就将商品补给用户。

2、二次验证:正式/沙箱

这个步骤必不可少,首先正式环境验证,如果验证通过,说明是线上环境,可以正常操作。如果验证不通过,说明是沙盒环境,需要在沙盒环境下再次验证,沙盒环境下的验证结果会有一个统一的弹框标识[Environment : Sandbox],只要内购没有上线,验证时都是沙盒环境弹框。 二次验证的这个过程可以避免在审核app时,因为没有验证通过直接被拒的风险。二次验证放在server端实现,更加安全。

3、客户端关单:关闭苹果订单

用户再次交易时,如果上次的交易没有被移除,那么此次的交易会一直在队列中等候,无法被提交,所以一定要在上次交易完成时移除交易。但是也不能随意移除交易,提前移除交易有可能导致服务端无法获取到支付完成的票据。

4、游客模式:需要支持游客购买

如果我们的app支持游客使用,那么这个内购就必须要求对游客进行开放,否则审核会被拒绝。

5、服务端票据查询细:共享密钥

服务端通过票据查询订单信息时,如果查询的是订阅型,需要传共享密钥,反之非订阅型则不需要。

6、苹果的回调接口功能

续费成功、退款、取消订阅、订单状态变更

7、同AppleId多设备

用户可以在多个设备用同一个苹果账号进行支付,以及其它逻辑操作

8、不在app内做内购切换

用户可以在appstore订阅中直接切换订阅内容,这情况需要考虑处理

9、ccc

0

评论区