2012年5月25日

【Android】月額課金の実装を翻訳してみた


前回のAndroidの月額課金について翻訳してみたに引き続き、
今回は実装方法の翻訳です。

まだ翻訳終わってないですが、随時更新していきます。

参考:Subscriptions | Android Developers

[以下、意訳]
誤訳がありましたらお教え願います。

定期購読の実装


定期購読は一種のアプリ内課金タイプです。もし開発者がすでに1度しか課金しないタイプのアプリ内課金を実装している場合、定期購読用のソースを追加するだけの最小限構成で変更可能です。これからアプリ内課金を実装する場合は、他のアプリ内課金商品と同様の通常の通信モデル、データ構造、UIで実装可能です。

詳しい解説はIn-app Billing Overviewを参照して下さい。この章では定期購読に関しての詳細をビジネスモデルに添って解説していきます。

サンプルアプリ


アプリ内課金と定期購読を実装するにあたり、In-app Billingのサンプルアプリが入手可能です。Android SDK ManagerのAndroid SDK repositoryからダウンロード出来ます。詳しくはDownloading the Sample Applicationを参照して下さい。

アプリモデル


定期購読を実装するアプリでは通常のアプリ内課金と同じモデル、つまり、購入リクエストをPlay Storeへ送信し、購入レスポンスをPlay Storeから非同期のBroadcast Intentで受け取ります。アプリ本体にGoogle PlayやAndroidプラットフォームとの通信を制御する機能はありません。

アプリは通常のアプリ内課金用コンポーネント(リクエストを送信する購入サービス、レスポンスを受け取るBroadcastReceiver、Google Payから送信されたレスポンスのセキュリティー認証など)を使用します。また、通知/エラー/ステータス/アプリへのコールバック送信用オブザーバを処理するハンドラの使用を推奨します。これらのコンポーネントと関連する動作はIn-app Billing Overviewと関連するドキュメントで説明されています。

Google Playと複数の購買通信を開始するには、通常のIn-app Billingと同様のリクエストと同様のレスポンス処理を実装して下さい。リクエストとレスポンスの中身に関しては、2つの新しいフィールドが用意されました。下記を参照して下さい。

購入トークン


定期購読の設計上の中心はこの購入トークンになります。購入トークンはユーザIDと定期購読IDに基づくユニークな文字列になっております。定期購読商品の購入が完了した(Google Walletで購入が完了した)段階でGoogle Playが購入トークンを発行し、In-app Billing APIを通して課金対象アプリに購入トークンを送信します。

PURCHASE_REQUEST(購入リクエスト)メッセージフローの最後に、GET_PURCHASE_INFORMATIONリクエストによって発行された購入トークンとその他、手続きの詳細をアプリ内で解読します。In-app Billingの呼び出しによって戻ってきたBundleにJSON配列オブジェクトが含まれています。定期購読の支払いに対応する為に、"purchaseToken"フィールドから購入トークンが入手可能です。

定期購読用JSONオブジェクトの例
{ "nonce"  : 1836535032137741465,
  "orders" :
    [{ "notificationId"   : "android.test.purchased",
       "orderId"          : "transactionId.android.test.purchased",
       "packageName"      : "com.example.dungeons",
       "productId"        : "android.test.purchased",
       "developerPayload" : "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
       "purchaseTime"     : 1290114783411,
       "purchaseState"    : 0,
       "purchaseToken"    : "rojeslcdyyiapnqcynkjyyjh" }]
}

購入トークンを受け取った後は、トークンをアプリ内に保存する事も出来ますし、サーバへ送信する事も出来ます。また、トークンは定期購読に関するステータスやキャンセル情報を開発者側から参照するためにも使用出来ます。もしトークンをアプリ内のみで保存する場合、Security and Designを読み、情報の難読化を行なって下さい。

In-app Billing APIのバージョンチェック


定期購読はGoogle Play 3.5(In-app Billing v2 API)以降でサポートされています。アプリ内では起動時にGoogle Playのバージョンチェックと、Google PlayがIn-app Billing v2APIと定期購読のサポートをしている事を確認して下さい。

上記のプロセスを行う為に、CHECK_BILLING_SUPPORTEDのBundleを作成、下記の要求されているkey-valueペアを含めて行なって下さい。

API_VERSION : 2
BILLING_REQUEST_ITEM_TYPE : "subs"

