25

サンドボックス テスター アカウントを使用してアプリ内購入の領収書の検証をテストする方法については、多くの例があります。

しかし、有料アプリ自体の領収書はどうですか?開発環境で App Receipt を取得するにはどうすればよいですか?

やりたいことは次の2つです。

  • アプリを購入していないユーザーによる当社アプリの不正コピーを防止するため。私が見たように、iTune アカウントが接続されていることを検出したアプリはアプリを所有していません (アプリを所有していないユーザーに警告が表示されますが、ユーザーがアプリを使用し続けることを停止できません)。

  • アプリの購入レシートをサーバーに送信します。彼らが私たちのアプリをいつ購入したか、彼らが持ってきたアプリのバージョンを知りたいのです。

4

4 に答える 4

41

答えのほとんどの部分は、Apple のドキュメントに記載されています。しかし、ギャップがあり、objective-c コードは非推奨のメソッドを使用しています。

この Swift 3 コードは、App Receipt を取得し、それを検証のためにアプリ ストアに送信する方法を示しています。必要なデータを保存する前に、必ずアプリ ストアでアプリの領収書を検証する必要があります。アプリ ストアに検証を依頼する利点は、JSON に簡単にシリアル化し、そこから必要なキーの値を引き出すことができるデータで応答することです。暗号化は必要ありません。

Apple がそのドキュメントで説明しているように、好ましいフローは次のようなものです...

device -> your trusted server -> app store -> your trusted server -> device

アプリ ストアがサーバーに戻ると、成功したと仮定して、必要なデータをシリアル化して引き出し、必要に応じて保存します。以下の JSON を参照してください。そして、結果やその他必要なものをアプリに送り返すことができます。

以下ではvalidateAppReceipt()、実際の例にするために、このフローを使用するだけです...

device -> app store -> device

サーバーでこれを機能させるには、サーバーvalidationURLStringを指すように変更し、必要なものを に追加しますrequestDictionary

これを開発でテストするには、次のことを行う必要があります。

  • itunesconnectでサンドボックスユーザーが設定されていることを確認してください
  • テスト デバイスで iTunes と App Store からサインアウトする
  • テスト中、プロンプトが表示されたら、サンドボックス ユーザーを使用します

これがコードです。幸せな道は順調に流れています。エラーと障害点は、単に出力されるか、コメントされます。必要に応じて対処してください。

この部分は、アプリのレシートを取得します。そこにない場合 (テスト中に発生します)、アプリ ストアに更新を要求します。

let receiptURL = Bundle.main.appStoreReceiptURL

func getAppReceipt() {
    guard let receiptURL = receiptURL else {  /* receiptURL is nil, it would be very weird to end up here */  return }
    do {
        let receipt = try Data(contentsOf: receiptURL)
        validateAppReceipt(receipt)
    } catch {
        // there is no app receipt, don't panic, ask apple to refresh it
        let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
        appReceiptRefreshRequest.delegate = self
        appReceiptRefreshRequest.start()
        // If all goes well control will land in the requestDidFinish() delegate method.
        // If something bad happens control will land in didFailWithError.
    }
}

func requestDidFinish(_ request: SKRequest) {
    // a fresh receipt should now be present at the url
    do {
        let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
        validateAppReceipt(receipt)
    } catch {
        // still no receipt, possible but unlikely to occur since this is the "success" delegate method
    }
}

func request(_ request: SKRequest, didFailWithError error: Error) {
    print("app receipt refresh request did fail with error: \(error)")
    // for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
}

この部分は、アプリの領収書を検証します。これはローカル検証ではありません。コメントの注 1 と注 2 を参照してください。

func validateAppReceipt(_ receipt: Data) {

    /*  Note 1: This is not local validation, the app receipt is sent to the app store for validation as explained here:
            https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
        Note 2: Refer to the url above. For good reasons apple recommends receipt validation follow this flow:
            device -> your trusted server -> app store -> your trusted server -> device
        In order to be a working example the validation url in this code simply points to the app store's sandbox servers.
        Depending on how you set up the request on your server you may be able to simply change the 
        structure of requestDictionary and the contents of validationURLString.
    */
    let base64encodedReceipt = receipt.base64EncodedString()
    let requestDictionary = ["receipt-data":base64encodedReceipt]
    guard JSONSerialization.isValidJSONObject(requestDictionary) else {  print("requestDictionary is not valid JSON");  return }
    do {
        let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
        let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"  // this works but as noted above it's best to use your own trusted server
        guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
        let session = URLSession(configuration: URLSessionConfiguration.default)
        var request = URLRequest(url: validationURL)
        request.httpMethod = "POST"
        request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
        let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
            if let data = data , error == nil {
                do {
                    let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
                    print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
                    // if you are using your server this will be a json representation of whatever your server provided
                } catch let error as NSError {
                    print("json serialization failed with error: \(error)")
                }
            } else {
                print("the upload task returned an error: \(error)")
            }
        }
        task.resume()
    } catch let error as NSError {
        print("json serialization failed with error: \(error)")
    }
}

