開発者コンソール

Appstore SDK IAPの実装

Appstore SDK IAPの実装

Appstore SDKアプリ内課金(IAP)APIについての理解を深めるために、Android IAPパッケージに含まれているクラスに関する以下の説明に目を通してください。AndroidアプリにIAP APIを組み込む方法については、このページのユースケースとコード例に従ってください。ここに記載されているコード例の多くは、Appstore SDKに含まれている消費型アイテムのIAPサンプルアプリで使用されています。

開始するには、ビデオチュートリアル(日本語字幕付き)を参照してください。アプリにIAPを実装する方法について詳しくは、以降の各セクションで説明します。

Android IAPパッケージについて

com.amazon.device.iapパッケージには、AndroidアプリにIAPを実装するためのクラスとインターフェイスが用意されています。

このパッケージには、次のインターフェイスとクラスが含まれています。

  • ResponseReceiver: Amazonアプリストアからのブロードキャストインテントを受信するクラス
  • PurchasingService: Amazonアプリストアを通じてリクエストを開始するクラス
  • PurchasingListener: PurchasingServiceによって開始されたリクエストに対する非同期レスポンスを受け取るインターフェイス

以下の表に、PurchasingServiceのリクエストメソッドと、各メソッドに対応するPurchasingListenerのレスポンスコールバックを示します。IAP APIを実装する場合は、これらのメソッド、コールバック、レスポンスオブジェクトを頻繁に使用することになります。

PurchasingServiceのメソッド PurchasingListenerのコールバック レスポンスオブジェクト
getUserData() onUserDataResponse() UserDataResponse
getPurchaseUpdates() onPurchaseUpdatesResponse() PurchaseUpdatesResponse
getProductData() onProductDataResponse() ProductDataResponse
purchase() onPurchaseResponse() PurchaseResponse
notifyFulfillment() なし なし
enablePendingPurchases() なし なし

マニフェストの要件

Android APIレベル30以上を対象とするアプリでアプリ内課金(IAP)APIを使用する場合は、アプリから照会する必要のあるパッケージのリストをAndroidManifest.xmlに定義する必要があります。Amazon App TesterとAmazonアプリストアを照会できるようにするには、マニフェストファイルに次のコードを追加します。

<manifest>
...
  <queries>
    <package android:name="com.amazon.sdktestclient" />
    <package android:name="com.amazon.venezia" />
  </queries>
</manifest>

また、ResponseReceiverクラスからインテントを受け取るようにマニフェストを更新する必要もあります。詳細については、ResponseReceiverを参照してください。

ResponseReceiver

IAP APIは、すべての処理を非同期に実行します。アプリでは、ResponseReceiverクラスを通じてAmazonアプリストアからブロードキャストインテントを受信する必要があります。このクラスがアプリ内で直接使用されることはありませんが、アプリでインテントを受信するには、マニフェストにResponseReceiverのエントリを追加する必要があります。次のコード例は、Appstore SDK用のAndroidManifest.xmlファイルにResponseReceiverを追加する方法を示しています。アプリがAndroid 12以降を対象としている場合は、MainActivityResponseReceiverandroid:exportedを明示的にtrueに設定する必要があります。

 <application>
 ...
    <activity android:name="com.amazon.sample.iap.entitlement.MainActivity"
              android:label="@string/app_name" android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <receiver android:name="com.amazon.device.iap.ResponseReceiver" android:exported="true"
              android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY" >
        <intent-filter>
        <action
            android:name="com.amazon.inapp.purchasing.NOTIFY" />
        </intent-filter>
    </receiver>
 </application>

PurchasingService

