Google Play Billing Library をアプリに統合する

このトピックでは、Google Play Billing Library をアプリに統合してアイテムの販売を開始する方法について説明します。

このトピックには、GitHub 上の公式サンプルアプリに基づくコード例が含まれています。統合の際に使用できるサンプルアプリとその他のリソースの一覧については、追加リソースをご覧ください。

購入ライフサイクル

1 回限りの購入または定期購入の一般的な購入フローは次のとおりです。

  1. 購入できるアイテムをユーザーに表示します。
  2. ユーザーが実際に購入を選択するまでの購入フローを起動します。
  3. サーバーで購入を確認します。
  4. ユーザーにコンテンツを提供します。
  5. コンテンツの配信を承認します。消費可能アイテムの場合は、その購入アイテムを消費し、ユーザーがアイテムを再購入できるようにします。

定期購入は、ユーザーがキャンセルするまで自動的に更新されます。定期購入には次のステータスがあります。

  • アクティブ: ユーザーは良好な状態にあり、問題なく定期購入を利用できます。
  • キャンセル済み: ユーザーは定期購入をキャンセルしましたが、期限切れになるまで利用できます。
  • 猶予期間内: 支払いに関する問題が発生しましたが、Google がお支払い方法を再試行している間、ユーザーは定期購入を利用できます。
  • 保留中: 支払いに関する問題が発生し、Google がお支払い方法を再試行している間、ユーザーは定期購入を利用できなくなりました。
  • 一時停止中: ユーザーは定期購入の利用を一時停止し、再開するまで利用できなくなりました。
  • 期限切れ: ユーザーは定期購入をキャンセルし、利用できなくなりました。期限切れになると、ユーザーは解約したものと見なされます。

購入トークンとオーダー ID

Google Play は、購入トークンとオーダー ID を使用して、アイテムと取引をトラッキングします。

  • 購入トークンとは、Google Play 上のアイテムに対する購入者の利用権を表す文字列です。Google ユーザーが購入オブジェクトで表される特定のアイテムの利用権を持つことを示します。購入トークンは Google Play Developer API で使用できます。
  • オーダー ID とは、Google Play 上の支払い取引を表す文字列です。この文字列は、購入者にメールで送信される領収書に記載されます。

オーダー ID は、支払い取引が発生するたびに作成されます。購入トークンは、ユーザーが購入フローを完了したときにのみ生成されます。

  • 1 回限りのアイテムの場合、購入が発生するたびに新しい購入トークンが作成されます。ほとんどの購入では、新しいオーダー ID も生成されます。ただし、プロモーション コードで説明しているように、ユーザーが代金を請求されない場合は例外です。
  • 定期購入の場合、初回購入時に購入トークンとオーダー ID が作成されます。自動更新のたびに、購入トークンは同じままで新しいオーダー ID が発行されます。アップグレード、ダウングレード、交換、再登録が発生したときは、新しい購入トークンとオーダー ID が作成されます。

定期購入では、次の点に注意してください。

  • 定期購入のアップグレードとダウングレード、およびその他の定期購入フローで生成される購入トークンは、前の購入トークンに置き換わる必要があります。Google Play Developer API の linkedPurchaseToken フィールドに表示される購入トークンを無効にする必要があります。詳細については、Implementing linkedPurchaseToken correctly to prevent duplicate subscriptions をご覧ください。
  • 定期購入の更新のオーダー番号には、何回目の更新かを表す整数が追加されます。たとえば、初回の定期購入のオーダー ID が GPA.1234-5678-9012-34567 である場合、その後のオーダー ID は GPA.1234-5678-9012-34567..0(1 回目の更新)、GPA.1234-5678-9012-34567..1(2 回目の更新)などになります。

エラー処理

Google Play Billing Library は、BillingResult の形式でエラーを返します。BillingResult に含まれている BillingResponseCode は、アプリで発生する可能性がある請求関連のエラーを分類したものです。たとえば、SERVICE_DISCONNECTED エラーコードが返された場合、アプリは Google Play との接続を再初期化する必要があります。また、BillingResult に含まれているデバッグ メッセージは、開発中のエラーの診断に役立ちます。

