The Android Developer Challenge is back! Submit your idea before December 2.

Google Play Billing Library の使用

このドキュメントでは、Google Play Billing Library を使用して、Google Play 請求サービスをアプリ内に組み込む方法について説明します。具体的には、すべてのアプリ内アイテムのタイプ(1 回限りの購入アイテム、特典アイテム、定期購入)に共通の Google Play 請求サービス機能の追加方法を示します。アプリにアプリ内アイテム固有の機能を追加する方法については、このページの最後に記載されているドキュメントをご覧ください。

このページを読む前に、次のことを行ってください。

  1. Google Play 請求サービスの概要を読み、重要な概念と用語について理解します。
  2. Google Play Console を使用してアプリ内アイテムを設定します。

コード スニペットについて

このガイドでは、TrivialDrive v2 サンプルアプリのコード スニペットを使用しています。このサンプルでは、Play Billing Library を使用して、運転ゲーム用のアプリ内アイテムを実装する方法を示します。利用可能なアイテムの一覧表示、購入フローの開始、アイテム消費の記録のほか、Google Play 請求サービスをアプリに追加するために必要なすべての手順が含まれています。図 1 は、このアプリの開始画面です。

図 1. Trivial Drive アプリの開始画面。

Google Play 請求サービスをアプリに追加する手順

以下の手順で Google Play 請求サービスをアプリに追加します。

アプリの依存関係を更新する

アプリの build.gradle ファイルの dependencies に次の行を追加します。

dependencies {
    ...
    implementation 'com.android.billingclient:billing:2.0.3'
}

最新版の Google Play Billing Library を使用していることを確認するには、Google Play Billing Library のリリースノートをご覧ください。

Google Play に接続する

Google Play 請求サービスのリクエストを行うには、まず次の手順で Google Play への接続を確立する必要があります。

  1. newBuilder() を呼び出して BillingClient のインスタンスを作成します。また、アプリで開始された購入や Google Play ストアで開始された購入に関する最新情報を受け取るため、setListener() も呼び出して PurchasesUpdatedListener への参照を渡す必要があります。

  2. Google Play への接続を確立します。セットアップ プロセスは非同期のため、クライアントのセットアップが完了してリクエストを行う準備ができたらコールバックを受け取るよう BillingClientStateListener を実装する必要があります。

  3. onBillingServiceDisconnected() コールバック メソッドをオーバーライドし、クライアントが Google Play への接続を失った時点で対処する際の独自の再試行ポリシーを実装します。たとえば、Google Play ストア サービスがバックグラウンドで更新されている場合、BillingClient は接続を失う可能性があります。さらにリクエストを行うには、BillingClient から startConnection() メソッドを呼び出して接続を再開する必要があります。

次のコードサンプルは、接続を開始して使用可能かどうかをテストする方法を示しています。

Kotlin

lateinit private var billingClient: BillingClient
...
billingClient = BillingClient.newBuilder(context).setListener(this).build()
billingClient.startConnection(object : BillingClientStateListener {
   override fun onBillingSetupFinished(billingResult: BillingResult) {
       if (billingResult.responseCode == BillingResponse.OK) {
           // The BillingClient is ready. You can query purchases here.
       }
   }
   override fun onBillingServiceDisconnected() {
       // Try to restart the connection on the next request to
       // Google Play by calling the startConnection() method.
   }
})

Java

private BillingClient billingClient;
...
billingClient = BillingClient.newBuilder(activity).setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() == BillingResponse.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

アプリ内アイテムの詳細に関するクエリ

アプリ内アイテムの設定時に作成した一意のアイテム ID は、Google Play でアプリ内アイテムの詳細を非同期的に取得するために使用されます。Google Play にアプリ内アイテムの詳細について照会するには、querySkuDetailsAsync() を呼び出します。このメソッド呼び出しでは、アイテム ID 文字列のリストと SkuType を指定する SkuDetailsParams のインスタンスを渡します。SkuType には、1 回限りのアイテムまたは特典アイテムでは SkuType.INAPP、定期購入では SkuType.SUBS を指定します。

非同期操作の結果を処理するには、SkuDetailsResponseListener インターフェースを実装するリスナーも指定する必要があります。次のサンプルコードに例を示すように、クエリが終了した際にリスナーに通知する onSkuDetailsResponse() をオーバーライドすることができます。

Kotlin