PurchasingServiceクラスは、情報を取得し、購入を実行して、購入されたアイテムの付与完了をAmazonに通知します。PurchasingServiceには、以下のメソッドが実装されています。コールバックを機能させるには、それぞれのメソッドを以下のように実装する必要があります。

  • registerListener(PurchasingListener purchasingListener): このメソッドは、コールバック発生のメカニズムとして使用されます。registerListener()は、PurchasingServiceクラスのほかのメソッドを呼び出す前に呼び出します。PurchasingListenerオブジェクトを使用すると、ResponseReceiverによってトリガーされるコールバックをアプリでリッスンして処理できるようになります。registerListener()は、アプリのメインUIスレッドのonCreate()メソッド内で呼び出します。
  • getUserData(): 現在ログインしているユーザーのアプリ固有IDとマーケットプレイスを取得するには、このメソッドを呼び出します。たとえば、ユーザーがアカウントを切り替えた場合や、同じデバイスで複数のユーザーがアプリにアクセスした場合、この呼び出しにより、取得するレシートが現在のユーザーアカウントのものであることを確認できます。getUserData()は、onResume()メソッド内で呼び出します。
  • getPurchaseUpdates(boolean reset):すべてのデバイスを対象として、すべての定期購入型アイテムおよび非消費型アイテムの購入情報を取得します。消費型アイテムの購入情報は、購入を行ったデバイスからのみ取得できます。getPurchaseUpdates()は、付与が未完了でキャンセルされた消費型アイテムの購入情報だけを取得します。このメソッドから返されたPurchaseUpdatesResponseデータを保持しておき、その後の呼び出しでは更新分だけをシステムに問い合わせることをお勧めします。レスポンスはページ分割されます。getPurchaseUpdates()は、onResume()メソッド内で呼び出します。
  • getProductData(java.util.Set skus): アプリに表示するSKUセットのアイテムデータを取得するには、このメソッドを呼び出します。getProductData()は、onResume()メソッド内で呼び出します。
  • purchase(java.lang.String sku): 特定のSKUの購入を開始するには、このメソッドを呼び出します。
  • notifyFulfillment(java.lang.String receiptId, FulfillmentResult fulfillmentResult): 指定したreceiptIdFulfillmentResultを送信するには、このメソッドを呼び出します。FulfillmentResultに指定できる値は、FULFILLEDまたはUNAVAILABLEです。
  • enablePendingPurchases(): アプリで子どもと親向けに購入保留機能を有効にするには、このメソッドを呼び出します。PurchaseResponse.RequestStatus値がPENDINGの場合、購入はまだ承認されていません。ステータスが保留中の場合は、ユーザーに非消費型アイテムを付与しないでください。購入が承認されると、onResume()メソッドでgetPurchaseUpdates()を呼び出すと、通常どおりに購入が処理されます。詳細については、購入保留機能の実装を参照してください。

PurchasingListener

非同期コールバックを処理するには、PurchasingListenerインターフェイスを実装します。これらのコールバックはUIスレッドで呼び出されるため、長時間実行されるタスクをUIスレッドで処理しないようにしてください。PurchasingListenerのインスタンスには、次の必要なメソッドを実装する必要があります。

レスポンスオブジェクト

PurchasingServiceを通じて呼び出しを開始すると、対応するレスポンスがPurchasingListenerに送られます。これらのレスポンスでは、それぞれ1つのレスポンスオブジェクトが使用されます。

  • UserDataResponse: 現在ログインしているユーザーのアプリ固有のユーザーIDとマーケットプレイスを提供します。また、クイック定期購入に対するユーザーの同意ステータスも提供します。
  • PurchaseUpdatesResponse: ページ分割されたレシートリストを提供します。レシートはソートされていません。
  • ProductDataResponse: SKUをキーとするアイテムデータを提供します。アイテムデータを入手できないSKUのリストはgetUnavailableSkus()メソッドで取得できます。
  • PurchaseResponse: アプリ内で開始された購入のステータスを提供します。PurchaseResponse.RequestStatusの示す結果がFAILEDになる理由には、単にユーザーが購入手続きをキャンセルしただけの場合もあることに注意してください。

Kotlinアプリ内のレスポンスオブジェクトの例を確認するには、次のGitHubから IAP Kotlinサンプルアプリを複製またはダウンロードしてください。

IAP APIをアプリに組み込む方法

ここまで、IAPの実装に必要なクラスについて少し詳しく説明してきました。次は、アプリにIAPのコードを記述していきます。

このセクションのコードスニペットは、SDKに付属する消費型アイテムのIAPサンプルアプリから引用したものです。

1. プレースホルダーメソッドの作成

コードの骨組みを作るために、以下の場所で以下のメソッドを呼び出すプレースホルダー(スタブコード)を作成します。

registerListener()は、アプリのメインアクティビティのonCreate()メソッド内で呼び出します。ほかの3つのメソッドは、アプリのホームアクティビティ(アプリが使用する主なアクティビティ)のonResume()メソッド内で呼び出します。これら4つの呼び出しはPurchasingServiceクラスの一部であり、IAPを実行するための基盤となります。以降の手順では、上記の呼び出しの実装方法について詳しく説明し、独自のコードを記述するときにモデルとして使用できるサンプルコードを紹介します。