Google Play への接続を初期化する

Google Play の課金システムを統合するための最初のステップは、アプリに Google Play Billing Library を追加して接続を初期化することです。

Google Play Billing Library への依存関係を追加する

次に示すように、Google Play Billing Library への依存関係をアプリの build.gradle ファイルに追加します。

Groovy

dependencies {
    def billing_version = "5.0.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "5.0.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

Kotlin を使用している場合、Google Play Billing Library の KTX モジュールに Kotlin 拡張機能とコルーチンのサポートが含まれているため、Google Play Billing Library を使用する際に慣用的な Kotlin コードを作成できます。これらの拡張機能をプロジェクトに含めるには、アプリの build.gradle ファイルに次の依存関係を追加します。

Groovy

dependencies {
    def billing_version = "5.0.0"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "5.0.0"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

BillingClient を初期化する

Google Play Billing Library への依存関係を追加したら、BillingClient インスタンスを初期化する必要があります。BillingClient は、Google Play Billing Library とアプリの他の部分の通信に使用されるメイン インターフェースです。BillingClient は、多くの一般的な請求処理に役立つコンビニエンス メソッド(同期メソッドと非同期メソッドの両方)を提供します。1 つのイベントに対する複数の PurchasesUpdatedListener コールバックを回避するため、一度に開くアクティブな BillingClient 接続は 1 つにすることを強くおすすめします。

BillingClient を作成するには、newBuilder() を使用します。任意のコンテキストを newBuilder() に渡すと、BillingClient がそれを使用してアプリケーションのコンテキストを取得します。つまり、メモリリークを心配する必要はありません。また、購入に関する最新情報を受け取るには、setListener() を呼び出して PurchasesUpdatedListener への参照を渡す必要があります。このリスナーは、すべてのアプリ内購入に関する最新情報を受信します。

Kotlin

private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented in a later section.
   }

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // To be implemented in a later section.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .build();

Google Play に接続する

BillingClient を作成したら、Google Play との接続を確立する必要があります。

Google Play に接続するには、startConnection() を呼び出します。接続プロセスは非同期です。クライアントのセットアップが完了してリクエストを送信する準備ができたら、コールバックを受け取るために BillingClientStateListener を実装する必要があります。

また、Google Play への接続の切断を処理するための再試行ロジックも実装する必要があります。再試行ロジックを実装するには、onBillingServiceDisconnected() コールバック メソッドをオーバーライドし、後続のリクエストを送信する前に BillingClientstartConnection() メソッドを呼び出して Google Play に再接続するようにします。

次の例は、接続を開始して使用可能かどうかをテストする方法を示しています。

Kotlin

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.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

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() ==  BillingResponseCode.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.
    }
});

購入可能なアイテムを表示する

Google Play への接続を確立したら、購入可能なアイテムをクエリしてユーザーに表示できます。

商品の詳細のクエリは、ローカライズされた商品情報を返すので、ユーザーにアイテムを表示する前の重要なステップとなります。定期購入の場合は、アイテムの表示がすべての Play ポリシーに従っていることを確認します。

アプリ内アイテムの詳細をクエリするには、queryProductDetailsAsync() を呼び出します。

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

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    ProductDetailsResponseListener { billingResult, productDetailsList ->
        // check billingResult
        // process returned productDetailsList
    }
)

Java

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(BillingResult billingResult,
                List<ProductDetails> productDetailsList) () {
            // check billingResult
            // process returned productDetailsList
        }
    }
)

商品の詳細をクエリするときは、ProductType と一緒に、Google Play Console で作成された商品 ID 文字列のリストを指定する QueryProductDetailsParams のインスタンスを渡します。ProductType には、1 回限りのアイテムの場合は ProductType.INAPP、定期購入の場合は ProductType.SUBS を指定できます。

Kotlin 拡張機能を使用したクエリ

Kotlin 拡張機能を使用している場合は、queryProductDetails() 拡張関数を呼び出すことでアプリ内アイテムの詳細をクエリできます。

queryProductDetails() は Kotlin コルーチンを使用するため、リスナーを別に定義する必要はありません。関数はクエリが完了するまで一時停止し、その後で結果を処理できます。