このような結果になるはずです。あなたの場合、これはサーバー上で作業するものです。

{
    environment = Sandbox;
    receipt =     {
        "adam_id" = 0;
        "app_item_id" = 0;
        "application_version" = "0";  // for me this was showing the build number rather than the app version, at least in testing
        "bundle_id" = "com.yourdomain.yourappname";  // your app's actual bundle id
        "download_id" = 0;
        "in_app" =         (
        );
        "original_application_version" = "1.0"; // this will always return 1.0 when testing, the real thing in production.
        "original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
        "original_purchase_date_ms" = 1375340400000;
        "original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
        "receipt_creation_date" = "2016-09-21 18:46:39 Etc/GMT";
        "receipt_creation_date_ms" = 1474483599000;
        "receipt_creation_date_pst" = "2016-09-21 11:46:39 America/Los_Angeles";
        "receipt_type" = ProductionSandbox;
        "request_date" = "2016-09-22 18:37:41 Etc/GMT";
        "request_date_ms" = 1474569461861;
        "request_date_pst" = "2016-09-22 11:37:41 America/Los_Angeles";
        "version_external_identifier" = 0;
    };
    status = 0;
}
于 2016-09-22T22:51:40.520 に答える
6

InApp 購入の実行方法を知っていることを前提としています。

取引が終了した後、領収書を確認する必要があります。

- (void)completeTransaction:(SKPaymentTransaction *)transaction 
{
    NSLog(@"completeTransaction...");
    
    [appDelegate setLoadingText:VALIDATING_RECEIPT_MSG];
    [self validateReceiptForTransaction];
}

製品が正常に購入されたら、検証する必要があります。サーバーがこれを行います。必要なのは、Apple サーバーから返された領収書データを渡すことだけです。

-(void)validateReceiptForTransaction
{
    /* Load the receipt from the app bundle. */
    
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    
    if (!receipt) { 
        /* No local receipt -- handle the error. */
    }
    
    /* ... Send the receipt data to your server ... */
    
    NSData *receipt; // Sent to the server by the device
    
    /* Create the JSON object that describes the request */
    
    NSError *error;
    
    NSDictionary *requestContents = @{ @"receipt-data": [receipt base64EncodedStringWithOptions:0] };
    
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    
    if (!requestData) { 
        /* ... Handle error ... */ 
    }
    
    // Create a POST request with the receipt data.
    
    NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
    
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];
    
    /* Make a connection to the iTunes Store on a background queue. */
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                               
                               if (connectionError) {
                                   /* ... Handle error ... */
                               } 
                               else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   
                                   if (!jsonResponse) { 
                                       /* ... Handle error ...*/ 
                                   }
                                   
                                   /* ... Send a response back to the device ... */
                               }
                           }];
}

応答のペイロードは、次のキーと値を含む JSON オブジェクトです。

状態:

レシートが有効な場合は 0、または以下のエラー コードのいずれか:

ここに画像の説明を入力

iOS 6 スタイルのトランザクション レシートの場合、ステータス コードは特定のトランザクションのレシートのステータスを反映します。

iOS 7 スタイルのアプリ レシートの場合、ステータス コードはアプリ レシート全体のステータスを反映します。たとえば、期限切れのサブスクリプションを含む有効なアプリ レシートを送信すると、レシート全体が有効であるため、応答は 0 になります。

レシート:

検証のために送信された領収書の JSON 表現。

覚えて:

  • サンドボックス環境でレシートの検証が成功すると、ステータス コード21007が返されます。

  • テスト環境では、 https://sandbox.itunes.apple.com/verifyReceiptを URL として使用します。本番環境では、https://buy.itunes.apple.com/verifyReceiptを URL として使用します。

  • サンドボックス環境で購入をテストするには、iTunes Connect でテスト ユーザー アカウントを設定する必要があります。


編集1

transactionReceipt非推奨: iOS 7.0 で最初に非推奨

if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
    // iOS 6.1 or earlier.
    // Use SKPaymentTransaction's transactionReceipt.

} else {
    // iOS 7 or later.

    NSURL *receiptFileURL = nil;
    NSBundle *bundle = [NSBundle mainBundle];
    if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {

        // Get the transaction receipt file path location in the app bundle.
        receiptFileURL = [bundle appStoreReceiptURL];

        // Read in the contents of the transaction file.

    } else {
        /* Fall back to deprecated transaction receipt,
           which is still available in iOS 7.
           Use SKPaymentTransaction's transactionReceipt. */
    }

}
于 2016-06-01T13:20:03.247 に答える