2. PurchasingListenerの実装と登録

ResponseReceiverによってトリガーされるコールバックをアプリでリッスンして処理できるように、onCreate()メソッドにPurchasingListenerを実装して登録します。

以下に示すコードスニペットでは、次のタスクを実行します。

  • (必須)PurchasingListenerを登録する。

  • (任意)購入レシートに関連するデータを格納するための新しいSampleIapManagerインスタンスを作成する。このタスクは省略できますが、その場合はアクセス可能なほかの場所に購入レシートデータを格納する必要があります。データベースを使用するかメモリ内にデータを格納するかは開発者が決定します。

  • (任意)アプリがサンドボックスモードで実行されているかどうかを確認する。Appstore SDKを使用していて、DRM用のLicensingListenerを実装している場合は、LicensingServiceクラスのgetAppstoreSDKModeメソッドを使用します。IAP v2.0を使用している場合は、PurchasingService.IS_SANDBOX_MODEフラグを使用してサンドボックスモードになっているかどうかを確認します。このフラグは、アプリの開発中、App Testerを使用してローカルでアプリをテストする際に役立ちます。

  • (任意)購入保留を有効にする。この機能を使用すると、Amazon Kids内で子どもがアプリ内課金をリクエストし、親にその購入を承認または却下させることができます。親のレスポンスを待つ間、購入は保留状態になります。購入保留機能をセットアップする手順については、購入保留機能の実装を参照してください。

private SampleIapManager sampleIapManager; // 購入レシートデータを格納します(任意)
protected void onCreate(final Bundle savedInstanceState) // onCreateにPurchasingListenerを実装します 
{
  super.onCreate(savedInstanceState);
  // setupApplicationSpecificOnCreate();
  
  // Appstore SDKにApplicationContextを登録し、LicensingListenerの実装(この場合はLicensingCallback)を使用して
  // DRMのライセンスを取得するリクエストを開始します
  LicensingService.verifyLicense(this.getApplicationContext(), new LicensingCallback());
  
  setupIAPOnCreate();
}

private void setupIAPOnCreate() {
  sampleIapManager = new SampleIapManager(this);

  final SamplePurchasingListener purchasingListener = new SamplePurchasingListener(sampleIapManager);
  Log.d(TAG, "onCreate: PurchasingListenerを登録します");

  PurchasingService.registerListener(this.getApplicationContext(), purchasingListener);
  PurchasingService.enablePendingPurchases(); // 購入保留を有効にします

  Log.d(TAG, "Appstore SDKモード:" + LicensingService.getAppstoreSDKMode()); // アプリがテストモードになっているかどうかを確認します
}

3.ユーザー情報の取得

onResume()getUserData()を呼び出して、現在のユーザーに関する情報(ユーザーIDとマーケットプレイス)を取得します。

// ...
private String currentUserId = null;
private String currentMarketplace = null;

// ...

public void onUserDataResponse(final UserDataResponse response) {

  final UserDataResponse.RequestStatus status = response.getRequestStatus();

  switch (status) {
    case SUCCESSFUL:
      currentUserId = response.getUserData().getUserId();
      currentMarketplace = response.getUserData().getMarketplace();
      break;

   case FAILED:
      // ユーザーがデバイスでAmazonにサインインしていないか、
      // アプリとAmazonアプリストアの接続に問題があります。
      // ユーザーと商品に関する詳細が正常に取得されるまで、
      // このデバイスのユーザーでIAPを一時的に無効にします。
      // getProductDataメソッドでProductDataが正常に取得されたら、
      // 再び購入が有効になります。
      iapManager.disableAllPurchases();
      break;
   case NOT_SUPPORTED:
      // デバイスがIAP機能をサポートしていません。
      // このデバイスのユーザーでIAPを無効にします。
      iapManager.disableAllPurchases();
      break;
  }
}

この例では、後で使用できるようにユーザーIDとマーケットプレイスをメモリ内に保持しています。

4.getPurchaseUpdatesメソッドの実装

getPurchaseUpdates()メソッドは、前回の呼び出し以降にユーザーが行った購入トランザクションをすべて取得します。onResume()メソッド内でgetPurchaseUpdates()を呼び出して、最新の更新情報を取得していることを確認します。