val skuList = ArrayList<String>()
skuList.add("premium_upgrade")
skuList.add("gas")
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build(), { billingResult, skuDetailsList ->
    // Process the result.
})

Java

List<String> skuList = new ArrayList<> ();
skuList.add("premium_upgrade");
skuList.add("gas");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult,
                List<SkuDetails> skuDetailsList) {
            // Process the result.
        }
    });

リストを APK にバンドルするか、安全なバックエンド サーバーに照会することで、アプリが自身のアイテム ID のリストを保持するようにします。

レスポンス コードを取得するには、getResponseCode() を呼び出します。リクエストが成功した場合のレスポンス コードは BillingResponse.OK です。Google Play からのその他のレスポンス コードの一覧については、BillingClient.BillingResponse をご覧ください。

エラーが発生した場合は、getDebugMessage() を使用して関連エラー メッセージを表示できます。

Google Play Billing Library は、SkuDetails オブジェクトの List にクエリの結果を保存します。デベロッパーは、リスト内の各 SkuDetails オブジェクトに対してさまざまなメソッドを呼び出して、価格や説明などのアプリ内アイテムに関する関連情報を表示できます。利用可能なアイテムの詳細情報を表示するには、SkuDetails クラスのメソッドのリストを参照してください。

次の例では、前のコード スニペットによって返された SkuDetails オブジェクトを使用して、アプリ内アイテムの価格を取得する方法を示しています。

Kotlin

if (result.responseCode == BillingResponse.OK && skuDetailsList != null) {
    for (skuDetails in skuDetailsList) {
        val sku = skuDetails.sku
        val price = skuDetails.price
        if ("premium_upgrade" == sku) {
            premiumUpgradePrice = price
        } else if ("gas" == sku) {
            gasPrice = price
        }
    }
}

Java

if (result.getResponseCode() == BillingResponse.OK && skuDetailsList != null) {
   for (SkuDetails skuDetails : skuDetailsList) {
       String sku = skuDetails.getSku();
       String price = skuDetails.getPrice();
       if ("premium_upgrade".equals(sku)) {
           premiumUpgradePrice = price;
       } else if ("gas".equals(sku)) {
           gasPrice = price;
       }
   }
}

価格は各ユーザーの国によって異なるため、アイテムの価格を取得することは、ユーザーによるアイテム購入のための重要なステップとなります。図 2 に示すように、Trivial Drive アプリではすべてのアプリ内アイテムがリストとして表示されます。

図 2. Trivial Drive のアプリ内アイテム画面。

一貫したオファー

割引 SKU を提供している場合、Google Play から SKU の元の価格も返されるので、割引が適用されていることをユーザーに知らせることができます。割引価格をユーザーに表示する getPrice() と、アイテムの元の価格を表示する getOriginalPrice() の両方を使用することをおすすめします。

SkuDetails には、元の SKU 価格を取得するための 2 つのメソッドがあります。

アプリ内アイテムの購入を有効にする

一部の Android デバイスでは、定期購入などの特定のアイテムタイプをサポートしていない従来のバージョンの Google Play ストア アプリを使用している場合があります。したがって、アプリが請求フローに入る前に isFeatureSupported() を呼び出して、販売するアイテムをデバイスがサポートしていることを確認します。アイテムタイプの一覧については、BillingClient.FeatureType をご覧ください。

アプリから購入リクエストを開始するには、UI スレッドから launchBillingFlow() メソッドを呼び出します。このとき、購入を完了するための関連データを含む BillingFlowParams オブジェクトへの参照を渡します。関連データには、アイテム ID(skuId)、アイテムタイプ(1 回限りのアイテムや特典アイテムの場合は SkuType.INAPP、定期購入の場合は SkuType.SUBS)などがあります。BillingFlowParams のインスタンスを取得するには、BillingFlowParams.Builder クラスを使用します。

Kotlin

// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
val flowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .build()
val responseCode = billingClient.launchBillingFlow(activity, flowParams)

Java

// Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
        .setSkuDetails(skuDetails)
        .build();
int responseCode = billingClient.launchBillingFlow(flowParams);

launchBillingFlow() メソッドを呼び出すと、システムで Google Play の購入画面が表示されます。図 3 は、1 回限りのアイテムの購入画面です。

図 3. Google Play の 1 回限りのアイテムの購入画面。

図 4 は、定期購入の購入画面です。

図 4. Google Play の定期購入の画面。