suspend fun processPurchases() {
    val productList = ArrayList<String>()
    productList.add("product_id_example")

    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)
        .setType(ProductType.SUBS)

    // leverage queryProductDetails Kotlin extension function
    val productDetailsResult = withContext(Dispatchers.IO) {
        billingClient.queryProductDetails(params.build())
    }

    // Process the result.
}

結果を処理する

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

販売するアイテムを提示する前に、ユーザーがまだそのアイテムを所有していないことを確認します。ユーザーのアイテム ライブラリに消費可能アイテムがまだある場合、ユーザーはそのアイテムを消費しない限り再購入できません。

定期購入を提示する前に、ユーザーがまだ定期購入を登録していないことを確認します。また、次の点に注意してください。

  • queryProductDetailsAsync() は、定期購入アイテムの詳細と、購読ごとに最大 50 件のオファーを返します。
  • queryProductDetailsAsync() は、ユーザーが対象となるオファーのみを返します。対象ではないオファーをユーザーが購入しようとした場合(たとえば、対象となるオファーのリストが古い場合)は、対象外であることを Play がユーザーに通知します。ユーザーは代わりに基本プランを購入することもできます。

購入フローを起動する

アプリから購入リクエストを開始するには、アプリのメインスレッドから launchBillingFlow() メソッドを呼び出します。このメソッドは、queryProductDetailsAsync() の呼び出しから取得された関連する ProductDetails オブジェクトを含む BillingFlowParams オブジェクトへの参照を受け取ります。BillingFlowParams オブジェクトを作成するには、BillingFlowParams.Builder クラスを使用します。

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;

val billingFlowParams = BillingFlowParams.newBuilder()
        // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // to get an offer token, call ProductDetails.offerDetails()
        // for a list of offers that are available to the user
        .setOfferToken(selectedOfferToken)
        .build()

// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(
            ImmutableList.of(
                ProductDetailsParams.newBuilder()
                    // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
                    .setProductDetails(productDetails)

                    // to get an offer token, call ProductDetails.getSubscriptionOfferDetails()
                    // for a list of offers that are available to the user
                    .setOfferToken(selectedOfferToken)
                    .build()
            )
        )
    .build();

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

launchBillingFlow() メソッドは、BillingClient.BillingResponseCode にリストされているレスポンス コードのいずれかを返します。この結果をチェックして、購入フローの起動時にエラーが発生していないことを確認してください。BillingResponseCodeOK であれば、起動は成功です。

launchBillingFlow() への呼び出しが成功すると、Google Play の購入画面が表示されます。図 1 は、定期購入の購入画面です。

購入可能な定期購入が表示された Google Play の購入画面
図 1. 購入可能な定期購入が表示された Google Play の購入画面

Google Play は onPurchasesUpdated() を呼び出して、PurchasesUpdatedListener インターフェースを実装するリスナーに購入オペレーションの結果を通知します。リスナーは、クライアントを初期化するときに setListener() メソッドを使用して指定します。

onPurchasesUpdated() を実装して、想定されるレスポンス コードを処理する必要があります。次の例は、onPurchasesUpdated() をオーバーライドする方法を示しています。

Kotlin

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

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

Google Play の購入成功画面
図 2. Google Play の購入成功画面

購入が成功すると、購入トークンも生成されます。これは、ユーザーとユーザーが購入したアプリ内アイテムの商品 ID を表す一意の識別子です。アプリでは購入トークンをローカルに保存することもできますが、安全なバックエンド サーバーに渡すことをおすすめします。そうすれば、購入を確認して不正行為を防止できます。このプロセスについては、次のセクションで詳しく説明します。

さらに、ユーザーには、オーダー ID(取引の一意の ID)が記載された領収書がメールで送られます。ユーザーへのメールに記載されるオーダー ID は、1 回限りのアイテムでは購入ごとに異なり、定期購入では初回購入とその後の自動更新ごとに異なります。デベロッパーは、Google Play Console でオーダー ID を使用して払い戻しを管理できます。

カスタマイズされた価格を提示する