このメソッドは、resetというboolean型のパラメーターを受け取ります。取得する情報の量に応じて、この値をtrueまたはfalseに設定します。

  • false - レスポンスとして、前回getPurchaseUpdates()が呼び出されてからの購入履歴がページ分割されて返されます。ユーザーへの付与が完了していない消費型アイテム、非消費型アイテム、定期購入型アイテムの購入に関するレシートが取得されます。Amazonアプリストアでは、基本的にこのアプローチの使用を推奨しています。

  • true - ユーザーの購入履歴全体を取得します。データをサーバー側のデータキャッシュなどに保存するか、メモリ内にすべてを保持する必要があります。ユーザーが購入アイテムを復元しようとしている場合や、アプリとAmazonアプリストアの間で整合性の問題が見つかった場合など、ユーザーの購入に関する完全なリストが必要な場合は、trueを使用します。

getPurchaseUpdatesのレスポンス

ほとんどのシナリオでは、getPurchaseUpdates()からレスポンスが返されます。レスポンスが送信されるのは以下の場合です。

  • 定期購入型アイテムと非消費型アイテム: 定期購入型アイテムと非消費型アイテムの購入では、常にレシートが返されます。
  • 消費型アイテム: 消費型アイテムのトランザクションが正常に完了していて、(notifyFulfillment()を呼び出して)Amazonにアイテムの付与完了を通知する場合、onPurchaseResponse()ではレシートが返されますが、getPurchaseUpdates()ではレシートは返されません。これ以外の場合はすべて、消費型アイテムのレシートが返されます。このgetPurchaseUpdates()メソッドでは、ごくまれに、付与が完了している消費型アイテムの購入情報のみが返されることがあります(アイテムの付与後、Amazonに通知する前にアプリがクラッシュした場合や、Amazon側で問題が発生した場合など)。このような状況では、アイテムを二重に付与することを避けるために、重複しているレシートを無視する必要があります。アイテムの配信時は、何らかの方法で配信状況を記録しておき、2つ目のレシートを受け取っても二重に配信しないようにしてください。
  • キャンセルされた購入: キャンセルされたすべてのタイプ(定期購入型アイテム、非消費型アイテム、消費型アイテム)の購入について、レシートが返されます。

getPurchaseUpdates()からレスポンスが返されると、PurchasingListener.onPurchaseUpdatesResponse()コールバックがトリガーされます。

@Override
protected void onResume() // getPurchaseUpdatesはonResume内でのみ呼び出します
{
  super.onResume();

//...

  PurchasingService.getUserData();

//...

  PurchasingService.getPurchaseUpdates(false);
}

getPurchaseUpdatesのレスポンスの処理

次に、レスポンスを処理する必要があります。

PurchasingListener.onPurchaseUpdatesResponse()コールバックがトリガーされたら、PurchaseUpdatesResponse.getRequestStatus()から返されるリクエストステータスを確認します。RequestStatusSUCCESSFULの場合は、各レシートを処理します。getReceipts()メソッドを使用して、レシートの詳細を取得できます。

ページ分割を処理するには、PurchaseUpdatesResponse.hasMore()の値を取得します。PurchaseUpdatesResponse.hasMore()からtrueが返された場合は、次のサンプルコードに示すように、getPurchaseUpdates()の再帰呼び出しを行います。

public class MyPurchasingListener implements PurchasingListener {
   boolean reset = false;
   //...

   public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
     //...
     // レシートを処理します
     switch (response.getRequestStatus()) {
       case SUCCESSFUL:
         for (final Receipt receipt : response.getReceipts()) {
           // レシートを処理します
         }
         if (response.hasMore()) {
           PurchasingService.getPurchaseUpdates(reset);
         }
         iapManager.refresh();
         break;
       case FAILED:
         // ユーザーデータが存在する場合、FAILEDはAmazonアプリストア側の問題であることを示します。
         // しばらくしてから再試行してください。
         // ユーザーと商品に関する詳細が正常に取得されるまで、
         // このデバイスでIAPを一時的に無効にします。
         // getProductDataメソッドでProductDataが正常に取得されたら、再び購入が有効になります。
         iapManager.disableAllPurchases();
         break;
       case NOT_SUPPORTED:
         // デバイスがIAP機能をサポートしていません。
         // このデバイスでIAPを無効にします。
         iapManager.disableAllPurchases();
     }
   }
   //...
}

レシートの処理

レシートを処理します。SampleIapManager.handleReceipt()メソッドを呼び出して、PurchaseUpdatesResponseの一部として返されたすべてのレシートを処理します。