launchBillingFlow() メソッドは、BillingClient.BillingResponse にリストされているレスポンス コードのいずれかを返します。Google Play は onPurchasesUpdated() メソッドを呼び出して、PurchasesUpdatedListener インターフェースを実装するリスナーに購入操作の結果を通知します。リスナーは、Google Play に接続するセクションで説明したように、setListener() メソッドを使用して指定されます。

レスポンス コードを処理するには、onPurchasesUpdated() メソッドを実装する必要があります。次のコード スニペットは、onPurchasesUpdated() メソッドをオーバーライドする方法を示しています。

Kotlin

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponse.OK && purchases != null) {
       for (purchase in purchases) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponse.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}

Java

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponse.OK
            && purchases != null) {
        for (Purchase purchase : purchases) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponse.USER_CANCELED) {
        // Handle an error caused by a user cancelling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

購入が成功すると、図 5 のような Google Play の成功画面が表示されます。

図 5. Google Play の成功画面

購入が成功すると、購入トークンも生成されます。これは、ユーザーおよび購入したアプリ内アイテムのアイテム ID を表す一意の識別子です。アプリは購入トークンをローカルに保存できますが、理想的には、安全なバックエンド サーバーに購入トークンを渡して、購入の検証と不正行為からの保護に使用します。購入トークンは、1 回限りのアイテム購入と特典アイテムごとに一意となります。ただし、定期購入は一度購入すると一定の請求期間単位で自動的に更新されるため、定期購入の購入トークンは請求期間ごとに維持されます。

またユーザーには、オーダー ID またはトランザクションの一意の ID を含む領収書がメールで送られます。メールには、1 回限りのアイテム購入ごとに一意のオーダー ID、定期購入の初回購入時およびその後の自動更新時の一意のオーダー ID が記載されています。Google Play Console で払い戻しを管理するには、オーダー ID を使用できます。詳しくは、アプリの注文の管理、払い戻しの手続きをご覧ください。

購入を承認する

Google Play Billing Library バージョン 2.0 以降を使用している場合、購入はすべて 3 日以内に承認する必要があります。購入が適切に承認されなかった場合は払い戻しが行われます。

Google Play では、アプリの内側(アプリ内)またはアプリの外側(アプリ外)からアイテムを購入することができます。ユーザーがアイテムを購入する場所に関係なく、Google Play で整合性のとれた購入エクスペリエンスを提供するには、ユーザーに権利を付与した後できるだけ早く、Google Play Billing Library を介して受信した、SUCCESS ステータスのすべての購入を承認する必要があります。3 日以内に購入を承認しない場合、ユーザーは自動的に払い戻しを受け、Google Play は購入を取り消します。保留中の取引では、購入ステータスが PENDING の場合、この 3 日の期間は適用されません。購入ステータスが SUCCESS に移行した時点からの適用になります。

次のいずれかの方法で購入を承認できます。

  • 消費アイテムの場合は、クライアント API にある consumeAsync() を使用します。
  • 非消費アイテムの場合は、クライアント API にある acknowledgePurchase() を使用します。
  • サーバー API では、新しい acknowledge() メソッドも利用できます。

定期購入の場合、新しい購入トークンを含む購入はすべて承認する必要があります。つまり初回購入、プランの変更、再登録はすべて承認する必要がありますが、その後の更新を承認する必要はありません。購入に承認が必要かどうかを判断するには、購入の承認フィールドを確認します。

Purchase オブジェクトには、購入が承認されたかどうかを示す isAcknowledged() メソッドが含まれています。さらに、サーバーサイド API には Product.purchases.get()Product.subscriptions.get() の確認ブール値が含まれています。購入を承認する前に、これらの方法で購入がすでに承認されているかどうかを確認してください。

次の例は、定期購入を承認する方法を示しています。

Kotlin

val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...

fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        // Grant entitlement to the user.
        ...

        // Acknowledge the purchase if it hasn't already been acknowledged.
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
                    .build()
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener)
        }
     }
}

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        // Grant entitlement to the user.
        ...

        // Acknowledge the purchase if it hasn't already been acknowledged.
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

ライセンス テスターに購入の承認をテストしてもらう

ライセンス テスターによる購入では、承認期間が短くなります。3 日ではなく 5 分以内に購入が承認されないと、購入の払い戻しと取り消しが行われます。

保留中のトランザクションをサポートする

