アプリ内課金を実装する

Google Play のアプリ内課金なら、簡単でシンプルなインターフェースでアプリ内課金リクエストの送信とアプリ内課金トランザクションの管理ができます。 下記に、Version 3 API を使ってアプリからアプリ内課金サービスを呼び出す方法を説明します。

注: 完全な実装およびアプリのテストに関する詳細は、トレーニング クラスのアプリ内アイテムを販売するをご覧ください。 トレーニング クラスではアプリ内課金に対応したアプリの完全なサンプルを提供しており、接続の設定、Google Play を経由した課金リクエストの送信とレスポンスの処理、メイン アクティビティからアプリ内課金を呼び出せるバックグラウンド スレッドの管理など、主要なタスクの処理にすぐに役立つクラスもあります。

開始する前に、アプリ内課金の概要に目を通し、アプリ内課金の概念を把握しておくと実装がしやすくなります。

アプリ内課金を実装するには、次の処理が必要です。

  1. アプリ内課金ライブラリをプロジェクトに追加します。
  2. AndroidManifest.xml ファイルをアップデートします。
  3. ServiceConnection を作成し IInAppBillingService にバインドします。
  4. アプリ内課金リクエストを、アプリから IInAppBillingService に送ります。
  5. Google Play からのアプリ内課金レスポンスを処理します。

AIDL ファイルをプロジェクトに追加する

IInAppBillingService.aidl は、In-app Billing Version 3 サービスへのインターフェイスを定義する AIDL(Android インターフェース定義言語)ファイルです。 このインターフェイスを使って、IPC メソッド を呼び出し、課金リクエストを行います。

AIDL ファイルの入手方法:

  1. Android SDK Manager を起動します。
  2. SDK Manager で、Extras セクションを展開します。
  3. [Google Play Billing Library] を選択します。
  4. [Install packages] をクリックして、ダウンロードを完了します。

IInAppBillingService.aidl ファイルが <sdk>/extras/google/play_billing/ にインストールされます。

AIDL をプロジェクトに追加する方法:

  1. まず Google Play Billing Library を自身の Android プロジェクトにダウンロードします。
    1. [Tools] > [Android] > [SDK Manager] の順に選択します。
    2. [Appearance & Behavior] > [System Settings] > [Android SDK] の中の [SDK Tools] タブをクリックして、[Google Play BillingLibrary] を選択し、ダウンロードします。
  2. 次に IInAppBillingService.aidl ファイルをプロジェクトにコピーします。
    • Android Studio を使用する場合:
      1. Project ツール ウィンドウの src/main に進みます。
      2. [File] > [New] > [Directory] を選択し、[New Directory] ウィンドウで aidl を入力して、[OK] を選択します。
      3. [File] > [New] > [Package] を選択し、[New Package] ウィンドウで com.android.vending.billing を入力して、[OK] を選択します。
      4. オペレーティング システムのファイル エクスプローラを使って <sdk>/extras/google/play_billing/ に進み、IInAppBillingService.aidl ファイルをコピーしてプロジェクトの com.android.vending.billing パッケージ内に貼りつけます。
    • Android Studio 以外の環境で開発している場合:ディレクトリ /src/com/android/vending/billing を作成し、IInAppBillingService.aidl ファイルをこのディレクトリにコピーします。 AIDL ファイルをプロジェクトに含めて、Gradle ツールでプロジェクトをビルドすると、IInAppBillingService.java ファイルが生成されます。
  3. アプリをビルドします。プロジェクト内の /gen ディレクトリに IInAppBillingService.java というファイルが生成されたことを確認してください。

アプリのマニフェストを更新する

アプリ内課金は Google Play アプリに依存しており、アプリと Google Play サーバー間のやりとりはすべて Google Play アプリが処理しています。 Google Play アプリを使うには、アプリが適切なパーミッションをリクエストする必要があります。 そのためには、AndroidManifest.xml ファイルに com.android.vending.BILLING パーミッションを追加します。 アプリが、アプリ内課金パーミッションを宣言していない状態で課金リクエストを送った場合、Google Play はリクエストを拒否し、エラーを返します。