アプリを欧州連合のユーザーに配信できる場合は、setIsOfferPersonalized() メソッドを使用して、自動意思決定によりアイテムの価格がカスタマイズされていることをユーザーに知らせます。

価格がユーザーに合わせてカスタマイズされたことを示している Google Play の購入画面。
図 3. 価格がユーザーに合わせてカスタマイズされたことを示す Google Play の購入画面。

必ず、消費者権利指令(2011/83/EU)の第 6 条(1)(ea)CRD に照らして、提供する価格がユーザーにカスタマイズされているかどうかを判断してください。

setIsOfferPersonalized() はブール値の入力を受け取ります。true の場合は、Play UI に開示を表示します。false の場合は、UI に開示を表示しません。デフォルト値は false です。

詳しくは、消費者向けヘルプセンターをご覧ください。

購入を処理する

ユーザーが購入を完了したら、アプリはその購入を処理する必要があります。ほとんどの場合、アプリは PurchasesUpdatedListener を通じて購入の通知を受け取ります。ただし、購入をフェッチするで説明しているように、アプリが BillingClient.queryPurchasesAsync() の呼び出しを認識する場合もあります。

アプリは次のようにして購入を処理する必要があります。

  1. 購入を確認します。
  2. ユーザーにコンテンツを配布し、コンテンツが配布されたことを確認します。必要に応じて、アイテムを消費済みとしてマークし、ユーザーがアイテムを再購入できるようにします。

購入を確認するには、まず購入ステータスPURCHASED かどうかをチェックします。購入が PENDING である場合は、保留中の取引の処理の説明に従って購入を処理します。onPurchasesUpdated() または queryPurchasesAsync() から受信した購入については、アプリが利用権を付与する前に購入を確認して正当性を保証する必要があります。購入を適切に確認する方法については、利用権を付与する前に購入を確認するをご覧ください。

購入を確認したら、アプリはユーザーに利用権を付与できます。利用権を付与した後、アプリは購入を承認する必要があります。この承認により、購入の利用権を付与したことが Google Play に通知されます。

利用権を付与して購入を承認するプロセスは、消費不可アイテムの購入か、消費可能アイテムの購入か、定期購入かによって異なります。

消費可能アイテムの場合、consumeAsync() メソッドは承認要件を履行し、アプリがユーザーに利用権を付与したことを示します。このメソッドにより、アプリは 1 回限りのアイテムを再購入可能にすることもできます。

1 回限りのアイテムが消費されたことを示すには、consumeAsync() を呼び出して、Google Play での再購入を可能にする購入トークンを追加します。ConsumeResponseListener インターフェースを実装するオブジェクトも渡す必要があります。このオブジェクトは、消費オペレーションの結果を処理します。オペレーションの完了時に Google Play Billing Library が呼び出す onConsumeResponse() メソッドをオーバーライドできます。

アイテムに関連付けられている購入トークンを使ってアイテムを消費する例を次に示します。

Kotlin

suspend fun handlePurchase(purchase: Purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    val purchase : Purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build()
    val consumeResult = withContext(Dispatchers.IO) {
        client.consumePurchase(consumeParams)
    }
}

Java

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    Purchase purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            }
        }
    };

    billingClient.consumeAsync(consumeParams, listener);
}

消費不可アイテムの購入を承認するには、Google Play Billing Library の BillingClient.acknowledgePurchase() または Google Play Developer API の Product.Purchases.Acknowledge を使用します。購入を承認する前に、アプリは Google Play Billing Library の isAcknowledged() メソッドまたは Google Developers API の acknowledgementState フィールドを使用して、購入がすでに承認されていないかをチェックする必要があります。

次の例は、Google Play Billing Library を使用して購入を承認する方法を示しています。

Kotlin

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

suspend fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
            val ackPurchaseResult = withContext(Dispatchers.IO) {
               client.acknowledgePurchase(acknowledgePurchaseParams.build())
            }
        }
     }
}

Java

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

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

定期購入は、消費不可アイテムと同様に処理されます。Google Play Billing Library の BillingClient.acknowledgePurchase() または Google Play Developer API の Purchases.Subscriptions.Acknowledge を使用して、定期購入を承認できます。初回の定期購入では必ず承認が必要です。定期購入の更新では承認は不要です。定期購入を承認する必要があるケースの詳細については、定期購入を販売するトピックをご覧ください。

