連載
» 2011年05月13日 00時00分 公開

Androidアプリにアプリ内課金を実装してみようAndroid Marketアプリ内課金サービス徹底解説(2)(2/4 ページ)

[緒方聡,株式会社イーフロー]

ステップ4:ローカルサービスの作成

 アプリはアプリとAndroid Market間の通信を容易にするためにローカルサービスを持つ必要があります。以下は、その最小構成です。

  • MarketBillingServiceに接続
  • (プロセス間通信のメソッドを呼び出して)課金リクエストをAndroid Marketアプリに送信
  • それぞれの課金リクエストに対して直ちに戻されるレスポンスをハンドリング

MarketBillingServiceに接続

 IMarketBillingServide.aidlファイルをプロジェクトに追加しているなら、MarketBillingServiceへの接続は比較的簡単です。以下のサンプルコードはMarketBillingServiceにサービスを接続するbindService()メソッドの使い方を示します。このコードはアプリのサービスのonCreate()メソッドに記述するものです。


try {
    boolean bindResult = mContext.bindService(
        new Intent("com.android.vending.billing.MarketBillingService.BIND"), this, Context.BIND_AUTO_CREATE);
    if (bindResult) {
        Log.i(TAG, "Service bind successful.");
    } else {
        Log.e(TAG, "Could not bind to the MarketBillingService.");
    }
} catch (SecurityException e) {
    Log.e(TAG, "Security exception: " + e);
}

 サービスに接続した後、IMarketBillingServiceインターフェイスの参照を生成する必要があります。その後プロセス間通信メソッドを呼び出して課金リクエストを作成できます。以下のコードはonServiceConnected()コールバックメソッドの使い方です。


    /**
     * The Android system calls this when we are connected to the MarketBillingService.
     */
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "MarketBillingService connected.");
        mService = IMarketBillingService.Stub.asInterface(service);
    }

 これでmServiceが初期化されたため、sendBillingRequest()メソッドを実行できます。

 MarketBillingServiceへの接続を行うサービスの完全な実装は、サンプルアプリのBillingServiceクラスを参照してください。

