AIDL でアプリ内課金を使用する

アプリ内課金サービスの一部の機能の実装には、Android インターフェース定義言語(AIDL)インターフェースを使用できます。

アイテムを購入する

図 1. 購入リクエストの基本的なシーケンス

In-app Billing API を使った一般的な購入フローは、次のとおりです。

  1. アプリから Google Play に isBillingSupported リクエストを送信して、使用している In-app Billing API のバージョンがサポートされていることを確認します。このリクエストでは、ユーザーの国での請求が Google Play でサポートされているかどうかも確認できます。
  2. アプリの起動時、またはユーザーのログイン時に、ユーザーの所有アイテムを Google Play で確認することをおすすめします。ユーザーのアプリ内購入情報を照会するには、getPurchases リクエストを送信します。リクエストが成功すると、購入アイテムのアイテム ID のリスト、個別の購入詳細のリスト、購入に対する署名リストを含む Bundle が Google Play から返されます。
  3. 通常、デベロッパーはユーザーに購入可能なアイテムをお知らせします。自分が Google Play で定義したアプリ内アイテムの詳細情報を照会するには、アプリから getSkuDetails リクエストを送信します。クエリ リクエストではアイテム ID のリストを指定してください。リクエストが成功すると、アイテムの価格、タイトル、説明、購入タイプなどの詳細情報を含む Bundle が Google Play から返されます。
  4. ユーザーがアプリ内アイテムを所有していなければ、購入を開始できます。購入リクエストを開始するには、アプリから getBuyIntent リクエストを送信します。その際、購入するアイテムのアイテム ID やその他のパラメータを指定してください。Play Console で新たなアプリ内アイテムを作成した際は、アイテム ID を記録しておく必要があります。
    1. Google Play は購入の精算 UI を立ち上げるためにアプリが使用する PendingIntent を含む Bundle を返します。
    2. アプリは startIntentSenderForResult メソッドを呼び出して、保留していたインテントを発行します。
    3. 精算フローが終了すると(つまり、ユーザーがアイテムを購入または購入をキャンセルすると)、Google Play は Intent レスポンスを onActivityResult メソッドに送信します。onActivityResult の結果コードには、購入が成功したかキャンセルされたかを示す結果コードが含まれます。Intent レスポンスには、Google Play が購入トランザクションを一意に識別するために生成した purchaseToken 文字列などの購入アイテム情報が含まれています。Intent には、プライベート デベロッパー キーで署名した購入署名も含まれます。

In-app Billing API の呼び出しやサーバー レスポンスに関する詳細については、In-app Billing リファレンスをご覧ください。

アプリ内アイテムを消費する

消費メカニズムを利用して、管理対象アイテムのユーザー所有権をトラックすることができます。

In-app Billing API では、すべてのアプリ内アイテムが管理されています。つまり、すべての管理対象アイテム購入のユーザー所有情報は Google Play で管理されているため、ユーザーの購入情報が必要な時にはいつでもアプリから照会できます。ユーザーが正常に管理対象アイテムを購入すると、Google Play に購入記録が残ります。管理対象アイテムが購入されると、「所有された」と認識されます。「所有された」状態の管理対象アイテムは、Google Play から購入できません。Google Play で再び購入可能な状態にするには、「所有された」管理対象アイテムに対する消費リクエストを送る必要があります。管理対象アイテムを消費すると、「所有されていない」状態に戻り、以前の購入データは破棄されます。

図 2. 消費リクエストの基本的なシーケンス

ユーザーが所有しているアイテムのリストを呼び出すには、アプリから Google Play に getPurchases を送信します。アプリは consumePurchase を呼び出すことで消費リクエストを送ります。リクエストの引数には、購入時に Google Play から取得した、管理対象アイテムに固有の purchaseToken 文字列を指定する必要があります。Google Play から正常に消費が記録されたことを示すステータス コードが返されます。

消費可能および消費不可な管理対象アイテム

管理対象アイテムの消費可否はデベロッパー側で決めることができます。