購入をフェッチする

アプリですべての購入を確実に処理するには、PurchasesUpdatedListener で購入の更新情報をリッスンするだけでは不十分です。その方法では、ユーザーが行った購入の一部をアプリが認識しない可能性があります。アプリが購入を見失う場合または気づかない場合のシナリオを以下に示します。

  • 購入中のネットワーク問題: ユーザーが購入に成功して Google から確認通知を受け取ったが、デバイスが PurchasesUpdatedListener を通じて購入通知を受け取る前にネットワーク接続を失った場合。
  • 複数のデバイス: ユーザーがあるデバイスでアイテムを購入し、別のデバイスに切り替えたときにそのアイテムが表示されることを期待する場合。
  • アプリ外で行われた購入の処理: 一部の購入(販促用クーポンの利用など)はアプリの外部で行われる可能性があります。

このような状況に対処するには、アプリの onResume() メソッドで必ず BillingClient.queryPurchasesAsync() を呼び出し、すべての購入が購入を処理するの説明どおりに、正常に処理されるようにします。

アプリ外で行われた購入の処理

販促用クーポンの利用など、一部の購入はアプリの外部で発生することがあります。ユーザーは、アプリの外部で購入を行った場合、アプリ内メッセージが表示されるか、なんらかのメカニズムによりアプリが購入情報を正常に受信して処理したという通知を受け取ることを期待します。これを実現するメカニズムとしては、以下のようなものがあります。

  • アプリ内ポップアップを表示する。
  • アプリ内メッセージ ボックスにメッセージを配信し、アプリ内メッセージ ボックスに新着メッセージがあることを明示する。
  • OS の通知メッセージを使用する。

アプリが購入を認識したとき、アプリはどのような状態でもありうることに留意してください。購入時にアプリがインストールされていなかったことすらありえます。ユーザーは、アプリの状態に関係なく、アプリを再開すれば購入情報を受信できることを期待します。

購入が行われたときのアプリの状態にかかわらず、購入を検出しなければなりません。ただし、アイテムの配布をすぐにユーザーに通知しなくてよい場合もあります。次に例を示します。

  • ゲームのアクション パートの最中にメッセージを表示すると、ユーザーの気が散るおそれがあります。この場合は、アクション パートの終了後に通知する必要があります。
  • カットシーンでメッセージを表示すると、ユーザーの気が散るおそれがあります。この場合は、カットシーンの終了後に通知する必要があります。
  • ゲームの冒頭のチュートリアルとユーザー設定パート。新規ユーザーに特典を通知するタイミングとしては、ゲームを開始した直後またはユーザーによる初期設定時をおすすめします。ただし、メインのゲーム シーケンスをプレイできる状態になるまで、ユーザーへの通知を遅らせてもかまいません。

アプリの外部で行われた購入について、いつどのようにユーザーに通知するかを決定するにあたっては、常にユーザーへの配慮を優先してください。ユーザーはすぐに通知されないと困惑するかもしれません。さらには、アプリの使用をやめたり、ユーザー サポートに問い合わせたり、ソーシャル メディアで不平をこぼしたりする可能性もあります。 注: PurchasesUpdatedListener がアプリのコンテキストに登録されていることにより、アプリ外で行われた購入も含めて、購入に関する最新情報が処理されます。つまり、アプリのプロセスが存在しない場合、PurchasesUpdatedListener には通知されません。そのため、購入をフェッチするで説明されているように、アプリは onResume() メソッドで BillingClient.queryPurchasesAsync() を呼び出す必要があります。

保留中のトランザクションを処理する

Google Play では、保留中の取引がサポートされています。これは、ユーザーが購入を開始してから購入のお支払い方法が処理されるまでに 1 つ以上の追加手順が必要な取引です。ユーザーのお支払い方法に従って正常に請求されたことを Google が通知するまで、アプリはこのタイプの購入について利用権を付与してはなりません。

