iOS 自动订阅的一些问题 升级套餐 || 降级套餐

交易流程的处理

主要是自动订阅开发是内购中比较麻烦的,细节问题比较多,很多问题需要确定。
例如要保证凭证的上传,漏单问题处理,后台如何判断订单的有效期,和订单的类型等等。

  • 凭证上传,漏单处理问题。
    因为内购整个流程涉及到三方,客户端、苹果服务器和后台,所以要把这个三方的一个内购行为做成一个原子交易,就比较麻烦了。

  • 一般的流程:请求苹果服务器->交易成功回调->获取本地凭证上传后台->后台校验(后台再去远程调用苹果服务)->校验成功后回调客户端->客户端获得权限。
    其中有几个可能出现的问题
    1.上传凭证到后台失败
    2.后台请求苹果失败或网络等一系列原因导致回调客户端失败。

    为了内购流程的闭环,就是一定要收到后台的回调结果才算完成。所以对应 1 、2 两点,可以通过凭证保存到本地,然后再次上传,直到服务器回调有结果。这一点很多文章都说过了,但好像有些细节问题没说,凭证就算不保存,再次获取本地凭证也是可以的,因为凭证中保存了所有的交易记录,但如果切换了苹果ID,就无法通过再次获取本地凭证来完成之前的交易了。所以稳妥起见,还是要保存凭证,同时可考虑保存到keychain中避免app卸载导致的凭证丢失问题(考虑keyChain的容容量问题,数量上应该有所限制,实现一个队列来的处理可能会比较好)。

客户端调试

客户端配合服务器端就比较麻烦了。客户端可以通过凭证向想苹果服务器获取信息验证收据。

文档上有指导:

- (void)query:(NSString *)receipt {
# ifdef DEBUG
//    NSData *receipt; // Sent to the server by the device
    // Create the JSON object that describes the request
    NSError *error;
    NSDictionary *requestContents = @{
    @"receipt-data": receipt,
    @"password" : @"xxxxxxxxxxxxxxxxxxxxxx",//内购的秘钥
    @"exclude-old-transactions":@(YES)
    };
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
    options:0
    error:&error];
    if (!requestData) { /* ... Handle error ... */ }
    // Create a POST request with the receipt data.
    NSURL *storeURL = [NSURL
    URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    // Make a connection to the iTunes Store on a background queue.
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    ///NSURLConnection 已不推荐使用,如果在线上使用要切换到新的api
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        if (connectionError) {
            
        } else {
            NSError *error;
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data
            options:0 error:&error];
            if (!jsonResponse) { /* ... Handle error ...*/ } else {
                
            }
            /* ... Send a response back to the device ... */
            }
    }];
# endif
}

如果仅仅在测试中使用,那么上线前记得要屏蔽代码。

同一个订阅组中更换订阅 xxx_2 升级到 xxx_3

这个更换订阅,目前发现两种情况,两种情况有一个共同点,就是都会出现
pending_renewal_info字段,此字段记录了自动续期的待处理信息,例如订阅的变更等。

参考:https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info

  • 同一个订阅组升级订阅
    pending_renewal_info 形如
 "pending_renewal_info" =     (
                {
            "auto_renew_product_id" = "xxxx_3";//变更的目标订阅
            "auto_renew_status" = 1;
            "original_transaction_id" = 1000000xxxxxx;
            "product_id" = "xxx_2";//原订阅
        }
    );

由便宜的订阅升级到贵的订阅,因为下个订阅会在当前订阅结束才会具体执行变更。所以如果这个时候获取最后一条交易信息,对应的product_id 依然是当前的,如"product_id" = "xxx_2";
此时最后一条记录为

"latest_receipt_info" =     (
                {
            "expires_date" = "2020-03-26 03:43:20 Etc/GMT";
            "expires_date_ms" = 1585194200000;
            "expires_date_pst" = "2020-03-25 20:43:20 America/Los_Angeles";
            "is_in_intro_offer_period" = false;
            "is_trial_period" = false;
            "original_purchase_date" = "2020-03-26 02:54:07 Etc/GMT";
            "original_purchase_date_ms" = 1585191247000;
            "original_purchase_date_pst" = "2020-03-25 19:54:07 America/Los_Angeles";
            "original_transaction_id" = 1000000643447076;
            "product_id" = "xxx_2";
            "purchase_date" = "2020-03-26 03:28:20 Etc/GMT";
            "purchase_date_ms" = 1585193300000;
            "purchase_date_pst" = "2020-03-25 20:28:20 America/Los_Angeles";
            quantity = 1;
        ....省略
        }
    );