MarketBillingServiceに課金リクエストを送信

 Serviceは、すでにIMarketBillingServiceインターフェイスの参照を持っているので、MarketBillingServiceに(プロセス間通信経由で)課金リクエストを送信するために、その参照を使うことが可能です。

 MarketBillingServiceプロセス間通信インターフェイスは1つのpublicメソッド(sendBillingRequest())を公開しており、それは1つのBundleを引数に取ります。このBundleはさまざまなキーと値のペアを使用し、実行したいリクエストタイプを指定する方法を提供します。

 例えば、アプリのパッケージ名、購入される商品、アプリのIDなどです。sendBillingRequest()メソッドはレスポンスコードを含むBundleを直ちに返します。しかし、これは完全な購入レスポンスではありません。完全なレスポンスは非同期のブロードキャストインテントで通知されます。

 MarketBillingServiceでサポートされる、さまざまなバンドルのキーに関する詳細な情報は、後述するリファレンスの「アプリ内課金サービスインターフェイス」を参照してください。

 sendBillingRequest()メソッドを使用すると、前回の「アプリ内課金のメッセージ処理」で紹介した以下の5つのタイプの課金リクエストを送信可能です。5つのリクエストタイプはBILLING_REQUESTというバンドルキーを使用して指定されます(下記リストはインデックスになっています)

  1. CHECK_BILLING_SUPPORTED:Android Marketアプリがアプリ内課金をサポートするか検証
  2. REQUEST_PURCHASE:アプリ内アイテムの購入リクエストを送信
  3. GET_PURCHASE_INFORMATION:購入または払い戻しのトランザクション情報を受信
  4. CONFIRM_NOTIFICATIONS:購入または払い戻しのトランザクション情報を受信したことを通知
  5. RESTORE_TRANSACTIONS:管理された購入のユーザートランザクション履歴を受信

 これらの課金リクエストのいずれかを行うためには最初に、BILLING_REQUEST、API_VERSION、PACKAGE_NAMEの3つのキーを含むBundleインスタンスを作成する必要があります。サンプルアプリでは、makeRequestBundle()という補助メソッドで、これを行います。


    protected Bundle makeRequestBundle(String method) {
        Bundle request = new Bundle();
        request.putString(BILLING_REQUEST, method);
        request.putInt(API_VERSION, 1);
        request.putString(PACKAGE_NAME, getPackageName());
        return request;
    }

 この補助メソッドを使用するために、5つの課金リクエストのうちの1つと一致する文字列を渡します。メソッドは必要な3つのキーが定義されたバンドルを返します。以降では、課金リクエストを送信するときの補助メソッドの使用方法を示します。

 なお、すべてのアプリ内課金リクエストはアプリのメインスレッドで作成する必要があるので、注意してください。

  アプリ内課金サポート有無の検証(CHECK_BILLING_SUPPORTED)

 以下のサンプルコードは、Android Marketアプリがアプリ内課金をサポートしているかを検証する方法を示しています。サンプル内で、mServiceはMarketBillingServiceインターフェイスのインスタンスです。


    /**
    * Request type is CHECK_BILLING_SUPPORTED
    */
    Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
    Bundle response = mService.sendBillingRequest(request);
    // Do something with this response.

 リクエストは直ちにRESPONSE_CODEキーのみを含むバンドルレスポンスを返します。RESPONSE_CODEキーは以下の値を持ちます。

  • RESULT_OK:アプリ内課金はサポートされる
  • RESULT_BILLING_UNAVAILABLE:アプリの指定したAPIバージョンが不正であるか、ユーザーがアプリ内購入を使用できない
  • RESULT_ERROR:Android Marketアプリで接続エラーが発生した
  • RESULT_DEVELOPER_ERROR:アプリはアプリ内課金リクエストの作成を試みたが、アプリのマニフェストに「com.android.vending.BILLING」権限が定義されていない。またはアプリが署名されていないか、誤りのあるリクエストを送信した
  • CHECK_BILLING_SUPPORTED:リクエストは非同期レスポンス(ブロードキャストインテント)を返さない

 CHECK_BILLING_SUPPORTEDのリクエストはRemoteExceptionブロック内で実行することを推奨します。RemoteExceptionの発生はリモートメソッドの呼び出しに失敗したこと、つまりAndroid Marketアプリが古いためアップデートが必要であることを示します。このケースでは、「Android マーケットのアップデート」のヘルプトピックのようなエラーメッセージがユーザーに表示されます。

 サンプルアプリは、このエラー状態をハンドリングする方法をデモします(Dungeons.javaのDIALOG_CANNTO_CONNECT_IDを参照してください)。

  購入リクエストの送信(REQUEST_PURCHASE)

 購入リクエストを生成するには以下のように行う必要があります(下記リストはインデックスになっています)

  1. REQUEST_PURCHASEリクエストを送信
  2. Android Marketアプリから戻されたPendingIntentを起動
  3. Android Marketアプリから送信されるブロードキャストインテントをハンドリング
  • 【1】リクエスト生成

 リクエストバンドルで4つのキーを指定する必要があります。以下のサンプルコードは、それらのキーのセットとアプリ内アイテムの購入リクエスト作成方法を示します。サンプル内のmProductIdは(アプリの商品リストに登録済みの)アプリ内商品のAndroid Market商品IDで、mServiceはMarketBillingServiceインターフェイスのインスタンスです。


    /**
     * Request type is REQUEST_PURCHASE
     */
    Bundle request = makeRequestBundle("REQUEST_PURCHASE");
    request.putString(ITEM_ID, mProductId);
    // Note that the developer payload is optional.
    if (mDeveloperPayload != null) {
        request.putString(DEVELOPER_PAYLOAD, mDeveloperPayload);
        Bundle response = mService.sendBillingRequest(request);
        // Do something with this response.
    }

 リクエストは直ちにRESPONSE_CODEキー、PURCHASE_INTENTキー、REQUEST_IDキーの3つを含むバンドルでレスポンスを返します。RESPONSE_CODEキーはリクエストの状態を、REQUEST_IDキーはリクエストのためのユニークなリクエストIDを提供します。PURCHASE_INTENTキーはチェックアウトのUIを起動するためのPendingIntentを提供します。

  • 【2】ペンティングインテント(PendingIntent)の起動

 どのようにペンディングインテントを使用するかは、実機上で動作しているAndroidのバージョンに依存します。

 Android 1.6では、ペンディングインテントをアプリのアクティビティスタックの代わりに独立したタスクとしてチェックアウトのUIを起動するように使用する必要があります。Android 2.0以上では、ペンティングインテントをアプリのアクティビティスタック上でチェックアウトのUIを起動するために使用できます。

 以下はAndroid 1.6と2.0の両方に対応したコードです。このコードはサンプルアプリのPurchaseObserver.java内に含まれます。


    void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
        if (mStartIntentSender != null) {
            // This is on Android 2.0 and beyond.  The in-app checkout page activity
            // will be on the activity stack of the application.
            try {
                // This implements the method call:
                // mActivity.startIntentSender(pendingIntent.getIntentSender(),
                //     intent, 0, 0, 0);
                mStartIntentSenderArgs[0] = pendingIntent.getIntentSender();
                mStartIntentSenderArgs[1] = intent;
                mStartIntentSenderArgs[2] = Integer.valueOf(0);
                mStartIntentSenderArgs[3] = Integer.valueOf(0);
                mStartIntentSenderArgs[4] = Integer.valueOf(0);
                mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs);
            } catch (Exception e) {
                Log.e(TAG, "error starting activity", e);
            }
        } else {
            // This is on Android 1.6. The in-app checkout page activity will be on its
            // own separate activity stack instead of on the activity stack of
            // the application.
            try {
                pendingIntent.send(mActivity, 0 /* code */, intent);
            } catch (CanceledException e) {
                Log.e(TAG, "error starting activity", e);
            }
        }
    }

 ペンティングインテントはアプリケーションのコンテキストではなくアクティビティのコンテキストで起動しなければいけません。また、singleTopモードでペンティングインテントは起動できません。

 もしそれらを行った場合、Androidシステムはペンティングインテントをアプリのプロセスにアタッチせず、代わりに、Android Marketがフォアグラウンドに現れ、アプリは期待通りに動作しないでしょう。

  • 【3】ブロードキャストインテントのハンドリング

 REQUEST_PURCHASEリクエストはまた、2つの非同期レスポンス(ブロードキャストインテント)を発生させます。

 最初は、Android Marketアプリが送信するリクエストについてのエラー情報を提供するRESPONSE_CODEブロードキャストインテントです。次にリクエストが成功した場合は、Android MarketアプリはIN_APP_NOTIFYブロードキャストインテントを送信します。このメッセージはREQUEST_PURCHASEリクエストのトランザクション詳細を受信するために使用する通知IDを含みます。

 またAndroid Marketアプリは、IN_APP_NOTIFYを払い戻しのために送信することを心に留めておいてください。詳細は、前回の「IN_APP_NOTIFYメッセージのハンドリング」を参照してください。

  購入または払い戻しのトランザクション情報を受信(GET_PURCHASE_INFORMATION)

 IN_APP_NOTIFYブロードキャストインテントに応答することでトランザクション情報を受信します。IN_APP_NOTIFYメッセージはトランザクション情報を受信するために使用できる通知IDを含みます。

 購入または払い戻しのためのトランザクション情報を受信するには、5つのキーをリクエストバンドルで指定しなければなりません。以下のサンプルコードは、それらのキーのセットとリクエストの作成方法を示します。


    /**
     * Request type is GET_PURCHASE_INFORMATION
     */
    Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
    request.putLong(REQUEST_NONCE, mNonce);
    request.putStringArray(NOTIFY_IDS, mNotifyIds);
    Bundle response = mService.sendBillingRequest(request);
    // Do something with this response.

 必須の3つのキー以外の追加のキーは、sendBillingRequest()メソッドが実行される前に追加されます。REQUEST_NONCEキーはアプリが生成しなければならない、暗号上安全なナンス(一度だけ使用されるランダムな数値)を含みます。

 Android Marketアプリは、このナンスをPURCHASE_STATE_CHANGEブロードキャストインテントで返すため、トランザクション情報の完全性の検証が可能になります。NOTIFY_IDSキーはIN_APP_NOTIFYブロードキャストインテントで受信した通知IDの配列を含みます。

 リクエストはRESPONSE_CODEとREQUEST_IDの2つのキーを含むバンドルレスポンスを直ちに返します。RESPONSE_CODEキーはリクエストの状態を、REQUEST_IDキーはリクエストのためのユニークなリクエストIDを提供します。

 またGET_PURCHASE_INFORMATIONリクエストは、2つの非同期レスポンス(ブロードバンドインテント)を引き起こします。最初は、Android Marketアプリが送信するリクエストの状態とエラー情報を提供するRESPONSE_CODEブロードバンドインテントです。次は、もしリクエストが成功した場合、Android MarketアプリはPURCHASE_STATE_CHANGEDブロードバンドインテントを送信します。

 このメッセージは詳細なトランザクション情報を含みます。トランザクション情報は署名された(暗号化されていない)JSON文字列を含みます。メッセージはシグネチャを含み、署名された文字列の完全性を検証可能です。

  購入または払い戻しのトランザクション情報受信通知(CONFIRM_NOTIFICATIONS)

 トランザクション情報を受信した応答を行うには、CONFIRM_NOTIFICATIONSリクエストを送信します。リクエストバンドルに4つのキーを指定しなければなりません。以下のサンプルコードはそれらのキーのセットとリクエスト作成方法を示します。


    /**
     * Request type is CONFIRM_NOTIFICATIONS
     */
    Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
    request.putStringArray(NOTIFY_IDS, mNotifyIds);
    Bundle response = mService.sendBillingRequest(request);
    // Do something with this response.

 必須の3つのキー以外の追加のNOTIFY_IDSキーは、sendBillingRequest()が実行される前にバンドルに追加されます。NOTIFY_IDSキーはIN_APP_NOTIFYブロードキャストインテントで受信した通知IDの配列を含み、またGET_PURCHASE_INFORMATIONリクエストで使用されます。

 リクエストはRESPONSE_CODEとREQUEST_IDの2つのキーを含むバンドルレスポンスを直ちに返します。RESPONSE_CODEキーはリクエストの状態を、REQUEST_IDキーはリクエストのためのユニークなリクエストIDを提供します。

 CONFIRM_NOTIFICATIONSリクエストはRESPONSE_CODEブロードキャストインテントを非同期で引き起こします。このブロードキャストインテントはリクエストについての状態とエラー情報を提供します。

 最良の手法として、CONFIRM_NOTIFICATIONSリクエストはユーザーに商品を配信するまで送信しないことです。この方法は、もしアプリがクラッシュやその他の障害で商品の配信ができなかったとしても、アプリは引き続き商品の配信に必要なAndroid MarketからのIN_APP_NOTIFYブロードキャストインテントを繰り返し受信するためです。

  管理された購入のユーザートランザクション履歴受信(RESTORE_TRANSACTIONS)

 ユーザーのトランザクション情報の修復をするために、RESTORE_TRANSACTIONSリクエストを送信します。リクエストバンドルで4つのキーを指定しなければいけません。以下のサンプルコードは、それらのキーのセットとリクエストの作成方法を示しています。


    /**
     * Request type is RESTORE_TRANSACTIONS
     */
    Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
    request.putLong(REQUEST_NONCE, mNonce);
    Bundle response = mService.sendBillingRequest(request);
    // Do something with this response.

 必須の3つのキー以外のREQUEST_NONCEキーは、sendBillingRequest()メソッドの実行前にバンドルに追加されます。REQUEST_NONCEキーはアプリが作成しなければならないナンスを含みます。Android Marketアプリは、このナンスをPURCHASE_STATE_CHANGEブロードキャストインテントで返すため、トランザクション情報の完全性の検証が可能になります。

 リクエストはRESPONSE_CODEとREQUEST_IDの2つのキーを含むバンドルレスポンスを直ちに返します。RESPONSE_CODEキーはリクエストの状態を、REQUEST_IDキーはリクエストのためのユニークなリクエストIDを提供します。

 RESTORE_TRANSACTIONSリクエストはまた、2つの非同期レスポンス(ブロードバンドインテント)を引き起こします。

 最初は、Android Marketアプリが送信するリクエストの状態とエラー情報を提供するRESPONSE_CODEブロードバンドインテントです。次は、もしリクエストが成功した場合、Android Marketアプリが送信するPURCHASE_STATE_CHANGEDブロードバンドインテントです。このメッセージは詳細なトランザクション情報を含みます。

 トランザクション情報は署名された(暗号化されていない)JSON文字列を含みます。メッセージはシグネチャを含み、署名された文字列の完全性を検証可能です。

その他サービスタスク

 BroadcastReceiverからのメッセージを集中して受信するために、サービスを用意してもいいでしょう。サービスはAndroid MarketアプリからBroadcastReceiverへ非同期に送られる、それらの情報を取りまとめられます。

 これらのインテントを送受信する方法については、サンプルアプリのBillingReceiver.javaとBillingService.javaを参照してください。開発者は自身の実装のベースとして、これらのサンプルを使えます。サンプルアプリのコードを流用するのであれば、次回の「設計・実装における8つのセキュリティ対策ポイント」に従ってください。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。