消費不可アイテム
アプリ内で一度だけ購入され、その効力がずっと続く管理対象アイテムは、通常消費可能にはしません。こうしたアイテムは一旦購入されると、永続的にユーザーの Google アカウントに関連付けられます。消費不可の管理対象アイテムの例としては、プレミアム アップグレードやレベルパックなどが挙げられます。
消費可能アイテム
逆に、何度も購入可能なアイテムは消費の対象にすることができます。一般的には、一時的な効力があるアイテムがこれに該当します。たとえば、ゲーム内キャラクターがライフポイントを増やしたり、追加のゴールドコインを獲得したりするような場合です。アプリ内で購入したアイテムに効力や効果を付与することを、管理対象アイテムを「プロビジョニング」すると言います。ユーザーに管理対象アイテムをプロビジョニングする方法を管理し、トラックするのはデベロッパーの責務になります。

重要: 消費可能な管理対象アイテムをアプリでプロビジョニングする前に、Google Play に消費リクエストを送り、消費が正しく記録されたというレスポンスを受け取る必要があります。

消費可能な購入をアプリ内で管理する

消費可能な管理対象アイテムを購入する基本フローは次のとおりです。

  1. getBuyIntent で、購入フローを開始します。
  2. Google Play から返された Bundle を調べて、購入が正常に完了したことを確認します。
  3. 購入が成功したら、consumePurchase メソッドを呼び出して購入を消費します。
  4. Google Play から返されたレスポンス コードを調べて、消費が正常に完了したことを確認します。
  5. 消費が完了したら、アプリ内でアイテムをプロビジョニングします。

その後、アプリの起動時またはログイン時に、ユーザーが未決済の消費可能なアプリ内アイテムを所有しているかどうかを確認します。所有している場合はそのアイテムの消費とプロビジョニングを行ってください。アプリ内で消費可能なアプリ内アイテムを実装する場合、次のようなアプリの開始フローが推奨されます。

  1. getPurchases リクエストを送信して、ユーザーが所有するアプリ内アイテムを照会します。
  2. 消費可能なアプリ内アイテムがある場合には、consumePurchase を呼び出してアイテムを消費します。アプリで消費アイテムの購入注文を完了したあと、消費リクエストを送信するタイミングがくる前に、停止または接続が切断されるケースも考えられるため、このステップが必要になります。
  3. Google Play から返されたレスポンス コードを調べて、消費が正常に完了したことを確認します。
  4. 消費が完了したら、アプリ内でアイテムをプロビジョニングします。

特典購入を設定する

AIDL を使って特典アイテムを扱うときは、ユーザーが特典を受ける前に購入の「インテント」をキャッシュする必要があります。バックグラウンド スレッドで購入インテントを呼び出し、ユーザーが特典を受けるため操作を行うまで、成功のレスポンス「インテント」を保存しておきます。

SKU のリストと読み込み

ユーザーに特典アイテムを付与する前に、getSkuDetails() を呼び出してアイテムの詳細を取得します。SKU のリスト内の各特典アイテムに対して、新しい JSON フィールド "rewardToken" が入力されます。

優れたユーザーエクスペリエンスを提供するため、ユーザーに特典アイテムを付与する前に、広告が読み込み済みかつ利用可能であることを確認する必要があります。そのため、バックグラウンド スレッドで getBuyIntentExtraParams() を呼び出します。BILLING_RESPONSE_RESULT_OK のレスポンスを受け取ったら、ユーザーに対して特典アイテムを有効にし、返された PendingIntent オブジェクトは後で使用するために保存します。 次のコード スニペットは、特典アイテムに関連付けられた広告を読み込むためのプロセスを示しています。

Kotlin

    val rewardToken = skuDetailsJson.optString("rewardToken")
    val extraParams = Bundle().putString("rewardToken", rewardToken)

    // This call blocks the current thread, so do this in the background.
    val buyIntentBundle : Bundle = mService.getBuyIntentExtraParams(9, packageName,
            sku, "inapp", "", extraParams)

    val response = buyIntentBundle.getInt("RESPONSE_CODE")
    if (response == BILLING_RESPONSE_RESULT_OK) {
        // Enable rewarded product.

        // Save this object for use later.
        val pendingIntentToSave = bundle.getParcelable(RESPONSE_BUY_INTENT)
    } else {
        // Don't offer rewarded product.
    }
    