アプリに必要なパーミッションを付与するには、AndroidManifest.xml ファイルの中に以下の一行を追加してください。

<uses-permission android:name="com.android.vending.BILLING" />

ServiceConnection を作成する

アプリと Google Play の間でメッセージをやりとりするには、ServiceConnection が必要です。 少なくとも、アプリでは次の処理が必要です。

  • IInAppBillingService にバインドします。
  • 課金リクエスト(IPC メソッドの呼び出し)を Google Play アプリに送信します。
  • 課金リスエストごとに返ってくる同期レスポンス メッセージを処理します。

IInAppBillingService にバインドする

Google Play 上のアプリ内課金サービスに接続するには、ServiceConnection を実装してアクティビティを IInAppBillingService にバインドします。 onServiceDisconnected と、onServiceConnected メソッドをオーバーライドし、接続できたら、IInAppBillingService インスタンスへのリファレンスを取得します。

IInAppBillingService mService;

ServiceConnection mServiceConn = new ServiceConnection() {
   @Override
   public void onServiceDisconnected(ComponentName name) {
       mService = null;
   }

   @Override
   public void onServiceConnected(ComponentName name,
      IBinder service) {
       mService = IInAppBillingService.Stub.asInterface(service);
   }
};

アクティビティの onCreate メソッドで、bindService メソッドを呼び出してバインドを実行します。 メソッドに、アプリ内課金サービスを参照する Intent と、自身が作成した ServiceConnection のインスタンスを渡します。インテントのターゲット パッケージ名を com.android.vending (Google Play アプリのパッケージ名)に明示的に設定します。

警告: 課金トランザクションの安全性を確保するため、下記の例のように、setPackage() を使って、インテントのターゲット パッケージ名を明示的に com.android.vending に設定するようにしてください。 パッケージ名を明示的に指定することで、Google Play アプリだけが自身のアプリからの課金リクエストを処理できるようになるため、他のアプリによるリクエストの傍受を防ぐことができます。

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Intent serviceIntent =
      new Intent("com.android.vending.billing.InAppBillingService.BIND");
  serviceIntent.setPackage("com.android.vending");
  bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}

これで、mService リファレンスを使って Google Play サービスと通信できるようになりました。

重要: Activity が終了したら、必ずアプリ内課金サービスのバインドを解除してください。 バインドを解除しない場合、オープンなサービス接続によって端末のパフォーマンスが低下する可能性があります。 下記の例は、アクティビティの onDestroy メソッドをオーバーライドして、アプリ内課金へのサービス接続である mServiceConn のバインドを解除する方法を示しています。

@Override
public void onDestroy() {
    super.onDestroy();
    if (mService != null) {
        unbindService(mServiceConn);
    }
}

IInAppBillingService にバインドしたサービス接続の完全な実装例については、トレーニング クラスのアプリ内アイテムを販売すると関連するサンプルをご覧ください。

アプリ内課金リクエストを行う

アプリが Google Play に接続されると、アプリ内アイテムの購入リクエストを開始できます。 ユーザーが支払い方法を入力するための購入インターフェースは Google Play で提供されているため、アプリが直接支払いのトランザクションを処理する必要はありません。 アイテムが購入されると、Google Play はユーザーがそのアイテムの所有者であることを認識し、同じ商品 ID で別のアイテムをユーザーが購入できないようにします(アイテムを使い切るまでの間)。 アプリ内でのアイテムの使用を制御し、アイテムの再購入ができるように Google Play に通知することも可能です。 さらに、ユーザーの購入履歴を素早く取得するため、Google Play にクエリを発行することもできます。 ユーザーがアプリを起動した際に購入意欲を刺激したい場合などに、この機能が役に立ちます。

購入可能なアイテムに対するクエリ

