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以降を対象としている場合は、MainActivityとResponseReceiver(英語のみ)でandroid: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)(英語のみ): 指定したreceiptIdのFulfillmentResult(英語のみ)を送信するには、このメソッドを呼び出します。FulfillmentResult(英語のみ)に指定できる値は、FULFILLED(英語のみ)またはUNAVAILABLE(英語のみ)です。enablePendingPurchases()(英語のみ): アプリで子どもと親向けに購入保留機能を有効にするには、このメソッドを呼び出します。PurchaseResponse.RequestStatus値(英語のみ)がPENDING(英語のみ)の場合、購入はまだ承認されていません。ステータスが保留中の場合は、ユーザーに非消費型アイテムを付与しないでください。購入が承認された場合、onResume()メソッドでgetPurchaseUpdates()(英語のみ)を呼び出すと、通常どおりに購入が処理されます。詳細については、購入保留機能の実装を参照してください。
PurchasingListener
非同期コールバックを処理するには、PurchasingListenerインターフェイス(英語のみ)を実装します。これらのコールバックはUIスレッドで呼び出されるため、長時間実行されるタスクをUIスレッドで処理しないようにしてください。PurchasingListener(英語のみ)のインスタンスには、次の必須メソッドを実装する必要があります。
onUserDataResponse(UserDataResponse userDataResponse)(英語のみ):getUserData()(英語のみ)の呼び出し後に呼び出されます。現在ログインしているユーザーのユーザーID、マーケットプレイス、居住国の国コードを特定します。また、クイック定期購入に対するユーザーの同意ステータスも提供します。onPurchaseUpdatesResponse(PurchaseUpdatesResponse purchaseUpdatesResponse)(英語のみ):getPurchaseUpdates()(英語のみ)の呼び出し後に呼び出されます。購入履歴を取得します。このメソッドから返されたPurchaseUpdatesResponseデータ(英語のみ)を保持しておき、その後の呼び出しでは更新分だけをシステムに問い合わせることをお勧めします。onProductDataResponse(ProductDataResponse productDataResponse)(英語のみ):getProductData()(英語のみ)の呼び出し後に呼び出されます。アプリで販売するSKUに関する情報を取得します。ProductDataResponseオブジェクト(英語のみ)から取得された有効なSKUを使用します。onPurchaseResponse(PurchaseResponse purchaseResponse)(英語のみ):purchase()(英語のみ)の呼び出し後に呼び出されます。購入のステータスを判断するために使用します。
レスポンスオブジェクト
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. プレースホルダーメソッドの作成
コードの骨組みを作るために、以下の場所で以下のメソッドを呼び出すプレースホルダー(スタブコード)を作成します。
onCreate()メソッド内でregisterListener()(英語のみ)を呼び出します。onResume()メソッド内でgetUserData()(英語のみ)を呼び出します。onResume()メソッド内でgetPurchaseUpdates()(英語のみ)を呼び出します。onResume()メソッド内でgetProductData()(英語のみ)を呼び出します。
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()(英語のみ)を呼び出して、最新の更新が確実に取得されるようにします。
getPurchaseUpdates()(英語のみ)を呼び出す必要があります。この必須の手順を実行しないと、アプリからユーザーへの購入アイテムの付与に失敗する可能性があります。この呼び出しは、ユーザーがサインインしているか、サインアウトしているか、ユーザーの定期購入ステータスがどうなっているかなどのビジネス上の基準で制限しないでください。
このメソッドは、resetというboolean型のパラメーターを受け取ります。取得する情報の量に応じて、この値をtrueまたはfalseに設定します。
false- レスポンスとして、前回getPurchaseUpdates()が呼び出されてからの購入履歴が返されます。購入のタイプによる動作は次のとおりです。- 非消費型アイテム - アプリが購入保留機能を使用している場合、アプリが
notifyFulfillment()を呼び出してFULFILLEDというアイテム付与の結果を送信するまで、Amazonアプリストアはアイテムが付与されていない非消費型アイテムのレシートをすべて返します。それ以外の場合、Amazonアプリストアは前回メソッドが呼び出されたとき以降の新しいトランザクションをすべて返します。 - 消費型アイテム - アプリが
FULFILLEDというアイテム付与の結果を送信するまで、Amazonアプリストアはアイテムが付与されていない消費型アイテムのレシートをすべて返します。 - 定期購入型アイテム - Amazonアプリストアは、アプリがアイテム付与の結果を送信していない新しいレシートのみを返します。これらのレシートは、アプリが
FULFILLEDまたはUNAVAILABLEのいずれかのアイテム付与の結果を送信してレシートを確認するまで、getPurchaseUpdates(false)の呼び出しのたびに表示されます。
- 非消費型アイテム - アプリが購入保留機能を使用している場合、アプリが
true- ユーザーの購入履歴全体を取得します。データをサーバー側のデータキャッシュなどに保存するか、メモリ内にすべてを保持する必要があります。ユーザーが購入アイテムを復元しようとしている場合や、アプリとAmazonアプリストアの間で整合性の問題が見つかった場合など、ユーザーの購入に関する完全なリストが必要な場合は、trueを使用します。
getPurchaseUpdates()メソッド(英語のみ)の動作は、クライアント(デバイス)キャッシュに存在するデータによって異なります。ユーザーが新しいデバイスからサインインした場合や、現在のデバイスのキャッシュを消去した場合は、resetの値にかかわらず、初回のgetPurchaseUpdates()(英語のみ)の呼び出しではすべてのレシートが返されます。後続の呼び出しでresetをfalseに設定すると、前回の呼び出し以降の最新のレシートのみが返されます。ユーザーが別のデバイスにサインインした場合や、ユーザーがデバイスをリセットした場合など、一部のシナリオではクライアントデバイス上のデータが消去されることがあります。このようなシナリオが発生すると、同じレシートが複数回返される可能性があります。アプリでは、新しいレシートと古いレシートを識別し、それぞれを適切に処理できる必要があります。
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()(英語のみ)から返されるリクエストステータスを確認します。RequestStatus(英語のみ)がSUCCESSFUL(英語のみ)の場合は、各レシートを処理します。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で検証します" );
}
productSkusに親SKUとすべての子SKUを追加して、getProductData()で検証します。子SKUを含める必要があるのは、価格情報が子SKUに関連付けられているためです。価格は定期購入期間によって異なるので、親SKUには価格がありません。詳細については、定期購入型アイテムに関する質問を参照してください。PurchasingService.getProductData()メソッド(英語のみ)を呼び出すと、PurchasingListener.onProductDataResponse()コールバック(英語のみ)がトリガーされます。ProductDataResponse.getRequestStatus()(英語のみ)から返されたリクエストステータスを確認し、この呼び出しによって検証されたアイテムまたはSKUのみを販売します。
成功したリクエスト
RequestStatus(英語のみ)がSUCCESSFUL(英語のみ)の場合は、アプリに表示するSKUをキーとして商品データマップを取得します。RequestStatus(英語のみ)がSUCCESSFUL(英語のみ)であっても、アイテムデータを入手できないSKUが存在する場合は、ProductDataResponse.getUnavailableSkus()(英語のみ)を呼び出して無効なSKUの商品データを取得し、アプリのユーザーがそれらの商品を購入できないようにしてください。
アプリ内でIAPアイコンを表示する場合は、AndroidManifest.xmlファイルを編集して、android.permission.INTERNETパーミッションを含める必要があります。
商品データマップには次の値が含まれます。
| フィールド | データ型 | 説明 |
|---|---|---|
sku |
文字列 | 商品のSKU(Stock-keeping unit)。 |
title |
文字列 | ローカライズされた商品タイトル。 |
description |
文字列 | ローカライズされた商品説明。 |
smallIconUrl |
文字列 | 商品の小アイコンのURL。 |
productType |
文字列 | 商品の種類。有効な値は CONSUMABLE、ENTITLED、SUBSCRIPTIONです。 |
coinsRewardAmount |
int | サポートは終了しました。Amazonコインは2025年8月20日にサービスを終了しました。 |
freeTrialPeriod |
文字列 | 定期購入期間中の無料体験期間。無料体験が設定されていて、ユーザーに利用資格がある場合にのみ返されます。 |
subscriptionPeriod |
文字列 | SKUの定期購入の期間。期間のSKUに対してのみ返されます。有効な値は Weekly、BiWeekly、Monthly、BiMonthly、Quarterly、SemiAnnually、Annuallyです。 |
promotions |
List<Promotion> | ユーザーが利用できるプロモーションの詳細です。期間のSKUに対してのみ返されます。Promotionオブジェクトの詳細については、次の表を参照してください。プロモーション価格を設定する方法については、プロモーション価格の設定を参照してください。 |
price |
文字列 | ローカライズされた商品価格(定期購入型アイテムの子SKUの場合は子SKUに関連付けられている) |
Promotionオブジェクトには次のフィールドが含まれています。
| フィールド | データ型 | 説明 |
|---|---|---|
promotionType |
文字列 | プロモーションのタイプ。有効な値は Introductoryです。 |
promotionPlans |
List<PromotionalPlan> | プロモーションの価格と請求サイクルの詳細。PromotionalPlanオブジェクトの詳細については、次の表を参照してください。 |
PromotionalPlanオブジェクトには次のフィールドが含まれています。
| フィールド | データ型 | 説明 |
|---|---|---|
promotionPrice |
文字列 | プロモーション期間中の期間SKUの価格(ローカライズされた形式)。 |
promotionPricePeriod |
文字列 | プロモーションの各請求サイクルの期間。有効な値は Weekly、BiWeekly、Monthly、BiMonthly、Quarterly、SemiAnnually、Annuallyです。 |
promotionPriceCycles |
整数 | 請求サイクルの数。 |
定期購入のさまざまなユースケースについて、商品データマップの例を確認するには、次のボタンをクリックしてください。これらの例はJSON形式です。
失敗したリクエスト
RequestStatus(英語のみ)がFAILED(英語のみ)の場合は、次のサンプルコードに示すように、アプリの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本番環境エンドポイントを使用します。
cancelDateを検証することを強く推奨します。キャンセル日を確認しないと、ユーザーが購入をキャンセルした後も、サービスが引き続き利用される恐れがあります。cancelDateフィールドの確認方法について詳しくは、IAPのベストプラクティスを参照してください。以下の例では、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(英語のみ)を送信します。
- ユーザーに既存のアカウントがあり、サービスの定期購入型アイテムを既に所有している。
- ユーザーにサービスのアカウントにサインアップする資格がない。
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: 2026年4月24日