Java

    String rewardToken = skuDetailsJson.optString("rewardToken");
    Bundle extraParams = new Bundle();
    extraParams.putString("rewardToken", rewardToken);

    // This call blocks the current thread, so do this in the background.
    Bundle buyIntentBundle = mService.getBuyIntentExtraParams(9, getPackageName(),
            sku, "inapp", "", extraParams);

    int response = buyIntentBundle.getInt("RESPONSE_CODE");
    if (response == BILLING_RESPONSE_RESULT_OK) {
        // Enable rewarded product.

        // Save this object for use later.
        PendingIntent pendingIntentToSave = bundle.getParcelable(RESPONSE_BUY_INTENT);
    } else {
        // Don't offer rewarded product.
    }
    

年齢に適した広告を宣言する

児童オンライン プライバシー保護法(Children's Online Privacy Protection Act: COPPA)一般データ保護規則(GDPR)など、子どもおよび同意年齢に満たないユーザーに関する法的義務の遵守を促進するために、どの広告を米国内では子ども向けとして扱う必要があるか、またはお住まいの国の該当する年齢に満たないユーザー向けとして扱う必要があるかをアプリで宣言してください。AdMob ヘルプセンターでは、広告リクエストに子ども向け取り扱いのタグをいつ設定するか、同意年齢に満たないユーザーの取り扱いのタグをいつ設定するか、およびそれぞれの効果について説明しています。

特典のリクエストが子どもや同意年齢に満たないユーザーを対象としていることを示すには、以下のコード スニペットのように childDirectedunderAgeOfConsent の追加パラメータを含めます。

Kotlin

    val rewardToken = skuDetailsJson.optString("rewardToken")
    val extraParams = Bundle().putString("rewardToken", rewardToken)
            .putInt("childDirected", ChildDirected.CHILD_DIRECTED)
            .putInt("underAgeOfConsent", UnderAgeOfConsent.UNDER_AGE_OF_CONSENT)

    // This call blocks the current thread, so do this in the background.
    val buyIntentBundle : Bundle = mService.getBuyIntentExtraParams(9, packageName,
            sku, "inapp", "", extraParams)
    

Java

    Bundle extraParams = new Bundle();
    extraParams.putString("rewardToken", rewardToken);
    extraParams.putInt("childDirected", ChildDirected.CHILD_DIRECTED);
    extraParams.putInt("underAgeOfConsent", UnderAgeOfConsent.UNDER_AGE_OF_CONSENT);

    // This call blocks the current thread, so do this in the background.
    Bundle buyIntentBundle =
      mService.getBuyIntentExtraParams(
        9, getPackageName(), sku, "inapp", "", extraParams);
    

ユーザーに特典を付与する前に広告を再生する

ユーザーがボタンをクリックして広告の視聴を開始すると、保存された PendingIntent オブジェクトを使用してアプリで広告の再生が開始されます。これには、startIntentSenderForResult() を呼び出します。

Kotlin

    startIntentSenderForResult(
        pendingIntentToSave,
        RC_BUY, Intent(),
        0,
        0,
        0
    )
    

Java

    startIntentSenderForResult(pendingIntentToSave, RC_BUY, new Intent(),
            0, 0, 0);
    

その後、次のコード スニペットに示すように請求ワークフローの結果を onActivityResult() で処理します。広告再生プロセスを処理する場合、Google Play は他の請求フローと同じ一連のサーバー レスポンス コードを使用します。

Kotlin

    fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent) {
        if (requestCode == RC_BUY) {
            int responseCode = data.getIntExtra(RESPONSE_CODE)
            String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA)
            String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE)

            // Handle reward purchase.
        }
    }
    

Java

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == RC_BUY) {
            int responseCode = data.getIntExtra(RESPONSE_CODE);
            String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
            String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

            // Handle reward purchase.
        }
    }
    

ローカル キャッシュ

現在 Google Play クライアントではアプリ内課金情報をデバイス上にローカルでキャッシュできるため、In-app Billing API を使ってこれらの情報をより頻繁に照会することができます。次の In-app Billing API の呼び出しは、ネットワーク接続ではなく、キャッシュのルックアップによって行われています。このため、API のレスポンス タイムは飛躍的に短縮されています。

  • getBuyIntent
  • getPurchases
  • isBillingSupported