public void onPurchaseUpdatesResponse(final PurchaseUpdatesResponse response) {
// ....
switch (status) {
case SUCCESSFUL:
	iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
	for (final Receipt receipt : response.getReceipts()) {
		iapManager.handleReceipt(receipt, response.getUserData());
	}
	if (response.hasMore()) {
		PurchasingService.getPurchaseUpdates(false);
	}
	iapManager.refreshOranges();
	break;
}
// ...
}              

5. getProductDataメソッドの実装

同じくonResume()メソッド内で、getProductData()を呼び出します。このメソッドはSKUを検証します。これは、無効なSKUが原因でユーザーの購入が失敗することを避けるためです。

次のサンプルコードでは、アプリの消費型アイテム、非消費型アイテム、定期購入型アイテムのSKUをAmazonで検証します。

protected void onResume() // 商品のSKUの検証はonResume内でのみ行います
{
   super.onResume();

   // ...

   final Set <string>productSkus =  new HashSet<string>();
   productSkus.add( "com.amazon.example.iap.consumable" );
   productSkus.add( "com.amazon.example.iap.entitlement" );
   productSkus.add( "com.amazon.example.iap.subscription" );
   PurchasingService.getProductData(productSkus); // PurchasingListener.onProductDataResponse()をトリガーします

   Log.v(TAG,  "SKUをAmazonで検証します" );
   }

PurchasingService.getProductData()メソッドを呼び出すと、PurchasingListener.onProductDataResponse()コールバックが呼び出されます。ProductDataResponse.getRequestStatus()から返されたリクエストステータスを確認し、この呼び出しによって検証されたアイテムまたはSKUのみを販売します。

成功したリクエスト

RequestStatusSUCCESSFULの場合は、アプリに表示するSKUをキーとして商品データマップを取得します。RequestStatusSUCCESSFULであっても、アイテムデータを入手できないSKUが存在する場合は、ProductDataResponse.getUnavailableSkus()を呼び出して無効なSKUのリストを取得し、アプリのユーザーがそれらの商品を購入できないようにしてください。

アプリ内でIAPアイコンを表示する場合は、AndroidManifest.xmlファイルを編集して、android.permission.INTERNETパーミッションを含める必要があります。

商品データマップには次の値が含まれます。

フィールド データ型 説明
sku 文字列 商品のSKU(Stock-keeping unit)。
title 文字列 ローカライズされた商品タイトル。
description 文字列 ローカライズされた商品説明。
smallIconUrl 文字列 商品の小アイコンのURL。
productType 文字列 商品の種類。有効な値は CONSUMABLEENTITLEDSUBSCRIPTIONです。
coinsRewardAmount 整数 ユーザーがこの商品の購入後に受け取れるAmazonコインの数です。
freeTrialPeriod 文字列 定期購入期間中の無料体験期間。無料体験が設定されていて、ユーザーに利用資格がある場合にのみ返されます。
subscriptionPeriod 文字列 SKUの定期購入の期間。期間のSKUに対してのみ返されます。有効な値は WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnuallyです。
promotions List<Promotion> ユーザーが利用できるプロモーションの詳細です。期間のSKUに対してのみ返されます。Promotionオブジェクトの詳細については、次の表を参照してください。プロモーション価格を設定する方法については、プロモーション価格の設定を参照してください。
price 文字列 ローカライズされた商品価格(定期購入型アイテムの子SKUの場合は子SKUに関連付けられている)

Promotionオブジェクトには次のフィールドが含まれています。

フィールド データ型 説明
promotionType 文字列 プロモーションのタイプ。有効な値は Introductoryです。
promotionPlans List<PromotionalPlan> プロモーションの価格と請求サイクルの詳細。PromotionalPlanオブジェクトの詳細については、次の表を参照してください。

PromotionalPlanオブジェクトには次のフィールドが含まれています。

フィールド データ型 説明
promotionPrice 文字列 プロモーション期間中の期間SKUの価格(ローカライズされた形式)。
promotionPricePeriod 文字列 プロモーションの各請求サイクルの期間。有効な値は WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnuallyです。
promotionPriceCycles 整数 請求サイクルの数。

定期購入のさまざまなユースケースについて、商品データマップの例を確認するには、次のボタンをクリックしてください。これらの例はJSON形式です。

失敗したリクエスト

