2013年2月1日金曜日

Android アプリ内課金(API Version3)の実装

Implementing In-app Billing (IAB Version 3)
  • 1. プロジェクトに In-app Billing ライブラリを追加する
  • 2. AndroidManifest.xml を変更する
  • 3. ServiceConnection を作成し、IInAppBillingService にバインドする
  • 4. アプリから In-app Billing リクエストを IInAppBillingService に送る
  • 5. Google Play からの In-app Billing レスポンスを処理する


1. プロジェクトに In-app Billing ライブラリを追加する

TrivialDriva サンプルから IInAppBillingService.aidl ファイルを Android プロジェクトにコピーする。Eclipse なら /src ディレクトリにインポートする。

/gen ディレクトリ内に IInAppBillingService.java が作成されていることを確認する。

* サンプルの取得
1. Android SDK Manager で Google Play Billing Library をインストールする
2. [sdk-install-dir]/extras/google/play_billing/in-app-billing-v03/samples/TrivialDrive/ からプロジェクトをインポートする



2. AndroidManifest.xml を変更する <uses-permission android:name="com.android.vending.BILLING" /> を追加する



3. ServiceConnection を作成する
----- アプリ ---------------------
|                               |
| メイン機能 - ServiceConnection ---- Google Play
|                               |
---------------------------------

少なくとも以下の処理がアプリ内で必要
・IInAppBillingService にバインドする
・Google Play アプリに IPC で billing リクエストを送る
・各 billing リクエストごとに返ってくる同期レスポンスメッセージを処理する


・IInAppBillingService にバインドする

Google Play の In-app Billing サービスへの接続を確立するには、Activity を IInAppBillingService にバインドするための ServiceConnection を実装する。
onServiceDisconnectedonServiceConnected メソッドを Override し、接続が確立された後に 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); } };

Activity の onCreate メソッド内で、bindService メソッドを呼んでバインドする。メソッドには In-app Billing サービスを参照する Intent および ServiceConnection のインスタンスを渡す。
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE);

Activity が終了する場合、In-app Billing サービスからのアンバインドを忘れずに行う。
@Override public void onDestroy() { super.onDestroy(); if (mServiceConn != null) { unbindService(mServiceConn); } }

4. In-app Billing リクエストを作成する

アプリが Google Play に接続されたら、アプリ内課金アイテムのリクエストを開始することができる。 Google Play が支払用のインタフェースを作成するため、アプリが直接支払い取引を処理する必要はない。
アイテムが購入されると、Google Play はユーザーがそのアイテムの所有権を取得したと認識し、そのアイテムが消費されるまで同じプロダクトIDのアイテムが購入されるのを防止する。
アプリではアイテムがどのように消費されるかをコントロールすることができ、Google Play にそのアイテムが再度購入できるようになったことを通知できる。
また、ユーザーによって作成された購入リストを Google Play から素早く取得することができる。これは、例えば、ユーザーがアプリを起動したときにユーザーの購入リストをリストアしたときに便利。


・購入可能なアイテムを問い合わせる

In-app Billing Version 3 API を使って Google Play からアイテムの詳細を問い合わせることができる。In-app Billing サービスにリクエストを送るには、プロダクトIDの String ArrayList を作成し、それを "ITEM_ID_LIST" というキーで Bundle に保持する。
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 メソッドを呼び出す。第1引数には In-app Billing API version の "3"、第2引数には呼び出しアプリのパッケージ名、第3引数には購入タイプの "inapp"、第4引数には作成した Bundle を渡す。
Bundle skuDetails = mService.getSkuDetails(3, getPackageName(), "inapp", querySkus);

リクエストが成功した場合、返ってきた Bundle には RESPONSE_CODE というキーで BILLING_RESPONSE_RESULT_OK (0) が含まれる。
その他の Response Code には以下のものがある
  • BILLING_RESPONSE_RESULT_OK 0
    Success

  • BILLING_RESPONSE_RESULT_USER_CANCELED 1
    User pressed back or canceled a dialog

  • BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE 3
    Billing API version is not supported for the type requested

  • BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE 4
    Requested product is not available for purchase

  • BILLING_RESPONSE_RESULT_DEVELOPER_ERROR 5
    Invalid arguments provided to the API. This error can also indicate that the application was not correctly signed or properly set up for In-app Billing in Google Play, or does not have the necessary permissions in its manifest

  • BILLING_RESPONSE_RESULT_ERROR 6
    Fatal error during the API action

  • BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED 7
    Failure to purchase since item is already owned

  • BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED 8
    Failure to consume since item is not owned


