web-dev-qa-db-ja.com

iOSの自動更新可能なサブスクリプションの(初期の)経験

Appleはついにいわゆる 自動更新可能なサブスクリプション 昨日を導入しました。私はアプリ内購入の経験がほとんどない(サンドボックスのみ)ので、ここですべてがうまくいったかどうかはわかりません。領収書のサーバー側検証が1つ必要のようです。サブスクリプションがまだ有効かどうかを確認する唯一の方法は、元のトランザクションデータをサーバー側に保存することです。このトピックに関するAppleのプログラミングガイドは、私にはすべて不可解です。

私の期待は、ストアキットAPIを介してiTunesに尋ねるだけで、iOSクライアントでのみ作業できることでした。彼/彼女はすでにこの(サブスクリプション-)製品を購入し、はい/いいえの回答を受け取りました一緒に有効期限付き。

自動更新可能なサブスクリプションまたは(どういうわけか似ているように見えるため)非消費可能な製品の経験がある人はいますか?これについての良いチュートリアルはありますか?

ありがとうございました。

28
Kai Huppmann

サンドボックスで実行していて、ほぼライブになっています...

サーバーを使用して領収書を確認する必要があります。

サーバーでは、レシートは常に新たに生成されるため、デバイス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時間以内に更新の料金を請求され、更新の費用を特定します
  • サブスクリプションはユーザーが管理でき、購入後にユーザーのアカウント設定に移動して自動更新をオフにすることができます
  • アクティブなサブスクリプション期間中は、現在のサブスクリプションをキャンセルすることはできません。
  • プライバシーポリシーと利用規約へのリンク
  • 無料試用期間の未使用部分が提供された場合、ユーザーがその出版物のサブスクリプションを購入すると、没収されます。」

更新II

アプリが再び拒否されました。サブスクリプションの受領書は、本番のAppStore検証URLでは検証されません。サンドボックスでこの問題を再現することはできません。私のアプリは問題なく動作します。これをデバッグする唯一の方法は、レビューのためにアプリを再度送信し、サーバーログを確認することです。

更新III

別の拒否。一方、Appleはさらに2つのステータスを文書化しました:

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

レビューのためにアプリを送信する前に、サーバーを本番レシート確認URLに切り替えないでください。そうした場合、検証時にステータス21007が返されます。

今回の拒否は次のようになります。

アプリケーションは、非標準的な方法でアプリ内購入プロセスを開始します。問題の説明に役立つ次の詳細が含まれています。アプリケーションの改訂と再送信を検討してください。

iTunesのユーザー名とパスワードは、アプリケーションの起動直後に要求されます。詳細については、添付のスクリーンショットを参照してください。

なぜこれが起こるのか私には分かりません。以前のトランザクションが復元されているため、パスワードダイアログがポップアップしますか?それとも、アプリストアに商品情報をリクエストするときにポップアップしますか?

更新IV

私は5回の拒否の直後にそれを手に入れました。私のコードは最も明白なエラーを実行していました。トランザクションがアプリに配信されるときは、必ずトランザクションを終了する必要があります。

トランザクションが完了していない場合、トランザクションはアプリに返送され、問題が発生します。

次のように、最初に支払いを開始する必要があります。

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

次に、アプリはすぐにアクティブ状態を辞任し、アプリデリゲートのこのメソッドが呼び出されます。

- (void)applicationWillResignActive:(UIApplication *)application

アプリが非アクティブである間、AppStoreはダイアログをポップアップします。アプリが再びアクティブになると:

- (void)applicationDidBecomeActive:(UIApplication *)application

OSは、次の方法でトランザクションを配信します。

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

受信したトランザクションをfinishTransactionに渡した直後に、メソッドrecordTransactionを呼び出す方法をご覧ください。このメソッドは、アプリサーバーを呼び出し、AppStoreでサブスクリプションの受信確認を行います。このような:

- (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するようにしてください。

また、遭遇する可能性のあるもう1つの問題は、アプリがサンドボックス内にある場合、サンドボックス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ボタンを追加して、1人のユーザーが所有する複数のデバイスの場合を処理することを望んでいました。このボタンは[[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プログラミング体験をお祈りします。 :-)

52
kitschmaster

ユーザーが有効なサブスクリプションを持っているかどうかを判断するには、a)リンク先のドキュメントに記載されているように既存の領収書を検証するか、b)ユーザーにサブスクリプションを再購入してAppleから応答を返すようにする必要があります。

後者はあなたの側でサーバー側の相互作用を必要としませんが、あなたが彼らのサブを確認したいたびにあなたの製品を効果的に「買い戻す」ようにユーザーに促す必要があるので、あなたは間違っていてあなたを拒絶する傾向があります。

したがって、唯一のオプションは-as Apple推奨-)を保存してから、ストアのレシートを確認することです。

さて、理論的にはストアのレシートをデバイスに保存して、その方法で確認できると思います。ただし、新しい検証システムでは共有シークレットが必要であり、アプリ自体にバンドルする必要があるため、これを行うには気が狂う必要があると思います(本当に悪い考えです)。

つまり、「iOSクライアントのみで作業できますか」という質問に対する答えは「技術的には「はい」ですが、セキュリティ上の問題が多数あるため、そうすることはお勧めできません。幸い、構築する必要のあるサーバー側のアーキテクチャは非常に単純です。iTunesのレシートをデバイスのUDIDにリンクするだけで、単純なAPIを使用してデバイスと通信できます。それがうまくいかない場合は、UrbanAirshipなどの既存のサードパーティのアプリ購入ヘルパーが製品に自動更新の登録者を追加することを間もなく確信しています。

ユーザーが別のデバイスで購入すると、Appleは以前の購入を自動的に復元するため、UDIDとレシートのリンクは正常に機能します。したがって、今度は新しいUDIDに関連付けてレシートを再度保存できます。

10
lxt

たぶん、自動更新可能なサンドボックス購入サーバーがダウンしていますか?消耗品/非消耗品/サブスクリプションのサンドボックスアイテムの購入は機能していますが、自動更新可能な購入では次のエラーが返されます。

エラードメイン= SKErrorDomainコード= 0「iTunesStoreに接続できません」UserInfo = 0x15b600 {NSLocalizedDescription = iTunesStoreに接続できません}

2
Adam Libby

サーバーに保存する必要はありません。クライアントでローカルに確認できます。現在、自動更新可能なスクリプトをコーディングしています

しかし、現在、サーバーがダウンしているか何かのようです。 Appleサーバーでの検証が機能しない

0
Erhard Dinhobl