Androidのアプリ開発関連のブログ

アプリ内購入の実装

[テスト用アカウントの作成]
いきなりですが、アプリ内購入の実装においてはプログラム以上にアカウントの準備が難題となります。
開発用のアカウントと別に個人用のGoogleアカウントを所有している場合などは問題になりませんが、
開発用アカウントでは 「出版社はこのアイテムを購入できません」と言われて購入テストが行えません。
開発用アカウントとは別にテスト用にGoogleアカウントを用意し、カード登録までする必要があります。
端末は実機を用意するのは難しいと思いますが、Android-x86 を使うのが良いと思います。
カード登録は必要ですが、Developer Consoleのアカウント設定からテスト用に権限を与えたアカウントでは決済ダイアログにテストと表示され決済は行われません。

[Google Play Billing Libraryをインストール]
Android SDKからGoogle Play Billing Libraryをインストール。
環境によるが、/opt/android-sdk/extras/google/play_billing とかにインストールされている。
アプリ内購入を導入するプロジェクトに /src/com/android/vending/billing ディレクトリを作成して IInAppBillingService.aidl をコピー。
別のプロジェクトでも共用できるようにlibプロジェクトを作成してそこに置くのが良いと思います。

[権限を追加]
AndroidManifest.xmlに以下の権限を追加。
<uses-permission android:name="com.android.vending.BILLING" />

[ビルドしてアップロード]
アイテムの作成は Google Play Developer Console から行えますが、
"com.android.vending.BILLING" の権限を追加したapkが配信されていないとアイテムの作成が行えません。
製品版ではなく、ベータ版かアルファ版での配信で問題ありません。

[Google ペイメント販売アカウントの作成]
アイテムの作成には「Google ペイメント販売アカウント」が必要なので、「Google Play Developer Console > アプリ内アイテム」から作成する。
住所,電話番号,ウェブサイトなどの情報の入力が必要。

[アイテムの作成]
Google Play Developer Console > アプリ内アイテム
からアイテムの作成ができます。
[管理対象のアイテム][定期購入]の2種類がありますが、通常のアイテムは[管理対象のアイテム]です。
アイテムIDや説明、価格などを設定し、[有効]にすると有効になります。
アイテムは1度購入すると2回目の購入はできませんが、購入済みアイテムを消費させることが可能です。(消費すると再購入が可能)

[import]
import com.android.vending.billing.IInAppBillingService;
import android.content.ServiceConnection;
import android.content.ComponentName;
import android.os.IBinder;
import android.app.PendingIntent;
上記5つが必要そう。1個目のは IInAppBillingService.aidl で導入できる。
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.lang.Thread;
import android.os.Handler;
import java.lang.Runnable;
import org.json.JSONObject;
↑この辺りも使うと思います。

[変数定義]
IInAppBillingServiceとServiceConnection
onCreate外でも参照できるようにclass変数として上記2つを定義する必要がある。
    IInAppBillingService mService;
    ServiceConnection mServiceConn=new ServiceConnection(){
        public void onServiceDisconnected(ComponentName name){
            mService=null;
        }
        public void onServiceConnected(ComponentName name,IBinder service){
            mService=IInAppBillingService.Stub.asInterface(service);
        }
    };
サンプルコードだとこんな感じで、定義してる。
ServiceConnectionの方は接続時と切断時に行いたい処理を書ける。

[onDestroy()]
        if(mService!=null){
            unbindService(mServiceConn);
        }
こんな感じで、接続中ならunbindServiceしないとダメらしい。
サンプルではonDestroy()に記述するようになっている。

[onCreate()]
        Intent serviceIntent=new Intent("com.android.vending.billing.InAppBillingService.BIND");
        serviceIntent.setPackage("com.android.vending");
        bindService(serviceIntent,mServiceConn,Context.BIND_AUTO_CREATE);
接続はこんな感じ。
サンプルではonCreate()でやってる。

[購入済みアイテムの問い合わせ]
mService=IInAppBillingService.Stub.asInterface(service);
try{
   Bundle res=mService.getPurchases(3,getPackageName(),"inapp",null);
}catch(Exception e){
   return;
}
getPurchases() で取得できるBundleに入っている key:"INAPP_PURCHASE_ITEM_LIST" が購入済みアイテムのアイテムIDが入った ArrayList<String> となっています。
"INAPP_PURCHASE_ITEM_LIST" 以外に、"RESPONSE_CODE","INAPP_PURCHASE_DATA_LIST","INAPP_DATA_SIGNATURE_LIST","INAPP_CONTINUATION_TOKEN"がBundle内に入っています。
自前サーバーで厳密に管理する場合以外は "INAPP_PURCHASE_ITEM_LIST" のみをチェックすれば良い気がします。
getPurchases()は通信が発生すると思いますのでUIスレッドと別のスレッドでリクエストする必要があります。
第1引数の3はIn-app Billingのバージョン。
第2引数はパッケージ名。
第3引数は購入型アイテムの場合は"inapp"。
第4引数は不明。

[有効なアイテム一覧の取得]
mService=IInAppBillingService.Stub.asInterface(service);
Bundle req=new Bundle();
ArrayList<String> list=new ArrayList<String>();
list.add("item_id");
req.putStringArrayList("ITEM_ID_LIST",list);
try{
   Bundle res=mService.getSkuDetails(3,getPackageName(),"inapp",req);
}catch(Exception e){}
getSkuDetails() で有効なアイテム一覧が取得できます。
こちらも通信が発生すると思われるのでスレッドで。
引数は getPurchases() と似た感じですが、第4引数に検索対象のアイテムIDの入った ArrayList<String> をkey:"ITEM_ID_LIST" で格納したBundleを渡します。
レスポンスはBundleですが、Bundle内にはリクエストの key:"ITEM_ID_LIST" に対応した key:"DETAILS_LIST" の ArrayList<String>  が入っており、中身のStringはJSON形式です。
JSONの内容はDeveloper Consoleで設定したアイテム情報ですが、 "price" でユーザーの地域に合わせた通過表記の価格が取得できます。

[購入リクエスト]
try{
   Bundle req=mService.getBuyIntent(3,getPackageName(),"hide_ad","inapp","item_id");
   startIntentSenderForResult(((PendingIntent)req.getParcelable("BUY_INTENT")).getIntentSender(),1001,new   Intent(),Integer.valueOf(0),Integer.valueOf(0),Integer.valueOf(0));
}catch(Exception e){}
購入リクエストはIntentを投げて行います。
ダイアログが出てくるだけなのでActivityは閉じませんが、onActivityResult() で結果が帰ってきます。
onActivityResult() で取得できるIntentに情報が入っていますが、自前サーバーで管理しないならrequestCodeとresultCodeのみ確認すれば良い気がします。