In-app Billing Version 3 API を使用すると、自身のアプリでアイテムの詳細情報を Google Play に照会できます。 アプリ内課金サービスにリクエストを渡すには、まず"ITEM_ID_LIST" キーの付いた商品 ID の文字列 ArrayList を含む Bundle を作成します。ここでは、それぞれの文字列が購入可能なアイテムに対応した商品 ID になります。

ArrayList<String> skuList = new ArrayList<String> ();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);

Google Play からこの情報を取得するには、In-app Billing Version 3 API の getSkuDetails メソッドを呼び出し、メソッドにアプリ内課金 API バージョン("3")、呼び出したアプリのパッケージ名、購入タイプ("inapp")、作成した Bundle を渡します。

Bundle skuDetails = mService.getSkuDetails(3,
   getPackageName(), "inapp", querySkus);

返された BundleBILLING_RESPONSE_RESULT_OK(0)のレスポンス コードがあれば、リクエストは成功です。

警告: メイン スレッド上で getSkuDetails メソッドは呼び出さないでください。 このメソッドを呼び出すとネットワーク リクエストが発行され、メイン スレッドがブロックされる可能性があります。 代わりに別のスレッドを作成し、その中で getSkuDetails メソッドを呼び出してください。

Google Play からの全レスポンス コード一の覧はアプリ内課金リファレンスをご覧ください。

クエリの結果は DETAILS_LIST キーとともに String ArrayList に格納され、購入情報は JSON 形式の文字列で保存されます。 返される商品詳細情報の種別については、アプリ内課金リファレンスをご覧ください。

以下の例は、先ほどのコード スニペットで返された skuDetails Bundle から、アプリ内アイテムの価格を取得する方法を示しています。

int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> responseList
      = skuDetails.getStringArrayList("DETAILS_LIST");

   for (String thisResponse : responseList) {
      JSONObject object = new JSONObject(thisResponse);
      String sku = object.getString("productId");
      String price = object.getString("price");
      if (sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price;
      else if (sku.equals("gas")) mGasPrice = price;
   }
}

アイテムを購入する

アプリで購入リクエストを開始するには、アプリ内課金サービスの getBuyIntent メソッドを呼び出します。 このメソッドに、In-app Billing API のバージョン("3")、呼び出すアプリのパッケージ名、購入アイテムの商品 ID、購入タイプ("inapp" または "subs")、文字列 developerPayload を渡します。 文字列 developerPayload は、Google Play に購入情報とともに返信してほしい追加の引数を指定するために使います。

Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
   sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");

返された Bundle に、BILLING_RESPONSE_RESULT_OK(0)のレスポンス コードと購入フローの開始時に使った PendingIntent が含まれていれば、リクエストは成功です。 Google Play からの全レスポンス コードの一覧はアプリ内課金リファレンスをご覧ください。 次に、BUY_INTENT キーを使って、Bundle レスポンスから PendingIntent を取得します。

PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");

購入トランザクションを完了するには、startIntentSenderForResult メソッドを呼び出し、作成した PendingIntent を使います。 以下の例では、リクエスト コードに対して任意の値 1001 を使用しています。

startIntentSenderForResult(pendingIntent.getIntentSender(),
   1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
   Integer.valueOf(0));

Google Play は、アプリの onActivityResult メソッドに対する PendingIntent へのレスポンスを送信します。 onActivityResult メソッドは Activity.RESULT_OK(1)または Activity.RESULT_CANCELED(0)の結果コードを持ちます。Intent レスポンスで返される注文情報の種別については、アプリ内課金リファレンスをご覧ください。

注文に対する購入データは JSON 形式の文字列で、レスポンス Intent 内の INAPP_PURCHASE_DATA キーにマッピングされます。以下に例を示します。

'{
   "orderId":"GPA.1234-5678-9012-34567",
   "packageName":"com.example.app",
   "productId":"exampleSku",
   "purchaseTime":1345678900000,
   "purchaseState":0,
   "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
   "purchaseToken":"opaque-token-up-to-1000-characters"
 }'