RequestStatusFAILEDの場合は、次のサンプルコードに示すように、アプリのIAP機能を無効にします。

public class MyPurchasingListener implements PurchasingListener {
   //  ...

   public void onProductDataResponse(final ProductDataResponse response) {
     switch (response.getRequestStatus()) {
       case SUCCESSFUL:
         for ( final String s : response.getUnavailableSkus()) {
           Log.v(TAG,  "アイテムデータを入手できないSKU:" + s);
         }
         // SKUをユーザーが購入することはできません。
         // 利用できないSKUの購入を無効にします。
         iapManager.disablePurchaseForSkus(response.getUnavailableSkus());
         // 利用可能なすべてのSKUの購入を有効にします。
         iapManager.enablePurchaseForSkus(response.getProductData());
         iapManager.refresh();

         final Map <string,>products = response.getProductData();
         for (final String key : products.keySet()) {
           Product product = products.get(key);
           Log.v(TAG, String.format( "商品:%s\n タイプ:%s\n SKU:%s\n 価格:%s\n 説明:%s\n" , product.getTitle(), product.getProductType(), product.getSku(), product.getPrice(), product.getDescription()));
         }
         break;

       case FAILED:
         Log.v(TAG,  "ProductDataRequestStatus: FAILED" );
         // ユーザーデータが存在する場合、FAILEDはAmazonアプリストア側の問題であることを示します。
         // しばらくしてから再試行してください。
         // ユーザーと商品に関する詳細が正常に取得されるまで、
         // このデバイスでIAPを一時的に無効にします。
         // getProductDataメソッドでProductDataが正常に取得されたら、再び購入が有効になります。
         iapManager.disableAllPurchases();
         break;
       case NOT_SUPPORTED:
         // デバイスがIAP機能をサポートしていません。
         // このデバイスでIAPを無効にします。
         iapManager.disableAllPurchases();
     }
   }

   //  ...
}

6. 購入を実行するコードの実装

購入を実行するコードを記述します。ここに示す例では消費型アイテムの購入を実行しますが、定期購入型アイテムや非消費型アイテムでも同様のコードを使用できます。

次のコードは消費型アイテムのサンプルアプリのMainActivityクラスからの抜粋で、PurchasingService.purchase()を呼び出して購入を開始します。このサンプルアプリでは、アプリユーザーが [Buy Orange] ボタンをタップすると、このメソッドが実行されます。

  public void onBuyOrangeClick(final View view) {
     final RequestId requestId = PurchasingService.purchase(MySku.ORANGE.getSku());
     Log.d(TAG, "onBuyOrangeClick: requestId (" + requestId + ")");
  }

次に、SamplePurchasingListener.onPurchaseResponse()コールバックを実装します。以下のコードでは、実際の購入処理はSampleIapManager.handleReceipt()によって行われます。

public void onPurchaseResponse(final PurchaseResponse response) {
	switch (status) {
    // ...
    case SUCCESSFUL:
      final Receipt receipt = response.getReceipt();
      iapManager.setAmazonUserId(response.getUserData().getUserId(), response.getUserData().getMarketplace());
      Log.d(TAG, "onPurchaseResponse: receipt json:" + receipt.toJSON());
      iapManager.handlePurchase(receipt, response.getUserData());
      iapManager.refresh();
      break;
    case ALREADY_PURCHASED:
      // ユーザーは既にアイテムに対するアクティブな非消費型アイテムを持っています。
      // アプリとAmazonアプリストアが同期していません。Amazonアプリストアからすべての購入を再同期します。
      PurchasingService.getPurchaseUpdates(true);
      break;
    case INVALID_SKU:
      Log.d(TAG,
            "onPurchaseResponse:SKUが無効です。購入ボタンは、onProductDataResponseによって既に無効になっています。");
      final Set<String> unavailableSkus = new HashSet<String>();
      unavailableSkus.add(response.getReceipt().getSku());
      iapManager.disablePurchaseForSkus(unavailableSkus);
      break;
    case FAILED:
      // 購入手続きの完了前にユーザーが終了しました。
      Log.d(TAG, "onPurchaseResponse:失敗したため、ローカルストレージから購入リクエストを削除してください。");
      iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
      break;
    case NOT_SUPPORTED:
      Log.d(TAG, "onPurchaseResponse:失敗したため、ローカルストレージから購入リクエストを削除してください。");
      iapManager.showPurchaseFailedMessage(response.getReceipt().getSku());
      // デバイスがIAP機能をサポートしていません。
      // このデバイスでIAPを無効にします。
      iapManager.disableAllPurchases();
      break;
	}
}

