2013年1月23日水曜日

アプリ内課金を実装する

参考:http://developer.android.com/google/play/billing/billing_integrate.html

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

android-sdks/extras/google/play_billing/in-app-billing-v03 にある IInAppBillingService.aidl ファイルをcom.android.vending.billingパッケージを作成してその中にコピーする。
するとEclipseが勝手に.javaファイルを生成する。

AndroidManifest.xmlを更新する

アプリ内課金のAPIを呼び出すためには、パーミッション設定が必要。
<uses-permission android:name="com.android.vending.BILLING" />

ServiceConnectionを作成する

アプリケーションはServiceConnectionを持っていなければいけない。最低限、以下のことをしなければいけない。
  • IInAppBillingServiceに接続する
  • 支払い要求を(IPCメソッド呼び出しとして) Google Playアプリケーションに実行する
  • 個々の支払いリクエストで戻される同期的なレスポンスメッセージを処理する

IInAppBillingServiceに接続する

Google Playのアプリ内課金サービスに接続するためには、ServiceConnectionを実装してActivityをIInAppBillingServiceに接続する。
onServiceDisconnectedonServiceConnectedメソッドをオーバーライドして、接続が確立できた後にIInAppBillingServiceインスタンスの参照を得る(下記はActivityのフィールド部分の抜粋)。
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メソッドを呼ぶことでアプリ内課金サービスに接続する。bindServiceにアプリ内課金サービスを参照する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);
これで、mServiceを使ってGoogle Playの課金サービスに接続できるようになった。
重要: Activitを終了するときは、サービスから切断すること。これをしないと、端末のパフォーマンス低下につながる。ActivityのonDestroyメソッドで切断するには下記のようにする。
@Override
public void onDestroy() {
    super.onDestroy();
    if (mServiceConn != null) {
        unbindService(mServiceConn);
    } 
}

アプリ内課金リクエストを送る

アプリケーションがGoogle Playに接続できるようになったら、アプリ内課金商品の購入リクエストを送れるようになる。アプリケーション内で購入処理を直接処理する必要はない。
アイテムが購入されると、Google Playはユーザーがアイテムを所有していることを認識し、ユーザーが同じプロダクトIDのアイテムを購入することができないようにする(アイテムが消費されるまで)。
アプリケーション内でアイテムの消費をコントロールし、Google Playにもう一度購入可能とするよう通知することができる。
ユーザーの購入したものをGoogle Playに問い合わせることもできる。これは、ユーザーがアプリケーションを立ち上げたときに購入したアイテムを復元するのに便利だ。

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

アプリケーション内で購入可能なアイテムの詳細を問い合わせることができる。
まずプロダクトIDの入ったArrayList<String>を作成して、それを"ITEM_ID_LIST"キーでBundleに入れる。
ArrayList skuList = new ArrayList();
skuList.add("premiumUpgrade");
skuList.add("gas");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);
アイテムの情報をGoogle Playから取得するには、getSkuDetailsメソッドを呼び出す。引数はAPIのバージョン(3)、アプリケーションのパッケージ名、購入形態("inapp")、それから先ほどつBundleの4個。
Bundle skuDetails = mService.getSkuDetails(3, 
   getPackageName(), "inapp", querySkus);
リクエストが成功したら、戻り値のBundleにレスポンスコードの BILLING_RESPONSE_RESULT_OK (0) が含まれている。
警告: getSkuDetails メソッドをメインスレッド(UIスレッド)で呼んではいけない。メソッド内ではネットワークに接続するため、メインスレッドで呼び出すと画面が固まってしまう。代わりに、別のスレッドを作成してそのスレッド内で getSkuDetailsメソッドを呼ぶこと。
クエリ結果はArrayList<String>としてDETAILS_LISTキーの値として含まれている。各StringはJSONフォーマットの文字列。この文字列については、In-app Billing Referenceを参照。

この例では、先ほどの例で戻り値として受け取ったBundleから価格情報を取り出している。
int response = skuDetails.getInt("RESPONSE_CODE");
if (response == 0) {
   ArrayList 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メソッドを呼び出す。引数はAPIバージョン(3)、アプリのパッケージ名、購入したいアイテムのプロダクトID、購入タイプ("inapp")、そしてdeveloperPayload文字列の5個。
developerPayload文字列は購入情報を送り返すために使われる文字列。
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
   sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
リクエストが成功したら、戻り値のBundleにレスポンスコード BILLING_RESPONSE_RESULT_OK (0) と購入フローを開始するためのPendingIntentが渡される。その他のレスポンスコードはIn-app Billing Referenceを参照。次に、BundleからBUY_INTENTキーの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));
レスポンスはonActivityResultメソッドで受け取る。onActivityResultメソッドは結果コードActivity.RESULT_OK (1) もしくはActivity.RESULT_CANCELED (0) が入る。結果の詳細はIn-app Billing Referenceを参照。
購入データはレスポンスIntentのINAPP_PURCHASE_DATAキーの値にJSON文字列で含まれている。
例:
'{ 
   "orderId":"12999763169054705758.1371079406387615", 
   "packageName":"com.example.app",
   "productId":"exampleSku",
   "purchaseTime":1345678900000,
   "purchaseState":0,
   "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
   "purchaseToken":"rojeslcdyyiapnqcynkjyyjh"
 }'
前の例から続いて、レスポンスコード、購入データ、署名をレスポンス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から受け取ったら、必ずデータの署名(orderIddeveloperPayload)を確認すること。加えて、あなた自身のセキュアなサーバーを使ってorderIdが確実に以前使われていないユニークな値であることを確認し、developerPayloadが先の購入リクエストに使用したものと一致することを確認すること。

購入したアイテムの問い合わせ

アプリ内で行った購入の情報を取り出すには、アプリ内課金サービスのgetPurchasesメソッドを呼び出す。引数はAPIバージョン(3)、パッケージ名、購入タイプ("inapp")の3個。
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
Google Playサービスは現在端末でログインしているユーザーアカウントが行った購入しか戻さない。リクエストが成功すると、戻り値のBundleはレスポンスコード0を持つ。戻り値のBundleはプロダクトIDのリスト、それぞれの購入の詳細のリスト、それぞれの購入の署名も持っている。
int 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
}

購入を消費する

APIバージョン3ではGoogle Playで購入したアイテムの所有状態を管理できる。一度アイテムが購入されると「所有」状態になり、Google Playから買えなくなる。Google Playから再び買えるようにするためには、消費リクエストを送る必要がある。全てのアプリ内課金アイテムは消費可能。どのようにしてアイテムを消費させるかはあなたの自由。通常はアイテムを一時的なベネフィットとして利用(例えばゲーム内通貨や装備品など)。一度買ったら永久に所有できるものの場合は、消費の実装をする必要はない(例えばプレミアムアップグレード等)。

購入の消費を記録するため、アプリ内課金サービスのconsumePurchaseメソッドを呼び出し、purchaseToken文字列を渡す。これは削除されるべき購入を識別する。
int response = mService.consumePurchase(3, getPackageName(), token);
警告: consumePurchase メソッドをメインスレッド(UIスレッド)内で呼んではいけない。
アイテムが購入された後、それをどのようにコントロール・追跡するかはあなたの責任。例えば、ユーザーがゲーム内通貨を購入したら、あなたは購入した金額でプレイヤーの所持金を更新しなければいけない。
セキュリティ的な推奨: アプリ内に反映させる前に消費リクエストを送らなければいけない。消費レスポンスが成功だと受け取って確実にするyou have received a successful consumption response from Google Play before you provision the item.

0 件のコメント:

コメントを投稿