getSkuDetails メソッドをメインスレッドから呼ばないこと。このメソッドはネットワークリクエストのトリガーになり、メインスレッドをブロックする。

問い合わせた結果は、"DETAIL_LIST" というキーで String ArrayList に格納され、各購入情報は JSON 形式の文字列で格納される。
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; } } JSON fields には以下のキーがある
  • productId
    The product ID for the product.

  • type
    Value must be “inapp” for an in-app purchase type.

  • price
    Formatted price of the item, including its currency sign. The price does not include tax.

  • title
    Title of the product.

  • description
    Description of the product.


・アイテムを購入する

アプリから購入リクエストを開始するには、In-app Billing Service の getBuyIntent メソッドを呼び出す。 第1引数には In-app Billing API version の "3"、第2引数には呼び出しアプリのパッケージ名、第3引数には product ID、第4引数には購入タイプの "inapp"、第4引数には developerPayload 文字列を渡す。 developerPayload 文字列は、購入情報として Google Play から返してほしい付加的な引数を指定するのに使う。
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");

リクエストが成功した場合、返ってきた Bundle には RESPONSE_CODE というキーで BILLING_RESPONSE_RESULT_OK (0) が含まれる。また、"BUY_INTENT" というキーで取得できる PendingIntent で購入フローを開始できる。
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");

購入処理を完了するには、取得した pendingIntent で startIntentSenderForResult メソッドを呼び出す。
startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));

Google Play は PendingIntent のレスポンスをアプリの onActivityResult に返す。 onActivityResult メソッドは Activity.RESULT_OK (1) もしくは Activity.RESULT_CANCELED (0) を resultCode として持つ。
  • RESPONSE_CODE
    0 if the purchase was success, error otherwise.

  • INAPP_PURCHASE_DATA
    A String in JSON format that contains details about the purchase order. See table 4 for a description of the JSON fields.

  • INAPP_DATA_SIGNATURE
    String containing the signature of the purchase data that was signed with the private key of the developer.


購入データは JSON 形式の文字列として Intent に格納されており、"INAPP_PURCHASE_DATA" というキーで取得できる。

例えば、次のようなデータが格納されている
'{ "orderId":"12999763169054705758.1371079406387615", "packageName":"com.example.app", "productId":"exampleSku", "purchaseTime":1345678900000, "purchaseState":0, "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ", "purchaseToken":"rojeslcdyyiapnqcynkjyyjh" }'
  • orderId
    A unique order identifier for the transaction. This corresponds to the Google Wallet Order ID.

  • packageName
    The application package from which the purchase originated.

  • productId
    The item's product identifier. Every item has a product ID, which you must specify in the application's product list on the Google Play publisher site.

  • purchaseTime
    The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970).

  • purchaseState
    The purchase state of the order. Possible values are 0 (purchased), 1 (canceled), or 2 (refunded).

  • developerPayload
    A developer-specified string that contains supplemental information about an order. You can specify a value for this field when you make a getBuyIntent request.

  • purchaseToken
    A token that uniquely identifies a purchase for a given item and user pair.


@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(); } } } } Security Recommendation : 購入リクエストを送るときに、この購入リクエストをユニークに識別する文字列トークンを作成し、developerPayload に含ませる。ランダムに生成した文字列をこのトークンに利用できる。Google Play からレスポンスを受けとった際に、orderId および developerPayload の文字列をチェックする。

セキュリティをさらに追加するには、自身のセキュアサーバー上でもチェックする。orderId がユニークな値で、以前に処理したことがないことを確認し、developerPayload の文字列が以前に送った購入リクエストのものと一致するかチェックする。


・購入したアイテムを問い合わせる

ユーザーによる購入情報を取得するには、In-app Billing Version 3 サービスの getPurchases メソッドを呼び出す。第1引数には In-app Billing API version の "3"、第2引数には呼び出しアプリのパッケージ名、第3引数には購入タイプの "inapp" を渡す。
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);