7. 購入レシートの処理と購入の完了

購入レシートを処理・検証したら、購入を完了できます。独自のアプリを設計するときは、多くの場合、これらの手順をすべて1か所で処理するアイテム付与のしくみを実装することになります。

アイテムを付与する前に、購入のレシートを検証します。これを行うには、バックエンドサーバーでAmazonのレシート検証サービス(RVS)を使用してreceiptIdを検証します。Amazonでは、RVS Sandbox環境とRVS本番環境の両方を提供しています。RVS SandboxとサーバーをセットアップしてRVSを使用できるようにする方法については、レシート検証サービス(RVS)のドキュメントを参照してください。

  • 開発中は、RVS Sandbox環境を使用して、App Testerで生成されたレシートを検証します。
  • 本番環境では、RVS本番環境エンドポイントを使用します。

以下の例では、handlePurchase()メソッドで、レシートがキャンセルされているかどうかを確認します。

  • キャンセルされたレシートに対して既にアイテム付与が完了している場合は、revokePurchase()メソッドを呼び出して購入を取り消します。
  • ユーザーがAmazon以外で購入したために既にコンテンツにアクセスできる場合は、cancelPurchase()を呼び出して、購入アイテムを付与できないことをAmazonに通知します。
  • レシートがキャンセルされていない場合は、サーバーからRVSを使用してレシートを検証し、grantPurchase()を呼び出して購入アイテムを付与します。
public void handlePurchase(final Receipt receipt, final UserData userData) {
    try {
        if (receipt.isCanceled()) {
            revokePurchase(receipt, userData);
        } else {
            // Amazonでは、レシートの検証はサーバー側で実行することを強く推奨します。
            if (!verifyReceiptFromYourService(receipt.getReceiptId(), userData)) {
                // 購入を検証できない場合は、
                // 適切なエラーメッセージをユーザーに表示します。
                mainActivity.showMessage("購入を検証できませんでした。後でもう一度お試しください。");
                return;
            }
            if (itemAlreadyPurcahsed(receipt, userData)) {
                    // アイテムがほかのストアから既に購入されている場合は、
                    // 購入をキャンセルし、適切なエラーメッセージをユーザーに表示します。
                    cancelPurchase(receipt, userData);
                    return;
            }
            if (receiptAlreadyFulfilled(receipt.getReceiptId(), userData)) {
                // レシートのアイテムが以前に付与されている場合は、付与済みであることを
                // Amazonアプリストアに改めて通知します。
                PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
                return;
            }
            grantPurchase(receipt, userData);
        }
        return;
    } catch (final Throwable e) {
        mainActivity.showMessage("購入を完了できませんでした。もう一度お試しください。");
    }
}

定期購入型アイテムの購入のガイドライン

購入可能アイテムが定期購入型アイテムである場合は、receiptIdの値に関して次の点に注意してください。

  • 定期購入が継続していて途中でキャンセルされたことがない場合、その定期購入型アイテム/ユーザーについてアプリが受け取るレシートは1つだけです。
  • 定期購入が継続的でない場合、たとえば、ユーザーが自動更新を選択せず、定期購入が期限切れになり、その1か月後に再び定期購入を開始した場合、アプリは複数のレシートを受け取ります。

8. Amazonへのアイテム付与の結果の送信とユーザーへのアイテム付与

アイテム付与の結果を送信すると、ユーザーが購入したコンテンツにアクセスできるかどうかをAmazon側で確認できるようになります。アイテム付与の結果は必ずAmazonにお知らせください。notifyFulfillment()メソッドを使用してFulfillmentResultを送信します。

  • ユーザーがアカウントを正常に作成し、サービスのコンテンツにアクセスできる場合は、アイテムを付与し、FulfillmentResult.FULFILLEDを指定してnotifyFulfillment()を呼び出します。この手順が完了すると、Amazonアプリストアからアプリに購入レシートが送信されなくなります。
  • ユーザーに既存のアカウントがあり、サービスの定期購入型アイテムを既に所有している場合や、ユーザーにサービスのアカウントにサインアップする資格がない場合は、FulfillmentResult.UNAVAILABLEを指定してnotifyFulfillment()を呼び出します。