如果仅仅判断当前的产品是否生效,判断latest_receipt_info -> expires_date_ms 即可。

............然后有一天到期了,就会变更到目标订阅,此时会收到自动续订,再去获取信息,latest_receipt_info 和 pending_renewal_info 会变化如下:

/// auto_renew_product_id 和  product_id 一致 订阅已经切换过去了。
 "pending_renewal_info" =     (
                {
            "auto_renew_product_id" = "xxx_3";
            "auto_renew_status" = 1;
            "original_transaction_id" = 1000000643xxxx;
            "product_id" = "xxx_3";
        }
    );
    
    //此为变更后的第一次续订,同时这个续订是真正把订阅切换过去
      "latest_receipt_info" =     (
                {
            "expires_date" = "2020-03-26 04:43:20 Etc/GMT";
            "expires_date_ms" = 1585197800000;
            "expires_date_pst" = "2020-03-25 21:43:20 America/Los_Angeles";
            "is_in_intro_offer_period" = false;
            "is_trial_period" = false;
            "original_purchase_date" = "2020-03-26 02:54:07 Etc/GMT";
            "original_purchase_date_ms" = 1585191247000;
            "original_purchase_date_pst" = "2020-03-25 19:54:07 America/Los_Angeles";
            "original_transaction_id" = 1000000643447076;
            "product_id" = "xxx_3";
            "purchase_date" = "2020-03-26 03:43:20 Etc/GMT";
            "purchase_date_ms" = 1585194200000;
            "purchase_date_pst" = "2020-03-25 20:43:20 America/Los_Angeles";
            quantity = 1;
          ....省略
        }
    );

这样的一个方式,根据苹果的内购提示都是很一致的 “当前xxx订阅到期时,将支付新订阅的费用。”。意思不是立刻补钱,也不是立刻切换,而是下次变更订阅的同时,变更收费。

  • 同一个订阅组降级套餐 xxx_3 降级到 xxx_2
    降级订阅与升级订阅有所不同,会立即生效并按照一定的计算方式退费(可能按未使用时间折算返回),这样的意思就是说这个降级订阅会立即产生一条记录,并且pending_renewal_info 里面的product_id 和 auto_renew_product_id 会一致。具体如下:
"latest_receipt_info" =     (
                {
            "expires_date" = "2020-03-26 04:23:23 Etc/GMT";
            "expires_date_ms" = 1585196603000;
            "expires_date_pst" = "2020-03-25 21:23:23 America/Los_Angeles";
            "is_in_intro_offer_period" = false;
            "is_trial_period" = false;
            "original_purchase_date" = "2020-03-26 02:54:07 Etc/GMT";
            "original_purchase_date_ms" = 1585191247000;
            "original_purchase_date_pst" = "2020-03-25 19:54:07 America/Los_Angeles";
            "original_transaction_id" = 1000000643447076;
            "product_id" = "xxx_2";
            "purchase_date" = "2020-03-26 04:08:23 Etc/GMT";
            "purchase_date_ms" = 1585195703000;
            "purchase_date_pst" = "2020-03-25 21:08:23 America/Los_Angeles";
            quantity = 1;
         ......省略
        }
    );
    "pending_renewal_info" =     (
                {
            "auto_renew_product_id" = "xxx_2";
            "auto_renew_status" = 1;
            "original_transaction_id" = 100000064344xxxx;
            "product_id" = "xxxx_2";
        }
    );

因为在这个过程中可能存在这样的疑惑,升级订阅,怎么不产生新的记录?而latest_receipt_info的记录还是老的production_id,让人费解,实际上变化的过程如上面那样。而这个记录是准确的,同时如果记录依然生效读取这个expires_date_ms过期时间也是准确的。

所以在确定订阅生效的情况下,以latest_receipt_info的数据来计算是否过期即可。

以上说明,仅仅测试开发中的分析分享,仅供参考,不一定准确!!!!

参考链接:
官方字段说明
中文文档参考

你可能感兴趣的:(iOS 自动订阅的一些问题 升级套餐 || 降级套餐)