iOS内购:自动续期订阅总结

前言:内购类型有四种:消耗型商品,非消耗型商品,非续期订阅,自动续期订阅. 顾名思义,从中最有难度的就是自动续期订阅的实现,开通自动续期订阅后,订阅会员的处理将会遇到如下问题:自动订阅的到期继续自动订阅的处理,订阅取消的处理,取消后又在App Store开启自动订阅的处理等一系列问题。我希望通过此篇,能提供完整的思路给需要的人,并希望读者能一起探讨成长。

1. 自动续订订阅前期准备

前期准备无非就是在App Store 建内购的选项,这部分网上有很多文章,我直接推荐看此篇 https://www.jianshu.com/p/479cf9e31104 完成前期准备及一些常理的知识了解。当然,项目中我也是直接集成IAPHelper https://github.com/saturngod/IAPHelper 进行内购API集成。

2. 自动续订服务端验证

主要参考如下解决:

http://www.cnblogs.com/zhaoqingqing/p/4597794.html
https://baijunyao.com/article/106

上面的部分解决了消耗型商品内购的功能。
但是此篇是自动订阅:所以需要增加一个参数: password: 秘钥, 就可以了, 但是官方文档说秘钥仅仅用在自动续订上面
大家叫后台加个验证,如果苹果验证返回21004的话(21004 你提供的共享密钥和账户的共享密钥不一致),就加上password字段去验证,可以成功。
秘钥去https://itunesconnect.apple.com/ 里面对应的APP里创建

image

经过验证:购买过自动续期订阅后,验证内购时(即使是消耗型商品)必须带上password字段。

3. 自动续订服务端验证问题

2中主要是手机内购成功后传receipt给服务端,然后去验证的。然后有没有想到很多需解决的问题?
3.1 订阅状态的处理

  1. 苹果是有提供服务端到服务端通知的处理:
    启用针对自动续期订阅的服务器通知:参考https://help.apple.com/app-store-connect/#/dev0067a330b
  2. 当然去看苹果的文档: 应用程序内购买编程指南
    https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW6
  3. 状态更新通知
    第2网址中间能看到如下的介绍:
    A statusUpdateNotification是用于自动续订订阅的服务器到服务器通知服务。通知指定发送通知时的订阅状态。
    要在处理事件时获取最新信息,您的应用应通过App Store验证最新收据。建议您使用状态更新通知服务以及收据验证来验证用户的当前订阅状态并为其提供服务。有关收据验证的信息,请参阅“ 收据验证编程指南”。
    要接收状态更新通知,请在App Store Connect中为您的应用配置订阅状态URL。App Store将通过HTTP POST将JSON对象传送到您的服务器,以获取表6-3中列出的密钥订阅事件。您的服务器负责解析,解释和响应所有statusUpdateNotification帖子。
    但是状态只有如下五种:INITIAL_BUY,CANCEL,RENEWAL,INTERACTIVE_RENEWAL,DID_CHANGE_RENEWAL_PREF。缺了用户无操作自动订阅的通知??!
  4. 用户无操作自动订阅通知
 If you read the description of the RENEWAL event, you will note - "Automatic renewal was successful for an expired subscription. Check Subscription Expiration Date to determine the next renewal date and time." In general, iTunes will attempt to charge the user account a day before an auto-renewing subscription is scheduled to expire. If the renewal is successful, there is no server-to server notification because the auto-renewing subscription did not enter into an expired state. However, in the few cases that iTunes is unable to renew the subscription (generally there was a connection problem with the credit card server) and the auto-renewing subscription is not renewed before the expiration_date passes, the auto-renewing subscription is technically considered “expired”. However, iTunes will still continue to attempt to renew the subscription. It iTunes is successful, then the “RENEWAL” event is sent. for this reason, the advice is presented - “Check Subscription Expiration Date to determine the next renewal date and time.”

主要处理如下:
如果您阅读了RENEWAL事件的描述,您将注意到 - “过期订阅的自动续订成功。检查订阅到期日期以确定下一个续订日期和时间。” 通常,iTunes会在计划自动续订订阅到期前一天尝试向用户帐户收费。如果续订成功,则没有服务器到服务器通知,因为自动续订订阅未进入过期状态。但是,在少数情况下,iTunes无法续订订阅(通常与信用卡服务器存在连接问题)并且在expiration_date通过之前未续订自动续订订阅,从技术上讲,自动续订订阅被视为“过期”。然而,iTunes仍将继续尝试续订订阅。iTunes成功,然后发送“RENEWAL”事件。出于这个原因,提出了建议 - “检查订阅到期日期以确定下一个续订日期和时间”。