送信するFulfillmentResultの概要については、notifyFulfillmentの呼び出しに関するガイドラインを参照してください。

アイテムをユーザーに付与するには、購入レコードを作成し、そのレコードを永続的な場所に保存します。次のコードは、アイテム付与の結果をAmazonに送信する方法と、ユーザーにアイテムを付与する方法を示しています。

次のコードは、アイテム付与の結果をAmazonに送信する方法と、ユーザーにアイテムを付与する方法を示しています。

private void grantConsumablePurchase(final Receipt receipt, final UserData userData) {
    try {
        // このコードは基本的な実装を示しています。アプリでは、スレッドセーフで
        // トランザクション対応の安定したロジックを実装してください。

        // アプリやサーバーで購入情報を作成し、
        // 購入アイテムをユーザーに付与します。
        createPurchase(receipt.getReceiptId(), userData.getUserId());
        final MySku mySku = MySku.fromSku(receipt.getSku(), userIapData.getAmazonMarketplace());
        // SKUがまだ適用可能であることを確認します。
        if (mySku == null) {
            Log.w(TAG, "レシート内のSKU [" + receipt.getSku() + "] は無効になっています。");
            // SKUが適用不可の場合、ステータスUNAVAILABLEを指定して
            // PurchasingService.notifyFulfillmentを呼び出します。
            updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
            return;
        }

        if (updatePurchaseStatus(receipt.getReceiptId(), PurchaseStatus.PAID, PurchaseStatus.FULFILLED)) {
            // SQLiteデータベースの購入ステータスの更新に成功しました。
            userIapData.setRemainingOranges(userIapData.getRemainingOranges() + 1);
            saveUserIapData();
            Log.i(TAG, "購入ステータスをPAIDからFULFILLEDに正しく更新しました。レシートID:" + receipt.getReceiptId());
            // アイテム付与の結果をAmazonアプリストアに送信します。Amazonが
            // 購入されたアイテム付与の結果を受け取った後、Amazonアプリストアは
            // アプリへの購入レシートの送信を停止します。
            PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
        } else {
            // SQLiteデータベースの購入ステータスの更新に失敗しました。
            // ステータスは既に変更されています。
            // これは通常、同じレシートが別のonPurchaseResponseまたは
            // onPurchaseUpdatesResponseコールバックで更新されたことを意味します。
            // このコードではエラーの記録のみを行います。
            Log.w(TAG, "購入ステータスをPAIDからFULFILLEDに更新できませんでした。レシートID:" + receipt.getReceiptId()
                       + "。ステータスは既に変更されています。");
        }

    } catch (final Throwable e) {
        // 何らかの理由でアプリが購入アイテムを付与できない場合のために、
        // ここに独自のエラー処理コードを追加します。
        // 次回PurchasingService.getPurchaseUpdates APIを呼び出すと、
        // Amazonから消費型アイテムの購入レシートが再度送信されます。
        Log.e(TAG, "購入された消費型アイテムを付与できませんでした。エラー:" + e.getMessage());
    }
}

notifyFulfillmentの呼び出しに関するガイドライン

notifyFulfillment()を呼び出すときに、どのFulfillmentResultを送信するかを判断するには、以下のガイドラインを使用します。

次の条件に該当する場合は、FULFILLEDを送信します。

  • ユーザーがアカウントを正常に作成し、サービスのコンテンツにアクセスできる。

次のいずれかの状況が発生した場合は、UNAVAILABLEを送信します。

  • ユーザーに既存のアカウントがあり、サービスの定期購入型アイテムを既に所有している。
  • ユーザーにサービスのアカウントにサインアップする資格がない。

9.購入のキャンセル

商品の購入をキャンセルするには、永続ストレージとバックエンドサーバーの情報を更新し、ステータスをUNAVAILABLEとしてnotifyFulfillment()を呼び出して、アイテム付与を完了できないことをAmazonに通知します。

private void cancelPurchase(final Receipt receipt, final UserData userData) {
  // アプリやサーバーで購入情報を更新して、
  // ユーザーが重複購入を試みていないかどうかを特定します。
  // アイテムを付与できないことをAmazonに通知するには、
  // アイテム付与ステータスとしてUNAVAILABLEを使用します。
   updatePurchaseStatus(receipt.getReceiptId(), null, PurchaseStatus.UNAVAILABLE);
   PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.UNAVAILABLE);
   return;
}

Last updated: 2024年11月6日