Google Play In-app BillingをAPI v3のサンプルを使って楽して導入する


課金怖くない。

- http://developer.android.com/google/play/billing/api.html

v2からかなり改善されていて良い。BroadcastをReceiveするのでなくて、onActivityResultへ結果が降ってくるようになったので、サンプルから持って来てそのままActivityに取り込みやすい。

対応しました。
Subscriptionには非対応とのこと。
v2, v3で迷う時は

- http://developer.android.com/google/play/billing/billing_overview.html#migration



## 1. SDKから持ってくる

- http://developer.android.com/training/in-app-billing/preparing-iab-app.html

### IInAppBillingService.aidl

`{sdk}/extras/google/play_billing/in-app-billing-v03/IInAppBillingService.aidl`を`src.com.android.vending.billing`パッケージへコピー。

buildすると`project/gen`に`IInAppBillingService.java`が生成される。

### サンプルのUtility

`extras/google/play_billing/in-app-billing-v03/samples/TrivialDrive/src/com/example/android/trivialdrivesample/util/*.java`を自分のプロジェクトで適当なパッケージを切ってコピー。よしなにpackage, importをエラーが出ないように抑制する。

## 2. サンプルのActivityを参考に課金処理をActivityに実装する
以下、実装例。

- `void requestBilling()`をActivity上で実行すれば非消費型アイテムの購入プロセスがスタートする
- `void requestSubscriptionBilling()`を実行すれば定期購読アイテムの購入プロセスがスタートする
- `boolean isPremiumUser()`,`boolean isPremiumSubscriber()`で課金した状態かどうかを取得出来る
ような各アクティビティで継承する基底のクラスをイメージ。

あとはPlayから確実に課金状態が取得出来ない（v2ではちらほら起きていた）ことも考慮して本体に保存、定期的にチェックを入れるような実装をするのが良さそう。また、BillingがSupportedかどうかをちゃんとハンドリングするべき。あとサンプルのUtilityの中身も精査した方が良さそう。


```java
public abstract class BaseActivity extends SherlockFragmentActivity {

    private static final String SKU_PREMIUM = "Developer Consoleで設定した非消費型アイテムのproduct id";
    private static final String SKU_PREMIUM_SUBSCRIPTION = "Developer Consoleで設定した定期購読アイテムのproduct id";

    private static final int REQUEST_CODE_PURCHASE_PREMIUM = よしなにリクエストコードを;
    private static final String BILLING_PUBLIC_KEY = "Developer Consoleで得られるBase64encodedな公開鍵（そのままコードに持たせず別の形からデコードなりして持ってくるのが推奨されている";

    private IabHelper mBillingHelper;
    private boolean mIsPremium = false;
    private boolean mIsSubscriber = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ~~
        setupBilling();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        tearDownBilling();
    }

    private void setupBilling() {
        mBillingHelper = new IabHelper(this, BILLING_PUBLIC_KEY);
        mBillingHelper.enableDebugLogging(true); // Remove before release
        mBillingHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
            public void onIabSetupFinished(IabResult result) {
                Log.d("billing", "Setup finished.");
                if (result.isFailure()) return;
                Log.d("billing", "Setup successful. Querying inventory.");
                mBillingHelper.queryInventoryAsync(mGotInventoryListener);
            }
        });
    }

    private void tearDownBilling() {
        if (mBillingHelper != null) mBillingHelper.dispose();
        mBillingHelper = null;
    }

    private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
            Log.d("billing", "Query inventory finished.");
            if (result.isFailure()) return;
            Log.d("billing", "Query inventory was successful.");
            mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
            Log.d("billing", "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
        }
    };

    private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
            Log.d("billing", "Purchase finished: " + result + ", purchase: " + purchase);
            if (result.isFailure()) return;
            Log.d("billing", "Purchase successful.");
            if (purchase.getSku().equals(SKU_PREMIUM)) {
                Log.d("billing", "Purchase is premium upgrade. Congratulating user.");
                mIsPremium = true;
            }
            if (purchase.getSku().equals(SKU_PREMIUM_SUBSCRIPTION)) {
                Log.d("billing", "Purchase is new subscribing. Congratulating.");
                mIsSubscriber = true;
            }
        }
    };

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!mBillingHelper.handleActivityResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    protected boolean isPremiumUser() {
        return this.mIsPremium;
    }

    protected boolean isSubscriber() {
        return this.mIsSubscriber;
    }

    protected void requestBilling() {
        mBillingHelper.launchPurchaseFlow(this, SKU_PREMIUM, REQUEST_CODE_PURCHASE_PREMIUM, mPurchaseFinishedListener);
    }

    protected void requestSubscriptionBilling() {
        if (mBillingHelper.subscriptionsSupported()) {
            mBillingHelper.launchSubscriptionPurchaseFlow(this, SKU_PREMIUM_SUBSCRIPTION, REQUEST_CODE_PURCHASE_PREMIUM, mPurchaseFinishedListener);
        }
    }

    protected void cancelSubscription() {
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getPackageName())));
    }

}
```

これだけ。v2で必死にListener作ったのは何だったんだ。