Amazon Developer

as

Settings
Sign out
Notifications
Alexa
Amazonアプリストア
Ring
AWS
ドキュメント
Support
Contact Us
My Cases
開発
テスト
公開
収益化
ユーザーエンゲージメント
デバイスの仕様
リソース

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アプリストアを通じてリクエストを開始するクラス
  • PurchasingListenerPurchasingServiceによって開始されたリクエストに対する非同期レスポンスを受け取るインターフェイス

以下の表に、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に指定できる値は、 FULFILLEDEXISTING_PURCHASENOT_ELIGIBLEUNAVAILABLEのいずれかです。
  • 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クラスの一部であり、アプリ内課金を実行するための基盤となります。以降の手順では、上記の呼び出しの実装方法について詳しく説明し、独自のコードを記述するときにモデルとして使用できるサンプルコードを紹介します。

2. PurchasingListenerの実装と登録

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

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

  • (必須)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;
private String currentCountryCode = 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();
      currentCountryCode = response.getUserData().getCountryCode();
      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アプリストアは、アプリがnotifyFulfillment()を呼び出してFULFILLEDというアイテム付与の結果を送信するまで、アイテム付与が完了していない非消費型アイテムのレシートをすべて返します。それ以外の場合、Amazonアプリストアは前回メソッドが呼び出されたとき以降の新しいトランザクションをすべて返します。
    • 消費型アイテム - Amazonアプリストアは、アプリがFULFILLEDというアイテム付与の結果を送信するまで、アイテム付与が完了していない消費型アイテムのレシートをすべて返します。
    • 定期購入型アイテム - Amazonアプリストアは、アプリがアイテム付与の結果を送信していない新しいレシートのみを返します。これらのレシートは、アプリがアイテム付与の結果を送信してレシートを確認するまで、getPurchaseUpdates(false)の呼び出しのたびに返されます。
  • true - ユーザーの購入履歴全体を取得します。データをサーバー側のデータキャッシュなどに保存するか、メモリ内にすべてを保持する必要があります。ユーザーが購入アイテムを復元しようとしている場合や、アプリとAmazonアプリストアの間で整合性の問題が見つかった場合など、ユーザーの購入に関する完全なリストが必要な場合は、trueを使用します。
デバイスキャッシュに関する注意事項

getPurchaseUpdates()メソッドの動作は、クライアント(デバイス)キャッシュに存在するデータによって異なります。ユーザーが新しいデバイスからサインインした場合や、現在のデバイスのキャッシュを消去した場合は、resetの値にかかわらず、初回のgetPurchaseUpdates()の呼び出しではすべてのレシートが返されます。後続の呼び出しでresetfalseに設定すると、前回の呼び出し以降の最新のレシートのみが返されます。

ユーザーが別のデバイスにサインインした場合や、ユーザーがデバイスをリセットした場合など、一部のシナリオではクライアントデバイス上のデータが消去されることがあります。このようなシナリオが発生すると、同じレシートが複数回返される可能性があります。アプリでは、新しいレシートと古いレシートを識別し、それぞれを適切に処理できる必要があります。

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 int サポートは終了しました。Amazonコインは2025年8月20日にサービスを終了しました。
freeTrialPeriod 文字列 定期購入期間中の無料体験期間。無料体験が設定されていて、ユーザーに利用資格がある場合にのみ返されます。
subscriptionPeriod 文字列 SKUの定期購入の期間。期間のSKUに対してのみ返されます。有効な値は WeeklyBiWeeklyMonthlyBiMonthlyQuarterlySemiAnnuallyAnnuallyです。
promotions List<Promotion> ユーザーが利用できるプロモーションの詳細です。期間のSKUに対してのみ返されます。Promotionオブジェクトの詳細については、次の表を参照してください。プロモーション価格を設定する方法については、プロモーション価格の設定を参照してください。
price 文字列 ローカライズされた商品価格(定期購入型アイテムの子SKUの場合は子SKUに関連付けられている)
addons List<String> 特定の基本定期購入型アイテムの期間SKUで利用できる、アドオン定期購入型アイテムの期間SKUのリスト。
bases List<String> 特定のアドオン定期購入型アイテムの期間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を送信します。アイテム付与の結果を送信すると、Amazonアプリストアからアプリに購入レシートが送信されなくなります。次の表は、それぞれのアイテム付与の結果を送信する条件を示しています。

notifyFulfillmentの呼び出しに関するガイドライン
アイテム付与の結果 送信条件
FULFILLED ユーザーがアカウントを正常に作成し、サービスのコンテンツにアクセスできる。
EXISTING_PURCHASE ユーザーに既存のアカウントがあり、サービスの定期購入型アイテムを既に所有している。
NOT_ELIGIBLE ユーザーにサービスのアカウントにサインアップする資格がない。
UNAVAILABLE その他の理由によって注文のアイテムを付与できない。

アイテムをユーザーに付与するには、購入レコードを作成し、そのレコードを永続的な場所に保存します。次のコードは、アイテム付与の結果を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());
    }
}

9. 購入のキャンセル

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

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

Last updated: 2026年5月20日