要验证自动续订订阅In-App Purchase是否是最新的,请使用verifyReceipt服务器验证appStoreReceipt。假设in_app数组中存在自动续订订阅项,则查看latest_receipt_info记录并查找具有晚于当前日期的expires_date的订阅记录,其中一个未设置cancellation_date字段。备注:被退款订单的唯一标识是:它带有一个cancellation_date字段。
参考:https://forums.developer.apple.com/message/283579#283579

注意

in_app与latest_receipt_info
测试时发现,这两个字段的数值几乎相同,不过有几点需要注意:
(1)自动续订订阅类型,在到期后会再生成一条购买记录,这条记录会出现在last_receipt_info里,但不会出现在in_app里
(2)自动续订订阅类型可以配置试用,试用记录只有在latest_receipt_info里,is_trial_period字段才是true
(3)消耗型购买记录有可能不会出现在latest_receipt_info,因此需要检查in_app来确保校验正确

用户取消订阅

购买了一个订阅后得全额付款,只有通过联系苹果客服服务才能退款。 比如,如果用户意外买错了产品,客服中心可以取消该交易并退款。 用户不能在订阅周期中间改变注意不支付剩余的订阅。
要想确认某次交易是否已经被取消,在收据 (receipt) 中查找 Cancellation Date (取消日期)字段。 如果该字段有日期,不管该订阅的过期日期是什么,该交易都已经被取消---取消交易就是跟没有购买过一样。
根据产品类型,只能检查当前的活动交易,可能需要检查过去所有的交易。比如,杂志应用需要检查过去所有的交易来决定用户访问了那些期刊。

附下自动订阅的小票:

string(42) "https://buy.itunes.apple.com/verifyReceipt"
string(16) "{"status":21007}"
string(46) "https://sandbox.itunes.apple.com/verifyReceipt"
string(9266) "{"status":0, "environment":"Sandbox", 
"receipt":{"receipt_type":"ProductionSandbox", "adam_id":0, "app_item_id":0, "bundle_id":"com.mbalib.ios.wiki", "application_version":"244", "download_id":0, "version_external_identifier":0, "receipt_creation_date":"2018-07-18 07:55:17 Etc/GMT", "receipt_creation_date_ms":"1531900517000", "receipt_creation_date_pst":"2018-07-18 00:55:17 America/Los_Angeles", "request_date":"2018-07-18 07:55:46 Etc/GMT", "request_date_ms":"1531900546430", "request_date_pst":"2018-07-18 00:55:46 America/Los_Angeles", "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", "original_application_version":"1.0", 
"in_app":[
{"quantity":"1", "product_id":"com.mbalib.ios.wiki_r", "transaction_id":"1000000419163890", "original_transaction_id":"1000000419163890", "purchase_date":"2018-07-18 07:55:15 Etc/GMT", "purchase_date_ms":"1531900515000", "purchase_date_pst":"2018-07-18 00:55:15 America/Los_Angeles", "original_purchase_date":"2018-07-18 07:55:17 Etc/GMT", "original_purchase_date_ms":"1531900517000", "original_purchase_date_pst":"2018-07-18 00:55:17 America/Los_Angeles", "expires_date":"2018-07-18 08:55:15 Etc/GMT", "expires_date_ms":"1531904115000", "expires_date_pst":"2018-07-18 01:55:15 America/Los_Angeles", "web_order_line_item_id":"1000000039541410", "is_trial_period":"true", "is_in_intro_offer_period":"false"}]}, 
"latest_receipt_info":[
{"quantity":"1", "product_id":"com.mbalib.ios.wiki_r", "transaction_id":"1000000419163890", "original_transaction_id":"1000000419163890", "purchase_date":"2018-07-18 07:55:15 Etc/GMT", "purchase_date_ms":"1531900515000", "purchase_date_pst":"2018-07-18 00:55:15 America/Los_Angeles", "original_purchase_date":"2018-07-18 07:55:17 Etc/GMT", "original_purchase_date_ms":"1531900517000", "original_purchase_date_pst":"2018-07-18 00:55:17 America/Los_Angeles", "expires_date":"2018-07-18 08:55:15 Etc/GMT", "expires_date_ms":"1531904115000", "expires_date_pst":"2018-07-18 01:55:15 America/Los_Angeles", "web_order_line_item_id":"1000000039541410", "is_trial_period":"true", "is_in_intro_offer_period":"false"}], 
"latest_receipt":"MII.....", 
"pending_renewal_info":[
{"auto_renew_product_id":"com.mbalib.ios.wiki_r", "original_transaction_id":"1000000419163890", "product_id":"com.mbalib.ios.wiki_r", "auto_renew_status":"1"}]}"
{"error":"\u8be5\u4ea7\u54c1\u4e0d\u5b58\u5728","errorno":13105}