Google Play 請求サービス ソリューションを実装する場合は、利用権を付与する前に追加の操作が必要な購入をサポートする必要があります。たとえば、ユーザーが実店舗でアプリ内アイテムを現金で購入するようなケースがあります。その場合は、トランザクションがアプリの外部で完了することになります。このシナリオでは、ユーザーがトランザクションを完了した後にのみ利用権を付与する必要があります。

保留中の購入を有効にするには、アプリの初期化時に enablePendingPurchases() を呼び出します。enablePendingPurchases() を呼び出さないと、Google Play Billing Library をインスタンス化できません。

Purchase.getPurchaseState() メソッドを使用して、購入ステータスが PURCHASEDPENDING かを確認します。ステータスが PURCHASED の場合にのみ利用権を付与するようにしてください。次の方法でステータスの変更を確認できます。

  1. アプリの起動時に BillingClient.queryPurchases() を呼び出して、ユーザーに関連付けられている未消費アイテムのリストを取得します。次に、返された Purchase オブジェクトごとに getPurchaseState() を呼び出します。
  2. onPurchasesUpdated() メソッドを実装し、Purchase オブジェクトの変更に対応します。

以下は、保留中の取引を処理する方法を示す例です。

Kotlin

fun handlePurchase(purchase: Purchase) {
    if (purchase.purchaseState == PurchaseState.PURCHASED) {
        // Grant the item to the user, and then acknowledge the purchase
    } else if (purchase.purchaseState == PurchaseState.PENDING) {
        // Here you can confirm to the user that they've started the pending
        // purchase, and to complete it, they should follow instructions that
        // are given to them. You can also choose to remind the user in the
        // future to complete the purchase if you detect that it is still
        // pending.
    }
}

Java

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        // Acknowledge purchase and grant the item to the user
    } else if (purchase.getPurchaseState() == PurchaseState.PENDING) {
        // Here you can confirm to the user that they've started the pending
        // purchase, and to complete it, they should follow instructions that
        // are given to them. You can also choose to remind the user in the
        // future to complete the purchase if you detect that it is still
        // pending.
    }
}

ライセンス テスターに保留中のトランザクションをテストしてもらう

保留中のトランザクションは、ライセンス テスターによるテストが可能です。ライセンス テスターは、2 つのテスト用クレジット カードに加えて、支払いが遅延する 2 つのテスト オプションを使用できます。これらは、数分後に自動的に完了またはキャンセルされます。

アプリのテストでは、これら 2 つのオプションのいずれかを使用した購入の直後に、アプリが利用権を付与したり購入を承認したりしないことを確認してください。自動的に完了するテスト オプションでの購入の場合、購入が完了した時点で、アプリが利用権を付与し購入を承認することを確認する必要があります。

自動的にキャンセルするテスト オプションでの購入の場合、購入は成功しないため、アプリが利用権を付与しないことを確認する必要があります。

デベロッパー ペイロードを添付する

購入に任意の文字列であるデベロッパー ペイロードを添付することができます。ただし、デベロッパー ペイロードを添付できるのは、購入が承認または消費された場合のみです。これは、購入フローを起動するときにペイロードを指定できる AIDL のデベロッパー ペイロードとは異なります。

消費アイテムの場合、次の例に示すように、consumeAsync() にはデベロッパー ペイロード フィールドを含む ConsumeParams オブジェクトを指定します。

Kotlin

val client: BillingClient = ...
val listener: ConsumeResponseListener = ...

val consumeParams =
    ConsumeParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build()

client.consumeAsync(consumeParams, listener)

Java

BillingClient client = ...
ConsumeResponseListener listener = ...

ConsumeParams consumeParams =
    ConsumeParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build();

client.consumeAsync(consumeParams, listener);

非消費アイテムの場合、次の例に示すように、acknowledgePurchase() にはデベロッパー ペイロード フィールドを含む AcknowledgePurchaseParams オブジェクトを指定します。

Kotlin

val client: BillingClient = ...
val listener: AcknowledgePurchaseResponseListener = ...

val acknowledgePurchaseParams =
    AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build()

client.acknowledgePurchase(acknowledgePurchaseParams, listener)

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener listener = ...

AcknowledgePurchaseParams acknowledgePurchaseParams =
    AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(/* token */)
        .setDeveloperPayload(/* payload */)
        .build();

client.acknowledgePurchase(acknowledgePurchaseParams, listener);

デベロッパー ペイロードにアクセスするには、対応する Purchase オブジェクトに対して getDeveloperPayload() を呼び出します。