注: Google Play は購入用のトークンを生成します。このトークンは、意味を成さない 1,000 字以内の文字の羅列です。 購入アイテムを消費するに記載しているように、購入アイテムを消費したときなどにトークン全体を別のメソッドに渡します。 トークンは短縮や切り捨てはせず、トークン全体を保存し、返す必要があります。

前の例に続き、レスポンス コード、購入データ、レスポンス Intent からの署名を取得します。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   if (requestCode == 1001) {
      int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
      String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
      String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");

      if (resultCode == RESULT_OK) {
         try {
            JSONObject jo = new JSONObject(purchaseData);
            String sku = jo.getString("productId");
            alert("You have bought the " + sku + ". Excellent choice,
               adventurer!");
          }
          catch (JSONException e) {
             alert("Failed to parse purchase data.");
             e.printStackTrace();
          }
      }
   }
}

セキュリティ上の推奨事項: 購入リクエストを送るときには、そのリクエストを一意に識別する文字列トークンを作成して developerPayload 内に含めてください。無作為に生成した文字列をトークンとして使えます。 Google Play から購入レスポンスを受け取ったら、返されたデータ署名、orderId、文字列 developerPayload を必ずチェックしてください。 セキュリティ向上のため、保護された自身のサーバー上でチェックを実施する必要があります。 orderId が固有の値であり、以前に使用したことがないものであることを確認してください。また、文字列 developerPayload は購入リクエストと共に前に送ったトークンと一致することも確認してください。

購入済みアイテムに対するクエリ

アプリでユーザーの購入情報を取得するには、the In-app Billing Version 3 サービスの getPurchases メソッドを呼び出します。 このメソッドに、In-app Billing API のバージョン("3")、呼び出すアプリのパッケージ名、購入タイプ("inapp" または "subs")を渡します。

Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);

Google Play が返すのは、端末に現在ログインしているユーザー アカウントでの購入情報のみです。 返された Bundle に 0 のレスポンス コードがあれば、リクエストは成功しています。またレスポンス Bundle には、商品 ID のリスト、購入ごとの注文詳細情報のリスト、各注文の署名も含まれています。

パフォーマンス向上のため、アプリ内課金サービスは、最初に getPurchase が呼び出された時点でユーザーが所有している商品を 700 個までしか返しません。 ユーザーが多くの商品を所有している場合、Google Play は Bundle レスポンス内の INAPP_CONTINUATION_TOKEN キーにマッピングされた文字列トークンを含めて、商品をさらに取得できることを伝えます。 その後、アプリは getPurchases を呼び出して、このトークンを引数として渡すことができます。 Google Play は、ユーザーが所有している全商品がアプリに送られるまで、Bundle レスポンス内で残りのトークンを返し続けます。

getPurchases から返されるデータの詳細については、アプリ内課金リファレンスをご覧ください。 以下の例は、レスポンスからデータを取得する方法を示しています。

int response = ownedItems.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList<String> ownedSkus =
      ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
   ArrayList<String>  purchaseDataList =
      ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
   ArrayList<String>  signatureList =
      ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
   String continuationToken =
      ownedItems.getString("INAPP_CONTINUATION_TOKEN");

   for (int i = 0; i < purchaseDataList.size(); ++i) {
      String purchaseData = purchaseDataList.get(i);
      String signature = signatureList.get(i);
      String sku = ownedSkus.get(i);

      // do something with this purchase information
      // e.g. display the updated list of products owned by user
   }

   // if continuationToken != null, call getPurchases again
   // and pass in the token to retrieve more items
}

購入アイテムを消費する

In-app Billing Version 3 API を使うと、Google Play で購入したアプリ内アイテムの所有者をトラックできます。 アプリ内アイテムが購入されると、「owned」と認識され、Google Play から購入できなくなります。 Google Play で再び購入可能にする前に、そのアプリ内アイテムに対して消費リクエストを送る必要があります。

