iOS 自动订阅开发

更新开局一张图:

iOS_IAP_UML.png

一、代码逻辑

关于iOS 订阅、自动订阅 本身功能开发很简单。跟正常的购买没什么大的差异。唯一需要特殊处理(自动订阅)的是,
在APP启动时候要增加侦听:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

因为自动订阅,除了第一次购买行为是用户主动触发的。后续续费都是Apple自动完成的,一般在要过期的前24小时开始,苹果会尝试扣费,扣费成功的话 会在APP下次启动的时候主动推送给APP。所以,APP启动的时候一定要添加上面的那句话。

另外就是处理续费了:

- (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;
        }
    }
}

上述代码片段对 transaction.originalTransaction 进行了判断,如果有内容一定为订阅类型的。为什么在这加个判断处理,是因为续费 是发生在APP启动的时候,这时候你登录流程等可能还没有走完,因为有的游戏在跟服务器进行 校验的时候会传一些userid等信息,或是加密的信息,视情况而定,是否要区分处理。
注意点:就是在沙箱环境测试时候,APP启动可能得到5次的 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions ;订单处理,算是要并发处理case SKPaymentTransactionStatePurchased: 这种case,这时候你得注意你得网络请求队列,不要最后一个订单请求覆盖了 前几个。(可以用信号量处理下,比较简单)

二、服务器验证receipt

服务器在校验receipt时候也就有一个坑:

1、那就是创建自动订阅的时候需要新建一个共享秘钥,就是一串字母。

2、服务器在向苹果服务器校验receipt时候,不仅需要传receipt,还需要传秘钥。

{
    “receipt-data” : “(actual receipt bytes here)”
    “password” : “(shared secret bytes here)”
}

3、介绍下receipt结构
 receipt通过base64解码可得:

{
    "signature" = "dfreree...."; //也是base64 
    "purchase-info" = "ewoJIm9x....."; //也是base64,这个里面存放详细时间,流水号等
    "environment" = "Sandbox";
    "pod" = "100";
    "signing-status" = "0";
}

"purchase-info"可以再次base64解码可得:

{
    "original-purchase-date-pst" = "2017-08-29 23:52:45 America/Los_Angeles";
    "purchase-date-ms" = "1504144439749";
    "unique-identifier" = "a063c2c321dd885642a5cddd9160e0ad8291d978";
    "original-transaction-id" = "1000000328915948";
    "expires-date" = "1504144739749";
    "transaction-id" = "1000000329310742";
    "original-purchase-date-ms" = "1504075965000";
    "web-order-line-item-id" = "1000000036091900";
    "bvrs" = "1";
    "unique-vendor-identifier" = "B78549AC-58D4-4750-8E6F-F4CCE6138A5A";
    "expires-date-formatted-pst" = "2017-08-30 18:58:59 America/Los_Angeles";
    "item-id" = "1276511095";
    "expires-date-formatted" = "2017-08-31 01:58:59 Etc/GMT";
    "product-id" = "lcm.denachina.pickle.38.1month";
    "purchase-date" = "2017-08-31 01:53:59 Etc/GMT";
    "original-purchase-date" = "2017-08-30 06:52:45 Etc/GMT";
    "bid" = "com.denachina.pickle";
    "purchase-date-pst" = "2017-08-30 18:53:59 America/Los_Angeles";
    "quantity" = "1";
}

你想要的东西,都可以获取到。客户端可以做这些事情,但是没有多大必要,还是服务器处理得好。(对于无服务器APP只能客户端处理了)
附上一个在线base64解码的:http://base64.xpcha.com/

补充:

订阅变化后->苹果订阅服务器通知app服务器设置:https://help.apple.com/app-store-connect/#/dev0067a330b

三、自动续费测试

重点都不是上面的,重点是测试,如何测试?尤其自动续费怎么测?
先看下Apple原文档:
When testing auto-renewable subscriptions in the test environment, keep in mind that the duration times are compressed. Additionally, test subscriptions only auto-renew a maximum of six times. Table 3-1 lists the compressed duration times.

Actual duration Test duration
1 week 3 minutes
1 month 5 minutes
2 months 10 minutes
3 months 15 minutes
6 months 30 minutes
1 year 1 hour

意思就是,沙箱环境 自动续费时间缩短了,一周 对应 三分钟,一月 对应 五分钟。。。
购买完一个一周 类型订阅,就不要在APP不退出的情况等待了,必须3分钟 或是 10分钟后重新登录,Apple才会主动告知你结果,也就是第一点提到的。

测试中会遇到几个问题:

1.沙箱环境自动续费是一定会自动续费的吗?
答案:不一定的,有时候会,有时候不会。所以要多测测,多建几个测试账号。

2.是否需要实现restoreCompletedTransactions ?
答案:视需求吧。有少量文章说2014年起苹果审核严格了,必须要有一个按钮实现restoreCompletedTransactions。另外,我听百度一位同学说,爱奇艺2015年因为这个被拒过。但是,目前来看很多使用了订阅的应用或是游戏,并没有这个功能。
我是感觉,看需求了。订阅 是跟着 userid 唯一呢? 还是跟着apple id 呢?在国内,一般都是前者。

四、讨论