たとえば、ユーザーはお支払い方法として現金を選択でき、これによりアプリ内アイテムの PENDING の購入が作成されます。次に、ユーザーは取引を完了する実店舗を選択し、通知とメールの両方でコードを受け取ります。ユーザーは実店舗に足を運び、レジでコードを提示して現金による支払いを行います。Google は、デベロッパーとユーザーの両方に現金の受領を通知します。この時点で、アプリはユーザーに利用権を付与できます。

アプリは、保留中の取引をサポートするために、アプリの初期化の一環として enablePendingPurchases() を呼び出す必要があります。

アプリが PurchasesUpdatedListener を通して、または queryPurchasesAsync() の呼び出しの結果として新規の購入を受信したら、getPurchaseState() メソッドを使用して、購入ステータスが PURCHASEDPENDING かを判定します。

ユーザーが購入を完了したときにアプリが実行中であった場合は、PurchasesUpdatedListener が再度呼び出され、PurchaseStatePURCHASED になります。この時点で、アプリは 1 回限りの購入を処理する標準の方法で購入を処理できます。また、アプリの onResume() メソッドで queryPurchasesAsync() を呼び出し、アプリが実行中でなかったときに PURCHASED ステータスに移行した購入を処理する必要があります。

アプリでは、OneTimeProductNotifications をリッスンすることにより、保留中の購入に関するリアルタイム デベロッパー通知を使用することもできます。購入が PENDING から PURCHASED に移行すると、アプリに ONE_TIME_PRODUCT_PURCHASED 通知が届きます。購入がキャンセルされると、アプリに ONE_TIME_PRODUCT_CANCELED 通知が届きます。これは、要求される期間内にユーザーが支払いを完了しなかった場合に発生することがあります。これらの通知を受け取った場合は、Purchases.productsPENDING ステータスを含む Google Play Developer API を使用できます。

このシナリオをテストする方法に関する詳細な手順については、保留中の購入をテストするをご覧ください。

複数数量の購入の処理

Google Play Billing Library のバージョン 4.0 以降では、購入カートから数量を指定して、1 回の取引で同じアプリ内アイテムを複数購入できます。アプリは複数数量の購入を処理し、指定された購入数量に基づいて利用権を付与することが想定されています。

複数数量の購入に対応するには、アプリのプロビジョニング ロジックでアイテムの数量を確認する必要があります。quantity フィールドにアクセスするには、次のいずれかの API を使用します。

複数数量の購入を処理するロジックを追加したら、Google Play Console のアプリ内アイテム管理ページで、対応するアイテムについて複数数量の購入機能を有効にする必要があります。

コード例

購入のクエリ

次の例は、ユーザーの定期購入をクエリする方法を示しています。queryPurchasesAsync() は、有効な定期購入と未消費の 1 回だけの購入のみを返すことに注意してください。

Kotlin

val params = QueryPurchasesParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchasesAsync Kotlin extension function
val purchasesResult = billingClient.queryPurchasesAsync(params.build())

// check purchasesResult.billingResult
// process returned purchasesResult.purchasesList, e.g. display the plans user owns

Java

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
      .setProductType(ProductType.SUBS)
      .build(),
    new PurchasesResponseListener() {
      public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

購入履歴の取得

queryPurchaseHistoryAsync() は、各アイテムに関してユーザーが行った直近の購入の情報を返します。期限切れの購入やキャンセル済みの購入、消費済みの購入も対象に含まれます。

Kotlin 拡張機能を使用している場合は、queryPurchaseHistory() 拡張関数を使用できます。

Kotlin

val params = QueryPurchaseHistoryParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchaseHistory Kotlin extension function
val purchaseHistoryResult = billingClient.queryPurchaseHistory(params.build())

// check purchaseHistoryResult.billingResult
// process returned purchaseHistoryResult.purchaseHistoryRecordList, e.g. display purchase

Java

billingClient.queryPurchaseHistoryAsync(
    QueryPurchaseHistoryParams.newBuilder()
        .setProductType(ProductType.SUBS)
        .build(),
    new PurchaseHistoryResponseListener() {
      public void onPurchaseHistoryResponse(
        BillingResult billingResult, List purchasesHistoryList) {
          // check billingResult
          // process returned purchase history list, e.g. display purchase history
        }
    }
);