重要: 管理対象のアプリ内アイテムは消費できますが、定期購入は消費できません。

アプリ内で消費メカニズムをどう使うかは、デベロッパーの判断に委ねられています。よくある例としては、ユーザーが何度も購入を希望するような(例: ゲーム内の通貨や品物など)一時的なメリットを付加して、アプリ内アイテムの消費を可能にするようなケースが考えられます。 また一般的には、一度しか購入しないアプリ内アイテムについては消費という形をとらず、永続的な効果があるサービス(例: プレミアム アップグレードなど)を提供します。

購入の消費を記録するには、consumePurchase メソッドをアプリ内課金サービスに送信し、削除対象の購入を識別する文字列の値 purchaseToken を渡します。 purchaseToken は、購入リクエストが成功した後に、Google Play サービスによって INAPP_PURCHASE_DATA 文字列で返されるデータの一部です。 次の例では、token 変数の purchaseToken で識別される商品の消費について記録しています。

int response = mService.consumePurchase(3, getPackageName(), token);

警告: メイン スレッド上で consumePurchase メソッドは呼び出さないでください。 このメソッドを呼び出すとネットワーク リクエストが発行され、メイン スレッドがブロックされる可能性があります。 代わりに別のスレッドを作成し、その中で consumePurchase メソッドを呼び出してください。

アプリ内アイテムのユーザーへの提供方法の管理とトラックは、デベロッパーに委ねられています。 たとえば、ユーザがゲーム内通貨を購入した場合、購入金額に基づいてプレイヤーの残高を更新する必要があります。

セキュリティ上の推奨事項: 消費可能なアプリ内購入のメリットをユーザーに与える前に、消費リクエストを送る必要があります。 アイテムを提供する前に、Google Play からの消費レスポンスを確実に受け取っていることを確認してください。

定期購入を実装する

定期購入の購入フローを開始する方法は、商品タイプを「subs」に指定することを除いては、商品購入のフローと同様です。 購入結果は、自身のアクティビティの onActivityResult メソッドに配信されます。これはアプリ内アイテムのケースとまったく同じです。

Bundle bundle = mService.getBuyIntent(3, "com.example.myapp",
   MY_SKU, "subs", developerPayload);

PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) {
   // Start purchase flow (this brings up the Google Play UI).
   // Result will be delivered through onActivityResult().
   startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(),
       Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
}

アクティブな定期購入に関するクエリには、getPurchases メソッドを使用して、商品タイプのパラメータを「subs」に指定してください。

Bundle activeSubs = mService.getPurchases(3, "com.example.myapp",
                   "subs", continueToken);

このメソッドを呼び出すと、ユーザーが購入したすべてのアクティブな定期購入を含む Bundle が返されます。 定期購入登録が更新されずに期限切れになると、返される Bundle には表示されません。

アプリを保護する

アプリに正確なトランザクション情報を確実に送信するため、Google Play では注文に対するレスポンス データを含む JSON 文字列に署名します。 Google Play がこの署名作成に使うのは、Developer Console 内で自身のアプリに関連づけられたプライベートキーです。 Developer Console はそれぞれのアプリに対して RSA キーペアを生成します。

注: このキーペアの公開鍵を確認するには、Developer Console で自身のアプリの詳細を表示し、[Services & APIs] をクリックして [Your License Key for This Application] という表題のフィールドをご覧ください。

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

アプリは署名済みのレスポンスを受け取ると、RSA キーペアのうちの公開鍵を使って、署名を確認します。署名確認をすることで、不正変更またはなりすましのレスポンスを見破ることができます。 署名確認は自身のアプリ内でも実行できますが、アプリが保護されたリモート サーバーに接続している場合は、そのサーバー上で確認することを推奨します。

セキュリティとそのデザインに関するベスト プラクティスの詳細は、セキュリティとデザインをご覧ください。