前言:内购类型有四种:消耗型商品,非消耗型商品,非续期订阅,自动续期订阅. 顾名思义,从中最有难度的就是自动续期订阅的实现,开通自动续期订阅后,订阅会员的处理将会遇到如下问题:自动订阅的到期继续自动订阅的处理,订阅取消的处理,取消后又在App Store开启自动订阅的处理等一系列问题。我希望通过此篇,能提供完整的思路给需要的人,并希望读者能一起探讨成长。
1. 自动续订订阅前期准备
前期准备无非就是在App Store 建内购的选项,这部分网上有很多文章, 完成前期准备及一些常理的知识了解。当然,项目中我也是直接集成IAPHelper https://github.com/saturngod/IAPHelper 进行内购API集成。
2. 自动续订服务端验证
主要参考如下解决:
https://baijunyao.com/article/106
上面的部分解决了消耗型商品内购的功能。
但是此篇是自动订阅:所以需要增加一个参数: password: 秘钥, 就可以了, 但是官方文档说秘钥仅仅用在自动续订上面
大家叫后台加个验证,如果苹果验证返回21004的话(21004 你提供的共享密钥和账户的共享密钥不一致),就加上password字段去验证,可以成功。 秘钥去https://itunesconnect.apple.com/ 里面对应的APP里创建
image
经过验证:购买过自动续期订阅后,验证内购时(即使是消耗型商品)必须带上password字段。
3. 自动续订服务端验证问题
2中主要是手机内购成功后传receipt给服务端,然后去验证的。然后有没有想到很多需解决的问题?
3.1 订阅状态的处理
- 苹果是有提供服务端到服务端通知的处理:
启用针对自动续期订阅的服务器通知:参考https://help.apple.com/app-store-connect/#/dev0067a330b - 当然去看苹果的文档: 应用程序内购买编程指南
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW6 - 状态更新通知
第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。缺了用户无操作自动订阅的通知??! - 用户无操作自动订阅通知
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