上記を含めたBundleを作成し、sendBillingRequest(Bundle)を使用しリクエストを送信しBundleを受け取ります。レスポンスはBILLING_RESPONSE_RESPONSE_CODEというキーで抽出出来ます。RESULT_OKが定期購読のサポートを意味しています

サンプルアプリでは、BILLING_REQUEST_ITEM_TYPEで受け取れる固定値を下記のように宣言しています。

// These are the types supported in the IAB v2
   public static final String ITEM_TYPE_INAPP = "inapp";
   public static final String ITEM_TYPE_SUBSCRIPTION = "subs";

簡単なリクエストBundle作成用のメソッドもサンプルアプリに含まれています。

protected Bundle makeRequestBundle(String method) {
  Bundle request = new Bundle();
  request.putString(Consts.BILLING_REQUEST_METHOD, method);
  request.putInt(Consts.BILLING_REQUEST_API_VERSION, 2);
  request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME, getPackageName());
  return request;
}

下記のメソッドが定期購読をサポートしているかどうか調べます。

/**
 * Wrapper class that checks if in-app billing is supported.
 */
class CheckBillingSupported extends BillingRequest {
  public String mProductType = null;
  public CheckBillingSupported() {
    // This object is never created as a side effect of starting this
    // service so we pass -1 as the startId to indicate that we should
    // not stop this service after executing this request.
    super(-1);
  }

  public CheckBillingSupported(String type) {
    super(-1);
    mProductType = type;
  }

  @Override
  protected long run() throws RemoteException {
    Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
    if (mProductType != null) {
      request.putString(Consts.BILLING_REQUEST_ITEM_TYPE, mProductType);
    }
    Bundle response = mService.sendBillingRequest(request);
    int responseCode = response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE);
    if (Consts.DEBUG) {
      Log.i(TAG, "CheckBillingSupported response code: " +
          ResponseCode.valueOf(responseCode));
    }
    boolean billingSupported = (responseCode == ResponseCode.RESULT_OK.ordinal());
    ResponseHandler.checkBillingSupportedResponse(billingSupported, mProductType);
    return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
  }
}

定期購読商品購入リクエスト


上記で説明したようなAPIのバージョン確認が終了し、定期購読がサポートされている事を確認しましたら、アプリから定期購読商品の購入フローへ進めます。ユーザが定期購読商品を選択し、購入フローを開始したら、アプリ内で通常のアプリ内課金のようにREQUEST_PURCHASEリクエストを送信し、購入フローの制御を行なって下さい。その後、アプリからGoogle Playの画面を起動し課金手続きを進めます。

REQUEST_PURCHASEはIn-app Billing Overviewで説明した商品の詳細を含むBundleが組み込まれています。定期購読商品では下記のキーが設定されています。

ITEM_ID : 定期購読の商品ID
ITEM_TYPE : "sub"(Google PlayはITEM_TYPEが無い場合、通常のアプリ内課金と認識します。)

Google PlayはRESPONSE_CODE、PURCHASE_INTENT、REQUEST_IDを含むBundleを返します。アプリからはPURCHASE_INTENTを使用し課金手続きの画面を立ち上げ、Messaging sequenceで説明されているメッセージフローに従い処理されます。

下記は定期購読商品の購入開始のサンプルです。mProductTypeはITEM_TYPE_SUBSCRIPTIONです。

/**
* Wrapper class that requests a purchase.
*/
class RequestPurchase extends BillingRequest {
  public final String mProductId;
  public final String mDeveloperPayload;
  public final String mProductType;

. . .

  @Override
  protected long run() throws RemoteException {
    Bundle request = makeRequestBundle("REQUEST_PURCHASE");
    request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);
    request.putString(Consts.BILLING_REQUEST_ITEM_TYPE, mProductType);
    // Note that the developer payload is optional.
    if (mDeveloperPayload != null) {
      request.putString(Consts.BILLING_REQUEST_DEVELOPER_PAYLOAD, mDeveloperPayload);
    }
    Bundle response = mService.sendBillingRequest(request);
    PendingIntent pendingIntent
        = response.getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
    if (pendingIntent == null) {
      Log.e(TAG, "Error with requestPurchase");
      return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
    }

    Intent intent = new Intent();
    ResponseHandler.buyPageIntentResponse(pendingIntent, intent);
    return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
    Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
  }

  @Override
  protected void responseCodeReceived(ResponseCode responseCode) {
    ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
  }
}

続き書いたよ...
Androidの月額課金の実装を翻訳してみた 2