1.自动订阅归属的问题:
a. 苹果设计自动订阅的初衷是 ,订阅一个服务, 这个服务需要跟着 Apple ID走。说白了,就是你A设备 用了Apple账号100001购买了,你换了B设备 用Apple账号100001登录app store,你同样能享受到服务。国外的一些音乐类型、杂志报刊等用的比较多,游戏类的少,苹果自己的Apple music也有自动订阅(首创)。

b. 目前国内的一些应用或是游戏,希望的是自动订阅 关联的是 APP的 user id ,而不是Apple ID。说白了,就是你购买了一个自动订阅服务,我不管你哪个apple id 支付的, 但是只能我一个 APP的 唯一用户可以享受服务。这时候就需要APP自身做处理了,就是记住首次购买的transaction-id,并且绑定某个用户。以后自动续费的话,都会有original-transaction-id,这个id 是第一次购买的transaction-id,根据这个服务器可以联系初始购买的服务。有点描述偏了,当transaction-id绑定了用户,再次收到其它用户transaction-id请求时候,视情况处理了。(你也可以根据unique-vendor-identifier处理)

2.同一个Apple ID购买完的自动订阅,可以再次点击购买吗(有效期内)?
答案:不可以,苹果自身会拦截,会出现这么个提示窗,如下图:


但是,sandbox测试环境,在第三大点的对应表格对应时间内,apple会拦截的,过了这个时间苹果是不会拦截的。

3.购买了自动订阅3个月的,可以换购 1年的 或是 1个月的吗?
答案:可以,苹果文档有提到,视为升级订阅套餐 或是 降级订阅套餐。

4.关于掉单的问题
答案:一定要在服务器校验完票据后,客户端收到服务器的反馈结果后再:
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

5.关于普通消费商品,如何防止黑卡、掉单、外币等?
我有时间会再写一篇。

6.沙箱测试时候,偶发的启动时候收到多个(有可能40个以上)票据?
这种情况一般发生在沙箱环境,一个账号多次测试订阅会偶发遇到,这里要注意这些票据的校验,要不批处理,要不通过信号量控制一下请求数。(PS:处理不好容易造成内存问题,小心!)

五、了解更多

https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW10
https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html
https://stackoverflow.com/questions/8033673/ios-sandbox-environment-auto-renewal-subscription
http://www.jianshu.com/p/28fc3cc8c49f
http://www.cnblogs.com/zidong0822/p/4701839.html
http://blog.csdn.net/xiaoyuanzhiying/article/details/46708043
http://www.jianshu.com/p/e9e4dc3dc9ee
https://www.raywenderlich.com/154737/app-purchases-auto-renewable-subscriptions-tutorial
http://www.360doc.com/content/14/1118/16/12282510_426165722.shtml

2019.11.13更新~

介于很多同学还在追问,以及之前关于服务器这块校验逻辑没有说的很明确,导致很多同学依然一脸懵逼的状态。特此更新一下~

问得较多的问题

1.续订后original-transaction-id会变化吗?
答:不会。

2.sandbox测试过程中会产生几十个票据,怎么办?
答:sandbox会出现这样的现象,建议通过信号量 或是 批处理 进行处理,处理不当会crash。(不懂信号量的自行google)

3.sandbox怎么测试取消订阅?
答:自己模拟啊,模拟苹果的post请求,先看苹果的server-to-server(https://developer.apple.com/documentation/storekit/in-app_purchase/enabling_server-to-server_notifications?language=objc),其中notification_type 对应值为 CANCEL ,其它字段按需要改成之前订阅的数据,向你们服务器发送请求啊,就点拨到这了。

4.自己的服务器怎么处理苹果的续订?
答:

  • 首先用户第一次购买订阅,server需要把票据存储(最好把过期时间也记录一下,字段record_expires_date),苹果会通知我们的server的,其中notification_type 对应值为 INITIAL_BUY。
  • 服务器需要做个定期(每天)检测,检测目前已有的所有订阅订单是否过期,如果发现过期了,就去苹果服务器验证receipt,其中苹果返回的latest_receipt_info 字段,会告诉最新的订阅订单情况,你可以校验expires-date与当前时间比较,判断该订阅有没有续订成功,并同时更新上述让记录的record_expires_date字段.
  • 我们为什么做上述的处理?大家都知道苹果服务器会在订阅过期的前一天,对用户进行自动扣费,如果扣费成功了,苹果服务器并不会通知我们的服务器,这是重点。不过有个特例,如果苹果订阅过期前一天扣费失败了,苹果服务器后面几天还会尝试对用户自动扣费,如果后面扣费成功了(这时候用户实际状态是没有续订成功),苹果会通知我们的server的,其中notification_type 对应值为 RENEWAL,对于RENEWAL我们还是需要给用户更新为正在订阅的状态。
  • 正式环境下,用户主动取消订阅,苹果会通知我们的server的,其中notification_type 对应值为 CANCEL,我们需要更新用户订阅的状态为取消。
  • 总结,对于自动续订订阅,我们自己的服务器完全可以与apple server的交互应对用户的订阅状态,只需要确定客户端传来的用户第一次购买, user id 对应 original-transaction-id的关系。后面的续订,取消,变更套餐,完全不依赖于客户端传来的信息。

更新退单处理:
https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications

追加一篇黑产预防:https://www.jianshu.com/p/2f3f9a0673a3

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

推荐阅读更多精彩内容