任何(早期)iOS自动更新订阅的经验

苹果昨天终于推出了所谓的自动更新订阅 。 由于我在应用程序内购买方面只有很less的(仅限于沙箱)体验,所以我不确定在这里是否完全正确。 似乎需要服务器端validation收据。 似乎唯一的办法是找出订阅是否仍然有效,是将原始的交易数据存储在服务器端。 关于这个话题的苹果节目指南对我来说都是神秘的。

我的期望是,我只能用iOS客户端工作,只需要通过商店工具包api询问iTunes,他/她是否已经购买了这个(订阅)产品,并且收到一个是/否的答案和一个到期date。

有没有人有自动更新订阅或(因为他们似乎有点类似)非消耗品的经验? 有没有什么好的教程呢?

谢谢。

我已经在沙箱里跑了

应该使用服务器来validation收据。

在服务器上,您可以使用收据数据logging设备udid,因为收据总是刚刚生成,并且可以在多个设备上工作,因为收据总是刚刚生成。

在设备上不需要存储任何敏感数据,也不应该:)

每当应用程序启动时,应该检查最后一个收据。 应用程序调用服务器,服务器与商店进行validation。 只要商店返回一个有效的收据应用程序服务该function。

我开发了一个Rails3.x应用程序来处理服务器端,validation的实际代码如下所示:

APPLE_SHARED_PASS = "enter_yours" APPLE_RECEIPT_VERIFY_URL = "https://sandbox.itunes.apple.com/verifyReceipt" #test # APPLE_RECEIPT_VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt" #real def self.verify_receipt(b64_receipt) json_resp = nil url = URI.parse(APPLE_RECEIPT_VERIFY_URL) http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'}) if resp.code == '200' json_resp = JSON.parse(resp_body) logger.info "verify_receipt response: #{json_resp}" end json_resp end #App Store error responses #21000 The App Store could not read the JSON object you provided. #21002 The data in the receipt-data property was malformed. #21003 The receipt could not be authenticated. #21004 The shared secret you provided does not match the shared secret on file for your account. #21005 The receipt server is not currently available. #21006 This receipt is valid but the subscription has expired. 

UPDATE

我的应用程序被拒绝了,因为元数据没有清楚地说明关于自动更新订阅的一些信息。

在iTunes Connect的元数据(在您的应用程序描述中):您需要清楚明显地向用户透露有关您的自动续订订阅的以下信息:

  • 出版物或服务的标题
  • 订阅的时长(每个订阅期间的时间段和/或交付次数)
  • 订阅价格以及每期的价格(如果适用)
  • 确认购买时,将向iTunes账户收取费用
  • 订阅自动更新,除非自动更新在当前期间结束前至less24小时closures
  • 账户将在本期结束前的24小时内收取续费,并确定续约费用
  • 用户可以pipe理订阅,购买后可以通过转到用户的账户设置closures自动续订
  • 在有效订阅期间不允许取消当前订阅
  • 链接到您的隐私政策和使用条款
  • 如果提供免费试用期的任何未使用部分,则在用户购买该出版物的订阅时将被没收。

更新二

应用程序再次被拒绝。 订阅收据未通过生产AppStorevalidationurl进行validation。 我不能在沙箱中重现这个问题,我的应用程序完美无瑕。 debugging这个的唯一方法是再次提交应用程序进行审查,并查看服务器日志。

更新三

另一个拒绝。 与此同时,苹果还logging了两个状态:

 #21007 This receipt is a sandbox receipt, but it was sent to the production service for verification. #21008 This receipt is a production receipt, but it was sent to the sandbox service for verification. 

在提交应用程序进行审查之前,不应该将服务器切换到生产收据validationurl。 如果有,则在validation时返回状态21007。

这次拒绝是这样写的:

应用程序以非标准方式启动In App Purchasestream程。 我们已经包含了以下细节来帮助解释这个问题,并希望您考虑修改并重新提交您的申请。

iTunes的用户名和密码正在被要求立即启动应用程序。 请参阅附件截图了解更多信息。

我不知道为什么会发生这种情况。 密码对话框是否popup,因为之前的事务正在恢复? 还是在从app store请求产品信息的时候popup?

更新四

我被拒绝了5次之后。 我的代码做了最明显的错误。 应该确保在交付应用程序时始终完成交易。

如果交易没有完成,他们会被送回到应用程序,事情变得奇怪的错误。

首先需要先付款,如下所示:

 //make the payment SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier]; [[SKPaymentQueue defaultQueue] addPayment:payment]; 

然后,应用程序很快就会退出其活动状态,并在应用程序委托中调用此方法:

 - (void)applicationWillResignActive:(UIApplication *)application 

应用程序处于非活动状态时,App Store会popup其对话框。 随着应用程序再次变得活跃:

 - (void)applicationDidBecomeActive:(UIApplication *)application 

操作系统通过以下方式交付交易

 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: { [self completeTransaction:transaction]; break; } case SKPaymentTransactionStateFailed: { [self failedTransaction:transaction]; break; } case SKPaymentTransactionStateRestored: { [self restoreTransaction:transaction]; break; } default: break; } } } 

然后完成交易:

 //a fresh purchase - (void) completeTransaction: (SKPaymentTransaction *)transaction { [self recordTransaction: transaction]; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } 

