【问题标题】:Any (early) experiences with auto-renewable subscriptions for iOSiOS 自动续订订阅的任何(早期)体验
【发布时间】:2011-06-28 09:53:56
【问题描述】:

Apple 昨天终于推出了所谓的auto-renewable subscriptions。由于我在应用内购买方面的经验很少(仅限沙盒),所以我不确定我在这里是否一切顺利。似乎有人需要对收据进行服务器端验证。找出订阅是否仍然有效的唯一方法似乎是将原始交易数据存储在服务器端。 Apples 关于这个主题的编程指南对我来说都是神秘的。

我的期望是,我只能使用 iOS 客户端,只需通过 store kit api 询问 iTunes,他/她是否已经购买了这个(订阅)产品并收到了是/否的答案 em> 有过期日期。

有没有人使用过自动更新订阅或(因为它们看起来有些相似)非消耗性产品?有没有关于这个的好教程?

谢谢。

【问题讨论】:

    标签: iphone ipad ios in-app-purchase


    【解决方案1】:

    我让它在沙盒中运行,几乎要上线了......

    应该使用服务器来验证收据。

    在服务器上,您可以使用收据数据记录设备 udid,因为收据总是新生成的,并且它可以跨多个设备工作,因为收据总是新生成的。

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

    每当应用程序出现时,都应与商店核对最后一张收据。应用程序调用服务器,服务器与商店进行验证。只要商店返回一个有效的收据应用程序就可以使用该功能。

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

    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.
    

    更新

    我的应用被拒绝了,因为元数据没有明确说明有关自动续订订阅的一些信息。

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

    • 出版物或服务的标题
    • 订阅时长(时间段和/或每个订阅期间的交付次数)
    • 订阅价格和每期价格(如果适用)
    • 付款将在确认购买时从 iTunes 帐户中扣除
    • 订阅会自动续订,除非在当前订阅期结束前至少 24 小时关闭自动续订
    • 将在当前周期结束前 24 小时内向帐户收取续订费用,并确定续订费用
    • 订阅可以由用户管理,并且可以在购买后转到用户的帐户设置来关闭自动续订
    • 在有效订阅期间不允许取消当前订阅
    • 指向您的隐私政策和使用条款的链接
    • 免费试用期的任何未使用部分(如果提供)将在用户购买该出版物的订阅时被没收。”

    更新二

    应用再次被拒绝。订阅收据未通过生产 AppStore 验证 url 进行验证。我无法在沙盒中重现此问题,我的应用程序完美无缺。调试此问题的唯一方法是再次提交应用程序以供审核并查看服务器日志。

    更新 III

    另一个拒绝。与此同时,Apple 记录了另外两种状态:

    #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.
    

    在提交应用程序以供审核之前,不应将服务器切换到生产环境 收据验证网址。如果有,则在验证时返回状态 21007。

    这次拒绝的内容如下:

    应用程序以非标准方式启动应用内购买过程。我们已包含以下详细信息以提供帮助 解释问题并希望您考虑修改并重新提交 你的申请。

    iTunes 用户名和密码会在应用程序启动时立即被要求。请参阅随附的屏幕截图以获取更多信息 信息。

    我不知道为什么会这样。是否因为先前的交易正在恢复而弹出密码对话框?还是在从应用商店请求产品信息时弹出?

    更新四

    我在 5 次被拒绝后马上就收到了。我的代码出现了最明显的错误。确实应该确保在将交易交付到应用程序时始终完成交易。

    如果交易未完成,它们会被送回应用程序,然后出现奇怪的错误。

    需要先发起付款,如下所示:

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

    然后应用程序将很快退出其活动状态,并调用应用程序委托上的此方法:

    - (void)applicationWillResignActive:(UIApplication *)application
    

    当应用程序处于非活动状态时,App Store 会弹出其对话框。当应用再次激活时:

    - (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,然后调用应用服务器并与App Store 进行订阅收据验证。像这样:

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

    以前我的代码仅在我的服务器验证收据后才尝试调用finishTransaction,但到那时交易已经不知何故丢失了。所以请务必尽快finishTransaction

    另一个可能遇到的问题是,当应用程序在沙盒中时,它会调用沙盒 App Store 验证 url,但当它处于审核状态时,它不知何故在世界之间。所以我不得不像这样更改我的服务器代码:

    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
    

    所以基本上总是使用生产 URL 进行验证,但如果它返回 21007 代码,则意味着沙盒收据已发送到生产 URL,然后人们只需使用沙盒 URL 再次尝试。这样,您的应用在沙盒和生产模式下的工作方式相同。

    最后,Apple 希望我在订阅按钮旁边添加一个 RESTORE 按钮,以处理一个用户拥有多个设备的情况。然后此按钮调用[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];,应用程序将与恢复的事务(如果有)一起交付。

    此外,有时测试用户帐户会以某种方式受到污染并且事情停止工作,并且您在订阅时可能会收到“无法连接到 iTunes 商店”的消息。它有助于创建一个新的测试用户。

    下面是其余的相关代码:

    - (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 编程体验。 :-)

    【讨论】:

    • 我知道你前一阵子发过这篇文章,但这是一篇伟大的文章。感谢您记录这一点;我敢肯定,如果我没有找到这篇文章,我会陷入和你一样的陷阱。
    • 当您说“您需要向用户清楚而显着地向用户披露有关您的自动续订订阅的以下信息”时,您的意思是在应用启动时有一条消息说它是基于自动续订的订阅应用程序?如果是这样,您在消息中添加了什么内容?
    • 感谢您的启发性帖子。由于 Apple 在此主题上的糟糕文档,除了令人沮丧的经历之外,其中一些信息真的很难获得。
    • 再次关于元数据:在单个项目的元数据中还是在应用程序的元数据中?我是否仍然需要附上这些信息,或者可能不再需要这些信息?感谢您的帖子和您通过帮助的方式 - 非常有用。
    • @kitschmaster :) 那么答案隐藏在 Apple 文档的深处,它是这样说的:“如果您的订阅价格上涨,那么所有订阅该订阅的用户的订阅特定订阅将被终止,并将通过电子邮件通知他们,并说明原因"。这似乎完全公平,因为不这样做可能会在系统中打开漏洞,比如展示一些东西并收取更高的价格。虽然它没有说明降价时会发生什么,但我认为它只会反映在下一个账单中,就像你说的那样:)。
    【解决方案2】:

    要确定用户是否有有效的订阅,您必须 a) 按照您链接到的文档中的说明验证现有收据,或 b) 让用户重新购买订阅并从 Apple 获得回复。

    后者不需要在您端进行任何服务器端交互,但这是错误的并且可能会被您拒绝,因为每次您想要验证时都需要提示用户有效地“重新购买”您的产品他们的潜艇。

    因此,唯一的选择是 - 正如 Apple 建议的那样 - 存储然后验证商店收据。

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

    这意味着“我可以仅使用 iOS 客户端吗”的问题的答案是“技术上是的”,但由于存在许多安全问题,建议这样做是非常不明智的。幸运的是,您需要构建的服务器端架构非常简单——只需将 iTunes 收据与设备的 UDID 链​​接起来,并使用一个简单的 API 与它们进行通信。如果您无法解决这个问题,我敢肯定,Urban Airship 等现有的第三方应用内购买助手很快就会在他们的产品中添加自动续订订阅服务。

    链接 UDID 和收据可以正常工作,因为当用户在另一台设备上进行购买时,Apple 会自动恢复他们之前的购买。因此,您可以再次保存收据,这次与新的 UDID 绑定。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        无需将其存储在服务器上。您可以在客户端本地验证它。我们目前正在编写一个自动更新脚本

        但目前看来,服务器已关闭或其他原因。苹果服务器验证不起作用

        【讨论】:

          猜你喜欢
          • 2013-10-27
          • 1970-01-01
          • 2018-05-19
          • 2013-05-20
          • 1970-01-01
          • 2020-08-23
          • 1970-01-01
          • 2017-06-16
          • 1970-01-01
          相关资源
          最近更新 更多