苹果内购(IAP)从入门到精通(4) |
您所在的位置:网站首页 › 苹果手机订阅已过期为什么还收钱 › 苹果内购(IAP)从入门到精通(4) |
该系列的其他文章: 【1】苹果内购(IAP)从入门到精通(1)-内购商品类型与配置 【2】苹果内购(IAP)从入门到精通(2)-银行卡与税务信息配置 【3】苹果内购(IAP)从入门到精通(3)- 商品充值流程(非订阅型) 【5】苹果内购(IAP)从入门到精通(5)- 掉单处理、防hook以及一些坑 【6】苹果内购(IAP)从入门到精通(6)- 实际业务结合&线上异常情况处理 1. 充值流程(自动订阅) 1.1. 商品购买等同于消耗型商品的购买。无非也是添加支付队列监听,初始化SKPayment并添加到支付队列中,然后付款,回调Purchased状态。在这里不再赘述。详细看上一篇文章:苹果内购(IAP)从入门到精通(3)- 商品充值流程(非订阅型)。主要的区别是在后面。 1.2. 票据校验请求苹果票据校验时,请求参数需要传一个新的参数,叫“共享秘钥”。这个在苹果后台配置商品ID的地方生成,如下所示:
在in_app内,多了以下几个字段 "expires_date" = "2019-09-18 06:44:15 Etc/GMT"; //订阅到期时间 "expires_date_ms" = 1568789055000; //订阅到期时间戳 "expires_date_pst" = "2019-09-17 23:44:15 America/Los_Angeles"; //订阅到期时间(美国) "is_in_intro_offer_period" = false; //是否在享受优惠期间 "web_order_line_item_id" = 1000000046978708; ////跨设备购买事件(包括订阅更新事件)的唯一标识符。此值是识别订阅购买的主键排查其他商品类型的票据后,我发现,即使是“非续期订阅”商品,也没有这几个字段。所以,可以判定,拥有这几个字段,就说明这个商品是自动订阅商品。(服务端可以用这5个字段来判断,这个票据是不是自动订阅的票据) 校验票据是否合法的逻辑同理于消耗型商品,如果校验通过了。就通知后台下发道具、更新客户端UI等。 2. 订阅续期“自动订阅”商品,顾名思义,苹果会自动扣款续费。 最常见的有两种情况自动订阅商品会失效: (1)用户手动取消订阅。需要用户手动去“设置”里去取消订阅。取消后,从下一个订阅周期开始,将不再扣费。同时,游戏内需要处理这个逻辑,停止下个周期的订阅服务的下发。 (2)购买订阅的AppleID绑定的银行卡或者信用卡没钱了。苹果会反复尝试扣款。如果都没有扣款成功,将停止订阅。(有坑,后面会说) 除此之外,苹果都会自动扣款去续订。有两种方式去判断续订是否成功: 一、每次启动app时,客户端主动获取receipt_data,上传给服务器做票据校验,服务器检查票据内是否有新的续订交易(时间可以判断),有则下发新的订阅商品。 二、server to server的校验方式,也是 苹果推荐的校验方式 ,由苹果主动告知我们状态。需要在appstore connect后台配置订阅状态URL,具体参考苹果官方文档启用针对自动续期订阅的服务器通知,同时也需要用到共享秘钥(上面有说到)。 参考上面的官方文档可以发现,苹果提供了两个server to server的接口,V1和V2。两个版本的接口、参数、回调都不一样。业界内主要用的还是V1版本,更加稳定(听说V2有一些bug),因此我们这里介绍V1版本的: 如果这样配置了server to server的通知,后台就会收到下面的几种状态更新通知类型: NOTIFICATION_TYPE描述INITIAL_BUY首次订阅CANCEL取消订阅DID_RENEW自动续订成功INTERACTIVE_RENEWALApp或者设置内交互式续订DID_FAIL_TO_RENEW计费问题未能续订DID_RECOVER已成功续订过去未能续订的过期订阅DID_CHANGE_RENEWAL_STATUS续订状态发生变化DID_CHANGE_RENEWAL_PREF续订降级CONSUMPTION_REQUEST发起退款申请REFUND退款成功PRICE_INCREASE_CONSENT提价状态INTERACTIVE_RENEWALApp或者设置内以交互方式续订REVOKE不能继续家庭共享通过server to server,服务端开发可以通过状态和回调的original_transaction_id匹配的订单与用户,进行相应的逻辑。 注意:服务端最好处理CANCEL类型。因为IAP存在黑产:比如买了一年会员,然后打电话给苹果客服退款,如果服务端不处理,这一年会员是生效的。 (1)更新票据: 在续费的前10天,Apple会进行续费的前期检查,尽量确保用户能够正常扣款。如果前期检查出了问题,会提醒用户应该处理对应的问题。 在续费的前24小时,Apple会尝试扣款,Apple会尝试几次扣款,如果一直扣款失败会停止扣款,订阅被动取消。注意,如果是支付相关的问题,Apple可能会进行长达60天的尝试。可以通过收据中的is_in_billing_retry_period判断Apple是否还在尝试中。 同一个订单凭据是可以一直使用的,不管你后面续订了多少次,随便这些中的一个凭据发给苹果验证,就能得到所有的订单信息和订阅状态。服务端需要保存这笔订单的recipt_data,并在每个周期结束之前请求苹果票据校验接口,根据返回的票据信息去得到用户是否仍然续订的信息。 续订后,in_app内会多一组新的票据数据。代表新的续订。如下所示: { environment = Sandbox; "latest_receipt" = "==========很长串票据字符============"; "latest_receipt_info" = ( { "expires_date" = "2019-09-23 09:18:17 Etc/GMT"; "expires_date_ms" = 1569230297000; "expires_date_pst" = "2019-09-23 02:18:17 America/Los_Angeles"; "is_in_intro_offer_period" = false; "is_trial_period" = true; "original_purchase_date" = "2019-09-23 09:15:18 Etc/GMT"; "original_purchase_date_ms" = 1569230118000; "original_purchase_date_pst" = "2019-09-23 02:15:18 America/Los_Angeles"; "original_transaction_id" = 1000000571201990; "product_id" = "com.auto.pay6"; "purchase_date" = "2019-09-23 09:15:17 Etc/GMT"; "purchase_date_ms" = 1569230117000; "purchase_date_pst" = "2019-09-23 02:15:17 America/Los_Angeles"; quantity = 1; "subscription_group_identifier" = 20548697; "transaction_id" = 1000000571201990; "web_order_line_item_id" = 1000000047083065; }, { "expires_date" = "2019-09-23 09:21:17 Etc/GMT"; "expires_date_ms" = 1569230477000; "expires_date_pst" = "2019-09-23 02:21:17 America/Los_Angeles"; "is_in_intro_offer_period" = false; "is_trial_period" = false; "original_purchase_date" = "2019-09-23 09:15:18 Etc/GMT"; "original_purchase_date_ms" = 1569230118000; "original_purchase_date_pst" = "2019-09-23 02:15:18 America/Los_Angeles"; "original_transaction_id" = 1000000571201990; "product_id" = "com.auto.pay6"; "purchase_date" = "2019-09-23 09:18:17 Etc/GMT"; "purchase_date_ms" = 1569230297000; "purchase_date_pst" = "2019-09-23 02:18:17 America/Los_Angeles"; quantity = 1; "subscription_group_identifier" = 20548697; "transaction_id" = 1000000571203602; "web_order_line_item_id" = 1000000047083066; } ); "pending_renewal_info" = ( { "auto_renew_product_id" = "com.auto.pay6"; "auto_renew_status" = 1; "original_transaction_id" = 1000000571201990; "product_id" = "com.auto.pay6"; } ); receipt = { "adam_id" = 0; "app_item_id" = 0; "application_version" = 1; "bundle_id" = "com.mytest.0522"; "download_id" = 0; "in_app" = ( { "expires_date" = "2019-09-23 09:21:17 Etc/GMT"; "expires_date_ms" = 1569230477000; "expires_date_pst" = "2019-09-23 02:21:17 America/Los_Angeles"; "is_in_intro_offer_period" = false; "is_trial_period" = false; "original_purchase_date" = "2019-09-23 09:15:18 Etc/GMT"; "original_purchase_date_ms" = 1569230118000; "original_purchase_date_pst" = "2019-09-23 02:15:18 America/Los_Angeles"; "original_transaction_id" = 1000000571201990; "product_id" = "com.auto.pay6"; "purchase_date" = "2019-09-23 09:18:17 Etc/GMT"; "purchase_date_ms" = 1569230297000; "purchase_date_pst" = "2019-09-23 02:18:17 America/Los_Angeles"; quantity = 1; "transaction_id" = 1000000571203602; "web_order_line_item_id" = 1000000047083066; }, { "expires_date" = "2019-09-23 09:18:17 Etc/GMT"; "expires_date_ms" = 1569230297000; "expires_date_pst" = "2019-09-23 02:18:17 America/Los_Angeles"; "is_in_intro_offer_period" = false; "is_trial_period" = true; "original_purchase_date" = "2019-09-23 09:15:18 Etc/GMT"; "original_purchase_date_ms" = 1569230118000; "original_purchase_date_pst" = "2019-09-23 02:15:18 America/Los_Angeles"; "original_transaction_id" = 1000000571201990; "product_id" = "com.auto.pay6"; "purchase_date" = "2019-09-23 09:15:17 Etc/GMT"; "purchase_date_ms" = 1569230117000; "purchase_date_pst" = "2019-09-23 02:15:17 America/Los_Angeles"; quantity = 1; "transaction_id" = 1000000571201990; "web_order_line_item_id" = 1000000047083065; } ); "original_application_version" = "1.0"; "original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT"; "original_purchase_date_ms" = 1375340400000; "original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles"; "receipt_creation_date" = "2019-09-23 09:20:00 Etc/GMT"; "receipt_creation_date_ms" = 1569230400000; "receipt_creation_date_pst" = "2019-09-23 02:20:00 America/Los_Angeles"; "receipt_type" = ProductionSandbox; "request_date" = "2019-09-23 09:20:10 Etc/GMT"; "request_date_ms" = 1569230410417; "request_date_pst" = "2019-09-23 02:20:10 America/Los_Angeles"; "version_external_identifier" = 0; }; status = 0; }我们关注in_app内的如下几个字段: purchase_date:代表当前这笔订单的扣款时间。续订也是扣款。如果是续订,这个时间正好是上一笔扣款数据里purchase_date+订阅周期。 original_purchase_date:代表这个自动订阅商品的第一次购买的时间。服务端可以用来判断这个用户是什么时候开启订阅的。 original_transaction_id:表示第一次订阅的时候的票据ID。因为服务端肯定用自己的订单号(orderId)和这个票据ID进行的绑定。所以要想跟踪是哪笔订单的续订,就使用这个。transaction_id每次都会改变,即使是恢复订阅,transaction_id也会改变,所以不要使用这个字段。(2)票据合法性校验 一般情况下,只要实现了server to server的通知,苹果都会在续订后告知你。但有小概率出现不告知的情况。所因此建议,服务端记录下来最后一个收据(receipt_data),在订阅过期时间expires_date前24小时,定时用最后一条收据轮询,如果用户续费未成功,检查is_in_billing_retry_period,如果这个为true,那么放到下个轮训队列里继续检查,直到is_in_billing_retry_period为false,表示Apple已经放弃了扣款。如果续订成功,服务端拿到最新的票据后,判断时间、商品ID是否合法,原始票据ID对应的订单ID是否合法。之后,给这个订单ID对应的用户进行订阅续期操作,发放相应道具或者权限。 (3)sandbox环境下的订阅周期 在沙盒环境下,测试自动续期订阅时,时限会缩短。此外,每天的订阅次数最多仅能自动续期12次(包括首次订阅)。 实际时限测试时限1 周3 分钟1个月5 分钟2 个月10 分钟3 个月15 分钟6 个月30 分钟1 年1 小时而取消订阅,苹果是没办法模拟的。所以一般是采用新建一个新的沙盒账号去解决。 3. 恢复订阅比如一个用户(人),他在A设备上购买了App的VIP(自动订阅商品)。他买了一台新的手机B,重新下载了这个App。但这个App的VIP是本地Keychain缓存设置。换了设备后,keychain没有了缓存,这个用户在设备B上不享有VIP。但用户付了钱,肯定需要享受服务,所以苹果提供了“restore”服务,恢复订阅的权限。这个是正常的逻辑。 但还有一种情况,是苹果允许,但却很bug的设定。订阅服务是跟AppleID绑定的。但我们每个app基本都是有自己的用户系统,这个用户系统跟AppleID无关(实际上iOS开发者也拿不到用户的AppleID)。所以,苹果要求:只要当前App是由一个已订阅过的AppleID下载的,这个App下的任何App账号,都可以享受这个订阅权限。 是不是很绕?我们举个最简单的例子(真的是最简单的): 用户A,有AppleID_A给这个APP下的账号A购买了会员(自动订阅);这时,用户A换了一个账号B。那么这个账号B也需要享受会员。如果没有享受,APP内可以开放一个“恢复订阅”功能,让用户A可以操作给账号B恢复订阅服务。 因为苹果这个不讲道理的要求,实际上的场景要复杂很多。AB用户、AB设备、AB苹果ID、AB账号、AB角色......我们不在这个地方过分展开说,具体场景需要结合自己的APP进行调试。 恢复流程如下所示。 开启恢复订阅: [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; //恢复已订阅商品点击恢复按钮后,支付队列开启监听订阅商品状态。 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (int i = 0; i |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |