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

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

購入ライフサイクル

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

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

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

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

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

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

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

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

Groovy

dependencies {
    def billing_version = "7.0.0"

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

Kotlin

dependencies {
    val billing_version = "7.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 = "7.0.0"

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

Kotlin

dependencies {
    val billing_version = "7.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)
   // Configure other settings.
   .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)
    // Configure other settings.
    .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) {
    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 = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("product_id_example")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

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

    // Process the result.
}

まれに、一部のデバイスでは Google Play 開発者サービスのバージョンが古いことが原因で、ProductDetailsqueryProductDetailsAsync() に対応していない場合があります。適切にこのシナリオに対応できるようにするには、Play Billing Library 5 移行ガイドで、下位互換性機能の使い方を確認してください。

結果を処理する

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 productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // For One-time product, "setOfferToken" method shouldn't be called.
        // For subscriptions, to get an offer token, call ProductDetails.subscriptionOfferDetails()
        // for a list of offers that are available to the user
        .setOfferToken(selectedOfferToken)
        .build()
)

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .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 = ...;

ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // For one-time products, "setOfferToken" method shouldn't be called.
            // For subscriptions, to get an offer token, call
            // ProductDetails.subscriptionOfferDetails() for a list of offers
            // that are available to the user.
            .setOfferToken(selectedOfferToken)
            .build()
    );

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .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() を呼び出して購入を認識することがあります。

また、安全なバックエンドにリアルタイム デベロッパー通知クライアントがある場合は、新規購入を通知する subscriptionNotification または oneTimeProductNotification を受信することで新規購入を登録できます。これらの通知を受け取ったら、Google Play Developer API を呼び出して完全なステータスを取得し、バックエンド ステータスを更新します。

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

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

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

購入を確認したら、アプリはユーザーに利用権を付与できます。購入に関連付けられたユーザー アカウントは、次の識別子で識別できます。サーバーサイドのアプリ内アイテムは、Purchases.products:get から返される ProductPurchase.obfuscatedExternalAccountId、サーバーサイドの定期購入は Purchases.subscriptions:get から返される SubscriptionPurchase.obfuscatedExternalAccountId、クライアントサイドの定期購入は Purchase.getAccountIdentifiers()obfuscatedAccountId です。ただし、購入時に setObfuscatedAccountId で設定しておく必要があります。

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

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

消費型アイテム

アプリに安全なバックエンドがある場合は、消費型アイテムには Purchases.products:consume を使用して、購入品が確実に消費されるようにすることをおすすめします。Purchases.products:get の呼び出し結果にある consumptionState をチェックして、購入品がまだ消費されていないことを確かめます。アプリがバックエンドのないクライアント専用である場合は、Google Play Billing Library の consumeAsync() を使用します。どちらのメソッドも承認の要件を満たし、アプリがユーザーに利用権を付与したことを示します。これらのメソッドにより、入力された購入トークンに対応する 1 回限りのアイテムを、アプリで再び購入可能にすることもできます。consumeAsync()では、ConsumeResponseListener インターフェースを実装するオブジェクトも渡す必要があります。このオブジェクトは、消費オペレーションの結果を処理します。オペレーションの完了時に Google Play Billing Library が呼び出す onConsumeResponse() メソッドをオーバーライドできます。

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

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);
}

非消費型アイテム

アプリに安全なバックエンドがある場合、非消費型アイテムの購入には、確実に承認できるように Purchases.products:acknowledge を使用することをおすすめします。Purchases.products:get の呼び出し結果にある acknowledgementState をチェックして、その購入が以前に承認されていないことを確かめます。

アプリがクライアント専用の場合は、Google Play Billing Library の BillingClient.acknowledgePurchase() を使用します。購入を承認する前に、アプリは Google Play Billing Library の isAcknowledged() メソッドを使用して、購入がすでに承認されていないかチェックする必要があります。

次の例は、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 Developer API の Purchases.subscriptions.acknowledge を使用してください。Purchases.subscriptions:get で購入リソースの acknowledgementState をチェックして、その購入が以前に承認されていないことを確かめます。バックエンドがない場合は、Google Play Billing Library の BillingClient.acknowledgePurchase() を使用し、isAcknowledged() をチェックした後、定期購入を承認できます。初回の定期購入では必ず承認が必要です。定期購入の更新は、承認の必要はありません。定期購入の承認が必要な場合の詳細については、定期購入を販売するトピックをご覧ください。

購入をフェッチする

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

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

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

次の例は、ユーザーの定期購入をフェッチする方法を示しています。 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<Purchase> purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

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

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

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

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

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

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

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

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

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

たとえば、ユーザーが後で現金で支払う実店舗を選択して取引を開始できます。ユーザーは通知とメールの両方でコードを受け取ります。ユーザーは実店舗に足を運び、レジでコードを提示して現金による支払いを行います。Google は、デベロッパーとユーザーの両方に支払いの受領を通知します。この時点で、アプリはユーザーに利用権を付与できます。

BillingClient の初期化の一環として enablePendingPurchases() を呼び出して、アプリの保留中の取引を有効にします。アプリは 1 回限りのアイテムに対し、保留中の取引を有効にしてサポートする必要があります。サポートを追加する前に、保留中の取引の購入ライフサイクルについて理解しておいてください。

アプリが PurchasesUpdatedListener を通じて、または queryPurchasesAsync() を呼び出した結果として新しい購入を受け取ったら、getPurchaseState() メソッドを使用して、購入ステータスが PURCHASEDPENDING かを判断します。ステータスが PURCHASED の場合にのみ利用権を付与してください。

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

購入が PENDING から PURCHASED に移行すると、リアルタイム デベロッパー通知クライアントに ONE_TIME_PRODUCT_PURCHASED または SUBSCRIPTION_PURCHASED 通知が届きます。購入がキャンセルされると、ONE_TIME_PRODUCT_CANCELED または SUBSCRIPTION_PENDING_PURCHASE_CANCELED 通知が届きます。これは、要求される期間内にユーザーが支払いを完了しなかった場合に発生することがあります。購入の現在のステータスは、Google Play Developer API を使用していつでも確認できます。

複数数量の購入の処理

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

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

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

ユーザーの請求構成をクエリする

getBillingConfigAsync(): ユーザーが Google Play で使用する国を提供します。

BillingClient の作成後に、ユーザーの請求構成をクエリできます。次のコード スニペットは、getBillingConfigAsync() を呼び出す方法を示しています。BillingConfigResponseListener を実装してレスポンスを処理します。このリスナーは、アプリから開始されたすべての請求構成クエリの更新を受け取ります。

返された BillingResult にエラーがない場合は、BillingConfig オブジェクトの countryCode フィールドを確認すると、ユーザーの Google Play の国を取得できます。

Kotlin

// Use the default GetBillingConfigParams.
val getBillingConfigParams = GetBillingConfigParams.newBuilder().build()
billingClient.getBillingConfigAsync(getBillingConfigParams,
    object : BillingConfigResponseListener {
        override fun onBillingConfigResponse(
            billingResult: BillingResult,
            billingConfig: BillingConfig?
        ) {
            if (billingResult.responseCode == BillingResponseCode.OK
                && billingConfig != null) {
                val countryCode = billingConfig.countryCode
                ...
            } else {
                // TODO: Handle errors
            }
        }
    })

Java

// Use the default GetBillingConfigParams.
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(getBillingConfigParams,
    new BillingConfigResponseListener() {
      public void onBillingConfigResponse(
          BillingResult billingResult, BillingConfig billingConfig) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK
            && billingConfig != null) {
            String countryCode = billingConfig.getCountryCode();
            ...
         } else {
            // TODO: Handle errors
        }
      }
    });