内购有4种:消耗型项目,非消耗型,自动续期订阅,非续期订阅。 其中”非消耗型“和”自动续期订阅“需要提供恢复购买的功能,例如创建一个恢复按钮,不然审核很可能会被拒绝。
xxxxxxxxxx
//调起苹果内购恢复接口
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
“消耗型项目”和“非续期订阅”苹果不会提供恢复的接口,不要调用上述方法去恢复,否则有可能被拒。 “非续期订阅”也是“跨设备同步”的,所以原则上来说也需要提供恢复购买的功能,但需要依靠app自建的账户体系恢复,不能用上述苹果提供的接口。
恢复购买将为用户完成的每个事务创建一个新事务,基本上是为事务队列观察者重新播放历史记录。
内购事务的观察者应尽早设置好,例如在程序启动后马上设置:
xxxxxxxxxx
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/* ... */
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}
完成事务要调用:
xxxxxxxxxx
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
完成事务会告诉StoreKit你已经完成了购买所需的一切。未完成的事务将一直留在队列中,直到它们完成为止,每次启动应用程序时都会调用事务队列观察者,以便应用程序能够继续去完成未被完成的事务。你的应用需要完成每一笔交易,不管交易成功还是失败。 需要注意:在你完成一个事务之后,不应该再对这次交易做任何操作,例如交付产品或者验证交易是否有效等。如果还有工作要做,那证明你还没有准备好完成这个事务。应在所有必要的工作完成后,再调用完成事务的方法。
对于iOS 7及以后的非消耗品和自动续期订阅,使用app收据作为你的持久记录。 对于iOS 7之前版本的非消耗品和自动续期订阅,请使用用户默认系统或iCloud保存持久记录。 对于非续期的订阅,请使用iCloud或你自己的服务器来保存持久记录。 对于消耗品,应用程序更新其内部状态以反映购买情况,但不需要保存持久记录,因为消耗品不会跨设备恢复或同步。
应用程序收据包含用户购买的记录,由苹果公司以密码签署。 对于消耗型项目,信息在付款时添加到收据中,并保留在收据中,直到你完成交易。在你完成交易之后,该信息将在下一次更新收据时被删除——例如,下一次用户进行购买时。 所有其他类型的购买信息在付款时被添加到收据中,并无限期地保留在收据中。
收据的验证应该在服务端去做,这样更加安全。 自动续期订阅验证收据时必须要带上密钥。
获取receiptData:
xxxxxxxxxx
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
if (!receiptData) { /* No local receipt -- handle the error. */ }
/* ... Send the receipt data to your server ... */
测试环境url:https://sandbox.itunes.apple.com/verifyReceipt 正式环境url:https://buy.itunes.apple.com/verifyReceipt 获取收据数据:
xxxxxxxxxx
//先对receiptData用base64编码
NSString *receiptBase64 = [receiptData base64EncodedStringWithOptions:0];
//请求参数包含两个字段:“receipt-data”和”password“,其中”password“是自动续期订阅的密钥,其他类型的内购没有这个密钥就不会加这个参数。
NSDictionary *params = @{@"receipt-data":receiptBase64, @"password":secretKey};
//将params转成jsonData
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params
options:NSJSONWritingPrettyPrinted
error:nil];
//下面就是网络请求,例如
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[req setHTTPBody:jsonData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
...
解析收据: 请求成功后返回的数据类似这个格式:
xxxxxxxxxx
{"status": , //如果收据有效,则为0,其他值表示错误
"receipt": ,
"latest_receipt": ,
"latest_receipt_info": , //只返回包含自动更新订阅的收据。这个键的值是一个包含所有应用程序内购买交易的数组。这排除了已经被你的应用标记为完成的消耗品的交易。
"latest_expired_receipt_info": ,
"pending_renewal_info": ,
"is-retryable": ,}
对于收据中每个字段的说明,参考苹果官方文档: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
这里说明一下自动续期订阅的验证问题: latest_receipt_info中包含所有订阅的交易信息,这是一个数组,里面每个元素代表一次订阅交易,元素中有expires_date_ms的字段表示订阅的过期时间,purchase_date_ms表示订阅的购买时间,product_id表示订阅的产品id,is_trial_period表示是否是免费试用。 需要注意,latest_receipt_info中的元素没有特定的排序,我们应该先将latest_receipt_info中的元素按expires_date_ms升序排序,然后获取最后一个元素,这个元素就是最新的交易信息。 另外,收据的json解析有可能会出错,要注意错误处理。
订阅在购买时全额支付。用户只有联系苹果客服才能获得退款。例如,如果用户不小心购买了错误的产品,可以取消订阅,并发出全额或部分退款。 要检查购买是否已经被“Apple客户支持”取消,请在收据中查找cancel Date字段。如果字段包含日期,无论订阅的过期日期如何,购买都已取消。
假如订阅设定了促销价,例如免费试用,SKProduct对象的introductoryPrice属性会有值,这个属性包含了促销价的信息,开发者应该根据这个属性去显示促销的相关UI。
非续期订阅与自动续期订阅在几个关键方面有所不同。这些差异使你的应用程序具有灵活性,可以根据你的需要实现正确的行为,对于“非续期订阅”: 你的应用程序负责计算订阅有效的时间段,并确定需要向用户提供哪些内容。 你的应用程序负责检测订阅即将过期,并通过再次购买产品提示用户更新订阅。 你的应用程序负责在用户的所有设备提供订阅,并让用户恢复过去的购买。例如,大多数订阅由服务器提供,你的服务器需要一些机制来识别用户,并将订阅购买与购买它们的用户关联起来。
在沙盒环境中自动续期订阅时限会缩短,便于测试:
实际时间 | 测试时间 |
---|---|
1周 | 3分钟 |
1个月 | 5分钟 |
3个月 | 15分钟 |
1年 | 1小时 |
测试订阅每天最多仅能自动续期 6 次。
审核人员会用沙盒账号测试内购,所以在审核的时候,收据验证要用测试环境的url去验证,不然会因为验证失败而被拒绝。 推荐的做法是,验证收据时,如果status==21007,就用测试环境的url再次验证,如果status==21008,就用正式环境的url再次验证。
自动续期订阅需要做一个自动续期会员说明,这个说明放在app的内购页面中,并且在AppStoreConnect中的描述中也要写一次,不然也会被拒绝,怎么写,可以参考优酷,爱奇艺app的描述。
https://help.apple.com/app-store-connect/#/devb57be10e7
作者:dengshunlai
转载请注明!