購入を消費または承認できるのは、購入ステータスが PURCHASED の場合のみです。

購入を確認する

ユーザーに購入アイテムへのアクセス権を提供する前に、購入ステータスが PURCHASED であることと、アプリが onPurchasesUpdated() で受け取るその他の購入の詳細を必ず確認する必要があります。

サーバーで購入を確認する

購入確認ロジックをサーバーに実装することで、APK ファイルをリバース エンジニアリングすることによって確認ロジックを無効にしようとする攻撃者からアプリを保護できます。安全なバックエンド サーバーで購入の詳細を確認するには、以下の手順を実行します。

  1. 購入トークンとユーザー アカウントの認証情報を、アプリから安全なバックエンド サーバーに送信します。安全なバックエンド サーバーは、検証が成功したら購入をユーザーに関連付ける必要があります。

  2. アプリからトークンを取得したら、次の操作を行います。

    1. Google Play Developer API の定期購入と購入に関する API を使用して GET リクエストを実行し、Google Play から購入の詳細を取得します(1 回限りのアイテムまたは特典アイテムの購入の場合は Purchases.products、定期購入の場合は Purchases.subscriptions)。GET リクエストには、アプリのパッケージ名、アイテム ID、トークン(購入トークン)を含めます。

    2. Google Play が購入詳細を返します。

    3. 安全なバックエンド サーバーは、オーダー ID が前回の購入を表していない一意の値であることを確認します。

    4. 安全なバックエンド サーバーが、手順 1 で受信したユーザー アカウントの認証情報を使用して、購入が行われたアプリ インスタンスのユーザーと購入トークンを関連付けます。

    5. (省略可)定期購入を検証する際、定期購入がアップグレードまたはダウングレードしていたり、定期購入が失効する前に再購入していたりする場合は、linkedPurchaseToken フィールドを確認します。Purchases.subscriptions リソースの linkedPurchaseToken フィールドには、前の(元の)購入のトークンが含まれています。linkedPurchaseToken について詳しくは、Purchases.subscriptions をご覧ください。

    6. ユーザーがアプリ内アイテムを利用できるようになります。

デバイスで購入を確認する

サーバーを使用できない場合でも、Android アプリ内で購入詳細を確認できます。

アプリに送信されるトランザクション情報の整合性を確保するため、Google Play では購入に対するレスポンス データを含む JSON 文字列に署名します。Google Play は Play Console 内でデベロッパーのアプリに関連付けられた秘密鍵を使って、この署名を作成します。Play Console はそれぞれのアプリに対して RSA キーペアを生成します。このレスポンス JSON を取得するには、Purchase クラスの getOriginalJson() メソッドを使用します。

Google Play が生成する Base64 でエンコードされた RSA 公開鍵は、X.509 subjectPublicKeyInfo DER SEQUENCE 形式でエンコード済みのバイナリ内にあります。これは Google Play のライセンス付与に使う公開鍵と同じものです。

アプリは署名済みのレスポンスを受け取ると、RSA キーペアのうちの公開鍵で署名を確認します。署名確認をすることで、不正変更またはなりすましのレスポンスを見破ることができます。

攻撃者によるセキュリティ プロトコルやその他のアプリ コンポーネントのリバース エンジニアリングを困難にするため、Google Play の公開鍵と Google Play 請求サービスのコードを難読化する必要があります。少なくとも、R8 または ProGuard を使用してアプリのコードを難読化することをおすすめします。難読化する場合は、ProGuard の構成ファイルに次の行を追加する必要があります。

-keep class com.android.vending.billing.**

Google Play の公開鍵と Google Play 請求サービスのコードを難読化したら、アプリで購入の詳細を検証する準備が整ったことになります。アプリが署名を検証したら、アプリのキーがその署名に含まれる JSON データに署名したことを確認します。

購入を最新の状態に保つ

ユーザーが行った購入を把握できなくなることが考えられます。アプリが購入の記録を失う場合と、購入の問い合わせが重要となる場合の 2 つのシナリオがあります。

サーバー障害時の処理

  1. ドライビング ゲームでのガソリンの追加などといった 1 回限りのアイテムをユーザーが購入します。
  2. アプリが確認のために購入トークンを安全なバックエンド サーバーに送信します。
  3. サーバーが一時的に障害を起こします。
  4. アプリはサーバーがダウンしていることを認識すると、購入に問題があることをユーザーに通知します。
  5. Android アプリは購入トークンの安全なバックエンド サーバーへの送信を再試行し、サーバーが復元されるとすぐに購入を終了します。
  6. アプリがコンテンツを公開します。

