任何(早期)iOS自动续订订阅体验

Kai*_*ann 28 iphone in-app-purchase ipad ios

苹果昨天终于推出了所谓的汽车可再生订阅.由于我只有很少(仅限沙箱)的应用内购买经验,我不确定我是否在这里完成了.似乎需要服务器端验证收据.似乎找出订阅是否仍然有效的唯一方法是在服务器端存储原始交易数据.关于这个主题的苹果编程指南对我来说都很神秘.

我的期望是,我只能与iOS客户端合作,只需通过商店套件api询问iTunes他/她是否已购买此(订阅)产品并收到是/否答案以及到期日期.

有没有人有自动再生订阅的经验或(因为它们看起来有点相似)非消费品?关于这个有什么好的教程吗?

谢谢.

kit*_*ter 53

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

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

在服务器上,您可以使用收据数据记录设备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.
Run Code Online (Sandbox Code Playgroud)

UPDATE

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

在iTunes Connect的元数据中(在您的应用说明中):您需要清楚明确地向用户披露有关您的自动续订订阅的以下信息:  

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

更新II

应用程序再次被拒绝.生产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.
Run Code Online (Sandbox Code Playgroud)

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

这次拒绝如下:

应用程序以非标准方式启动In App Purchase流程.我们已经包含以下详细信息以帮助解释该问题,并希望您考虑修改并重新提交您的申请.

应用程序启动时会立即要求iTunes用户名和密码.有关更多信息,请参阅随附的屏幕截图.

我不知道为什么会这样.是否会弹出密码对话框,因为正在恢复上一个事务?或者它是否从应用程序商店请求产品信息时弹出?

更新IV

我在5次拒绝后得到了它.我的代码做了最明显的错误.人们应该确保在交付给应用程序时始终完成交易.

如果交易没有完成,它们会被送回应用程序,事情就会出现奇怪的错误.

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

//make the payment
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
Run Code Online (Sandbox Code Playgroud)

然后应用程序将很快重新调整其活动状态,并在app委托上调用此方法:

- (void)applicationWillResignActive:(UIApplication *)application
Run Code Online (Sandbox Code Playgroud)

当应用程序处于非活动状态时,App Store会弹出其对话框.随着应用再次变为活动状态:

- (void)applicationDidBecomeActive:(UIApplication *)application
Run Code Online (Sandbox Code Playgroud)

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

- (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;
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后一个完成交易:

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

请参阅如何finishTransaction在将收到的交易传递给之后立即调用方法recordTransaction,然后调用应用服务器并使用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];

}
Run Code Online (Sandbox Code Playgroud)

以前我的代码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
Run Code Online (Sandbox Code Playgroud)

因此,基本上总是会使用生产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];    
Run Code Online (Sandbox Code Playgroud)

}

祝您顺利进行InAppPurchase编程体验.:-)

  • 我知道你刚刚发布了这个,但这是一个很棒的*写作.谢谢你记录这个; 我很确定如果我没有找到这篇文章,我会陷入你所陷入的所有陷阱中. (6认同)

lxt*_*lxt 10

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

后者不需要在您的端部进行任何服务器端交互,但是错误并且可能会让您被拒绝,因为您需要提示用户每次要验证其子服务时有效地"重新购买"您的产品.

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

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

这意味着您的问题的答案"我可以仅与iOS客户合作"是"技术上是的",但由于存在许多安全问题,这样做会非常不明智.幸运的是,您需要构建的服务器端架构非常简单 - 只需将iTunes收据与设备的UDID相关联,并使用简单的API与它们进行通信.如果您无法解决这个问题,我很快就会确定现有的第三方应用程序购买助手(如Urban Airship)会在其产品中添加自动续订功能.

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