请参阅如何在收到的事务传递给recordTransaction之后,如何调用方法finishTransaction ,然后调用apps服务器并与App Store进行订阅收据validation。 喜欢这个:

 - (void)recordTransaction: (SKPaymentTransaction *)transaction { [self subscribeWithTransaction:transaction]; } - (void)subscribeWithTransaction:(SKPaymentTransaction*)transaction { NSData *receiptData = [transaction transactionReceipt]; NSString *receiptEncoded = [Kriya base64encode:(uint8_t*)receiptData.bytes length:receiptData.length];//encode to base64 before sending NSString *urlString = [NSString stringWithFormat:@"%@/api/%@/%@/subscribe", [Kriya server_url], APP_ID, [Kriya deviceId]]; NSURL *url = [NSURL URLWithString:urlString]; ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease]; [request setPostValue:[[transaction payment] productIdentifier] forKey:@"product"]; [request setPostValue:receiptEncoded forKey:@"receipt"]; [request setPostValue:[Kriya deviceModelString] forKey:@"model"]; [request setPostValue:[Kriya deviceiOSString] forKey:@"ios"]; [request setPostValue:[appDelegate version] forKey:@"v"]; [request setDidFinishSelector:@selector(subscribeWithTransactionFinished:)]; [request setDidFailSelector:@selector(subscribeWithTransactionFailed:)]; [request setDelegate:self]; [request startAsynchronous]; } 

以前,我的代码只是在我的服务器validation了收据后才试图调用finishTransaction ,但是到那时,事务已经丢失了。 所以只要确保尽快完成交易。

另一个可以碰到的问题是,当应用程序在沙箱中时,它会调用沙箱App Storevalidationurl,但是当它在审核时,它在世界之间是不知何故的。 所以我不得不像这样改变我的服务器代码:

 APPLE_SHARED_PASS = "83f1ec5e7d864e89beef4d2402091cd0" #you can get this in iTunes Connect APPLE_RECEIPT_VERIFY_URL_SANDBOX = "https://sandbox.itunes.apple.com/verifyReceipt" APPLE_RECEIPT_VERIFY_URL_PRODUCTION = "https://buy.itunes.apple.com/verifyReceipt" def self.verify_receipt_for(b64_receipt, receipt_verify_url) json_resp = nil url = URI.parse(receipt_verify_url) http = Net::HTTP.new(url.host, url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'}) if resp.code == '200' json_resp = JSON.parse(resp_body) end json_resp end def self.verify_receipt(b64_receipt) json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_PRODUCTION) if json_resp!=nil if json_resp.kind_of? Hash if json_resp['status']==21007 #try the sandbox then json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_SANDBOX) end end end json_resp end 

因此,基本上一个总是validation生产URL,但如果它返回21007代码,那么这意味着一个沙箱收据发送到生产URL,然后再简单地尝试与沙箱的URL。 这样,您的应用程序在沙箱和生产模式下的工作方式也是一样的

最后,苹果希望我在订阅button旁边添加一个RESTOREbutton,以处理一个用户拥有的多个设备的情况。 这个button然后调用[[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; 并且应用程序将交付恢复的交易(如果有的话)。

此外,有时testing用户帐户受到污染,并停止工作,你可能会得到一个“不能连接到iTunes商店”消息订阅时。 它有助于创build一个新的testing用户。

以下是相关代码的其余部分:

 - (void) restoreTransaction: (SKPaymentTransaction *)transaction { [self recordTransaction: transaction]; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } - (void) failedTransaction: (SKPaymentTransaction *)transaction { if (transaction.error.code == SKErrorPaymentCancelled) { //present error to user here } [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 

}

祝您顺利的InAppPurchase编程经验。 🙂

要确定用户是否具有有效的订阅,您必须a)按照链接的文档中所述validation现有收据,或者b)让用户重新购买订阅,并从Apple获得响应。

后者不需要任何服务器端的交互,但却是错误的,很容易让你被拒绝,因为你需要提醒用户每次你想要validation他们的子文件时有效的“重新购买”你的产品。

所以,唯一的select是 – 正如苹果推荐的那样 – 存储并validation商店收据。

现在,我想在理论上你可以将商店收据保存在设备上,并以此方式进行validation。 不过,我认为你必须疯狂才能做到这一点,因为新的validation系统需要共享的秘密,你必须与应用程序本身捆绑(一个非常糟糕的主意)。

这意味着您的问题“我可以只与iOS客户端合作”的答案是“技术上可行”,但是由于一些安全问题,这样做会非常不方便。 幸运的是,您需要构build的服务器端体系结构非常简单 – 只需将iTunes收据与设备的UDID链接起来,然后使用一个简单的API与之进行通信即可。 如果您无法解决这个问题,我相信很快就会有现成的第三方应用购买助手,比如Urban Airship将自动更新潜艇添加到他们的产品中。

链接UDID和收据工作正常,因为当用户在另一台设备上购买时,苹果会自动恢复以前的购买。 所以你可以再次保存收据,这次绑定到一个新的UDID。

也许自动更新的沙盒购买服务器已closures? 消耗品/非消耗品/订阅沙箱项目购买正在工作,但自动更新购买返回此错误:

错误域= SKErrorDomain代码= 0“无法连接到iTunes Store”UserInfo = 0x15b600 {NSLocalizedDescription =无法连接到iTunes Store}

没有必要将其存储在服务器上。 你可以在客户端本地validation它。 我们目前正在编写一个自动更新脚本

但目前看来,服务器倒塌或什么的。 与苹果服务器validation不起作用