複数のデバイスの処理

  1. ユーザーが自分の Android デバイスで定期購入を購入します。
  2. アプリが確認のために購入トークンを安全なバックエンド サーバーに送信します。
  3. サーバーが購入トークンを検証します。
  4. アプリがコンテンツを公開します。
  5. ユーザーが定期購入を使用するために Android タブレットに切り替えます。
  6. 新しいデバイス上のアプリが更新された購入リストを問い合わせます。
  7. アプリが定期購入を認識し、タブレットからのアクセスを許可します。

キャッシュに保存された購入のクエリ

ユーザーがアプリから行った購入に関する情報を取得するには、次の例に示すように、queryPurchases() を(購入タイプとして SkuType.INAPP または SkuType.SUBS を指定して)BillingClient に対して呼び出します。

Kotlin

val purchasesResult: PurchasesResult =
        billingClient.queryPurchases(SkuType.INAPP)

Java

PurchasesResult purchasesResult = billingClient.queryPurchases(SkuType.INAPP);

Google Play は、デバイスにログインしているユーザー アカウントによる購入を返します。リクエストが成功すると、Play Billing Library は Purchase オブジェクトの List にクエリ結果を保存します。

リストを取得するには、PurchasesResult に対して getPurchasesList() を呼び出します。その後、Purchase オブジェクトに対してさまざまなメソッドを呼び出して、購入ステータスや時間など、アイテムに関する関連情報を表示できます。利用可能な詳細情報の種類を表示するには、Purchase クラスのメソッドのリストをご覧ください。

コードでは queryPurchases() を少なくとも 2 回呼び出します。

  • アプリが起動されるたびに queryPurchases() を呼び出して、アプリが最後に停止してからユーザーが行った購入を復元できます。
  • onResume() メソッドで queryPurchases() を呼び出します。これは、アプリがバックグラウンドで実行中でもユーザーが購入を行えるためです(Google Play ストア アプリのプロモーション コードを利用するなど)。

起動時と再開時に queryPurchases() を呼び出すことで、アプリが実行されていない間にユーザーが行ったすべての購入とクーポンの利用を確実に調べることができます。さらに、アプリの実行中にユーザーが行った購入がアプリで見逃されるようなことがあっても、次回のアクティビティの再開時に queryPurchases() を呼び出して確認できます。

最近の購入のクエリ

queryPurchases() メソッドは、ネットワーク リクエストを開始せずに Google Play ストア アプリのキャッシュを使用します。ユーザーの最新の購入をアイテム ID ごとに確認する必要がある場合は、queryPurchaseHistoryAsync() を使用して、購入タイプと PurchaseHistoryResponseListener を渡してクエリ結果を処理します。

queryPurchaseHistoryAsync() は、ユーザーの最新の購入に関するアイテム ID ごとの情報を含む PurchaseHistory オブジェクトを返します。期限切れの購入、キャンセルされた購入、消費済みの購入も対象です。可能な場合には、queryPurchaseHistoryAsync() ではなく、ローカル キャッシュを使用する queryPurchases() を使うようにしてください。queryPurchaseHistoryAsync() を使用する場合は、[更新] ボタンと組み合わせてユーザーが購入リストを更新できるようにすることも可能です。

onPurchaseHistoryResponse() メソッドをオーバーライドする方法を次のコードの例で示します。

Kotlin

billingClient.queryPurchaseHistoryAsync(SkuType.INAPP, { billingResult, purchasesList ->
   if (billingResult.responseCode == BillingResponse.OK && purchasesList != null) {
       for (purchase in purchasesList) {
           // Process the result.
       }
   }
})

Java

billingClient.queryPurchaseHistoryAsync(SkuType.INAPP,
                                         new PurchaseHistoryResponseListener() {
    @Override
    public void onPurchaseHistoryResponse(BillingResult billingResult,
                                          List<Purchase> purchasesList) {
        if (billingResult.getResponseCode() == BillingResponse.OK
                && purchasesList != null) {
            for (Purchase purchase : purchasesList) {
                // Process the result.
            }
         }
    }
});

次の手順

ユーザーにアイテムの購入を許可したら、アイテム固有のシナリオをカバーする方法を学びます。