Google Play サービスは、デバイスに現在ログインしているユーザーアカウントの購入についてのみ返す。 リクエストが成功した場合、返ってきた Bundle には RESPONSE_CODE というキーで BILLING_RESPONSE_RESULT_OK (0) が含まれる。また、"INAPP_PURCHASE_ITEM_LIST" というキーで product ID のリストが、"INAPP_PURCHASE_DATA_LIST" というキーでオーダー詳細のリストが、"INAPP_DATA_SIGNATURE" というキーで各購入のシグネチャのリストが含まれる。

パフォーマンス改善のために、getPurchase が最初に呼ばれたとき、In-app Billing サービスは700個のプロダクトまでしか返さない。
ユーザーが大量のプロダクトを持っている場合、Google Play は "INAPP_CONTINUATION_TOKEN" というキーに文字トークンを割り当て、さらに取得できるプロダクトがあることを示す。引数としてこのトークンを渡す事でアプリは続きの getPurcases を呼び出す事ができる。Google Play はユーザーのすべてのプロダクトがアプリに送られるまでレスポンスの Bundle に続きのトークンを含めて返す。
nt response = ownedItems.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); ArrayList purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE"); 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 }
  • RESPONSE_CODE
    0 if the request was successful, error otherwise.

  • INAPP_PURCHASE_ITEM_LIST
    StringArrayList containing the list of productIds of purchases from this app.

  • INAPP_PURCHASE_DATA_LIST
    StringArrayList containing the details for purchases from this app. See table 4 for the list of detail information stored in each INAPP_PURCHASE_DATA item in the list.

  • INAPP_DATA_SIGNATURE_LIST
    StringArrayList containing the signatures of purchases from this app.

  • INAPP_CONTINUATION_TOKEN
    String containing a continuation token to retrieve the next set of in-app products owned by the user. This is only set by the Google Play service if the number of products owned by the user is very large. When a continuation token is present in the response, you must make another call to getPurchases and pass in the continuation token that you received. The subsequent getPurchases call returns more purchases and possibly another continuation token.


・購入アイテムの消費

In-app Billing Version 3 API を使って、Google Play の購入されたアイテムの所有権をトラックできる。一度アイテムが購入されると、その所有権があると考えられ Google Play から購入できなくなる。 Google Play が再びアイテムを購入可能にする前に、アイテムの消費リクエストを送らなければならない。
全ての managed な in-app products は消費可能。
消費メカニズムをどう利用するかは開発者次第。
典型的には、ユーザーが複数回購入したいような一時的な利益があるプロダクト(例えばゲーム内通貨や装備)は消費可能な実装にする。一度だけ購入したり、永続的な効果を提供するプロダクト(例えばプレミアムアップグレード)は消費しないように実装する。

購入アイテムの消費を記録するには、In-app Billing service の consumePurchase メソッドを呼び出す。第1引数には In-app Billing API version の "3"、第2引数には呼び出しアプリのパッケージ名、第3引数には purchaseToken 文字列を渡す。purchaseToken は成功した購入リクエストで Google Play サービスから返される "INAPP_PURCHASE_DATA" 文字列の一部として含まれる。
int response = mService.consumePurchase(3, getPackageName(), token);

Warinig: consumePurchase はメインスレッドから呼び出さない事。このメソッドはネットワークリクエストのトリガーになり、メインスレッドをブロックする。

購入した in-app product がユーザーにどのように提供されるかをコントロールしトラックするかは開発者の責任である。例えば、ゲーム内通貨をユーザーが購入した場合、購入した通貨の量に応じてプレイヤーの状態を変えなければならない。

Security Recommendation: ユーザーにアプリ内課金を消費する利益をプロビジョニングする前に消費リクエストを送らなければならない。アイテムをプロビジョンする前に Google Play から成功した消費レスポンスを受けとっていることを確認する。



---------------------

Version 2 に比べてかなりシンプルになっています。

サンプルアプリのコードをを見るのが一番いいです。

まだ Subscription は Version 3 で対応していない(coming soon とは書いてある)ので、早くきてほしい!



0 件のコメント:

コメントを投稿