添加自动订阅免费试用:

image

官方文档 https://help.apple.com/app-store-connect/#/dev7e89e149d

如果用户再同一自动订阅组,如用户自动订阅买过,免费试用标识就要隐藏掉。因为内购支付购买不会出现免费试用。
首次购买 is_trial_period = true; is_in_intro_offer_period是否是否是在试用期 ; expires_date-purchase_date 就是免费的周期

自动订阅expires_date 会苹果会自动订阅,然后App启动会- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

在APP启动时候要增加侦听:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchasing: // 0
                break;
            case SKPaymentTransactionStatePurchased: // 1
                 //订阅特殊处理
                 if(transaction.originalTransaction){
                      //如果是自动续费的订单originalTransaction会有内容 
                 }else{
                      //普通购买,以及 第一次购买 自动订阅
                 }
                break;
            case SKPaymentTransactionStateFailed: // 2
                [self failTracker:transaction];
                break;
            case SKPaymentTransactionStateRestored: // 3
                [self restoreTransaction:transaction];
                
                break;
            default:

                break;
        }
    }
}
    [latest_receipt_info] => Array
        (
            [0] => Array
                (
                    [quantity] => 1
                    [product_id] => com....
                    [transaction_id] => 1000000498609863
                    [original_transaction_id] => 1000000498609863
                    [purchase_date] => 2019-01-30 09:12:46 Etc/GMT
                    [purchase_date_ms] => 1548839566000
                    [purchase_date_pst] => 2019-01-30 01:12:46 America/Los_Angeles
                    [original_purchase_date] => 2019-01-30 09:12:47 Etc/GMT
                    [original_purchase_date_ms] => 1548839567000
                    [original_purchase_date_pst] => 2019-01-30 01:12:47 America/Los_Angeles
                    [expires_date] => 2019-01-30 09:15:46 Etc/GMT
                    [expires_date_ms] => 1548839746000
                    [expires_date_pst] => 2019-01-30 01:15:46 America/Los_Angeles
                    [web_order_line_item_id] => 1000000042488789
                    [is_trial_period] => true
                    [is_in_intro_offer_period] => false
                )

        )

让用户管理订阅
不需要编码实现订阅管理 UI ,应用程序可以打开以下 URL

https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/manageSubscriptions

参考:

  1. https://www.jianshu.com/p/f2d30aa59cbb
  2. https://www.jianshu.com/p/17e0d11149f3

记录:微信和支付宝支付-看我的,用我的就够了https://www.jianshu.com/p/020621c3a660#

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • App Store 审核指南2017/07/27 简介 App 正在改变世界,丰富人们的生活,并为像您一样的开发者...
    小白沐春风阅读 3,484评论 0 2
  • 最近一直忙于公司项目、今天抽空总结了一下关于上线审核的一些变动!和大家分享一下相互学习。 这两天发现苹果在审核指南...
    _VisitorsZsl阅读 884评论 0 1
  • 像我这么完美的人,从每天的读书健身,碰到那个最爱的人,遇到此生为之付出生命的事业,为所有的家人朋友锦囊相助。学习了...
    徐栋_8632阅读 350评论 0 0
  • 什么是母子之间的分离焦虑呢?给大家提供三个场景: 场景一:亮亮,十个月大的男孩,和父母都在爷爷奶奶家里玩,然后,母...
    天星心理咨询阅读 2,013评论 0 4