Androidのアプリ内課金IABv2のサービス終了と新しいIABv3への移行方法

Androidのアプリ内課金IAB v2 ( In-app Billing Version2 )は、
2015年1月27日にサービス終了することがGoogleから告知されています。

<I>We plan to turn off the In-app Billing Version 2 service on January 27, 2015</I>
http://developer.android.com/google/play/billing/v2/api.html

新しいIAB v3への移行方法とメリットについて紹介します。

![iab_sample_dialog.png](https://qiita-image-store.s3.amazonaws.com/0/34488/676bc8fb-17ae-f956-7e81-c6a02539f1d4.png)

##IAB v2の終了後にIAB v2を使い続けた場合どうなってしまうのか？
購入ができなくなります。
IAB v3に移行しましょう。
ユーザーがIAB v3対応アプリに適切にアップデートできるように、
2014年11月までに対応することをGoogleは推奨しています。

##IAB v3に移行したときのメリット
###1. 購入結果をアプリが確実に受け取ることができる
IAB v2では購入結果がBroadcast IntentでIN_APP_NOTIFYの通知が来るようになっていましたが、その通知が届かないことがあり、アプリが購入結果を受け取れないことがありました。

IAB v3では購入結果がアプリのActivityにIntentで返されるようになり、
アプリが適切に購入結果を受け取ることができるようになります。


###2. ユーザーの購入情報をより詳細に知ることができる
購入処理後にアプリが受け取るINAPP_PURCHASE_DATAのJSONにorderidのパラメータがIABv3で追加されました。

|フィールド名|説明|
|:---|:--|
| orderId | <B>(NEW)</B> Google WalletのOrderIDと対応|
|packageName|アプリ名|
| productId |商品ID|
| purchaseTime |購入時刻|
| purchaseState |購入ステータス (0:購入完了 1:キャンセル 2:払戻し)|
| developerPayload |購入処理時に使用する検証用文字列|
| purchaseToken |トークン|

（参照：<I>Descriptions of the JSON fields for INAPP_PURCHASE_DATA</I>）
http://developer.android.com/google/play/billing/billing_reference.html#getBuyIntent


.
orderidでは販売者番号と定期購読の更新回数が追加され、継続課金されているユーザーが何回更新してくれているかを知ることができます。

例：orderIdのサンプル

|orderId|説明|
|:--|:--|
|12999556515565155651.5565135565155651    |(base order number)|
|12999556515565155651.5565135565155651..0 |初回購入|
|12999556515565155651.5565135565155651..1 |更新1回目|
|12999556515565155651.5565135565155651..2 |更新2回目|
（参照：<I>Subscription order numbers</I>）
http://developer.android.com/google/play/billing/billing_subscriptions.html#payment

##IAB v3に移行するための実装方法
簡単な流れは以下です。
1. 環境設定
2. Google Playとのコネクションの作成
3. 購入リクエストの作成
4. 購入結果のレスポンス対応

<b>ですが、
GoogleのUtilを利用したもっと簡単な流れは以下です。
</b>


1. 環境設定
2. GoogleのサンプルからUtilをimport
3. 購入処理

本記事ではGoogleのサンプルからUtilを引用して実装する方法を紹介します。

###1. 環境設定
・IInAppBillingService.aidlの追加
Android SDK Managerを起動して、
Extrasフォルダの<B>Google Play Billing Library</B>をインストールしてください。

インストール後、以下のフォルダ内にあるIInAppBillingService.aidlを手に入れます。

```xml
<android-sdk>/extras/google/play_billing/
```

IInAppBillingService.aidlをプロジェクトの以下に配置します。

```xml
/src/com.android.vending.billing/
```


・AndroidManifest.xmlの変更
permissionに以下を追加します。

```xml

<uses-permission android:name="com.android.vending.BILLING" />

```

###2. GoogleのサンプルからUtilをコピペ
\<android-sdk\>/extras/google/play_billing/samples/のフォルダ内にGoogleのサンプルアプリのコードがあるので、その中にあるUtilフォルダ内のファイルを全てコピペすると簡単に購入処理の開始と結果の受け取りができます。


```xml
<android-sdk>/extras/google/play_billing/samples/TrivialDrive/src /com/example/android/trivialdrivesample/util/

```

自身のprojectのsrcフォルダのどこかにutilをコピペします。
コピペした各ファイルのpackageの箇所を変更することを忘れないで下さい。

```java
package com.example.android.trivialdrivesample.util
↓
package （あなたの指定するパッケージ名）
```

###3. 購入リクエストの開始
コピペしたクラスの中でプログラマーが使用するのはIabHelperのみです。

課金処理を行うActivityをBillingActivityとして話を進めます。


####初期化

```java
public class BillingActivity extends Activity {

    // Google Play Dev Console上で作成したあなたの課金商品のID
    static final String PRODUCT_ID = "あなたの課金商品のID";


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Google Play Developer Consoleからアプリのライセンスキーを入手
        // もしくは適当な文字列
        String base64EncodedPublicKey = "(アプリのライセンスキー)";

        // インスタンスの生成
        mHelper = new IabHelper(this, base64EncodedPublicKey);

        // 購入リクエスト前の準備
        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
            public void onIabSetupFinished(IabResult result) {
                Log.d("IAB", "セットアップ完了");

                if (!result.isSuccess()) {
                    Log.d("IAB", "セットアップ失敗");
                    return;
                }

                if (mHelper == null) return;

                Log.d(TAG, "セットアップ成功。購入済みアイテムを取得する");
                mHelper.queryInventoryAsync(mGotInventoryListener);
            }
        });
    }

    // 購入済みアイテムの取得結果の受け取り用メソッドを作成
    IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
            Log.d("IAB", "購入済みアイテムの取得完了");

            if (mHelper == null) return;

            if (result.isFailure()) {
                Log.d("IAB", "購入済みアイテムの取得失敗");
                return;
            }

            Log.d("IAB", "購入済みアイテムの取得成功");

            // 購入済みアイテムの確認
            Purchase purchase = inventory.getPurchase(PRODUCT_ID);
            if (purchase != null) {
                Log.d("IAB", "商品を購入済みです。"));
            }
    };

    // 購入ボタンを押した時に購入リクエストを送る
    // 例としてサブスクリプション（定期購読）商品を購入する
    public void onBuyButtonClicked(View arg0) {

        // 端末がサブスクリプション課金に対応しているかを確認する
        if (!mHelper.subscriptionsSupported()) {
            Log.d("IAB","あなたの端末ではサブスクリプション購入はできません。")
            return;
        }

        Log.d("IAB","購入処理を開始");
        String requestCode = "10001" // 適当なリクエストコード;
       
        try {
            mHelper.launchPurchaseFlow(this,
                    PRODUCT_ID, IabHelper.ITEM_TYPE_SUBS,
                    requestCode, mPurchaseFinishedListener, null);
        } catch (IllegalStateException ex) {
            Log.d("例外：購入処理中です。");
        } 
    }


    // 購入結果の受け取り用メソッド
    IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
            Log.d("IAB", "購入完了 result:" + result + ", purchase: " + purchase);

            if (mHelper == null) return;

            if (result.isFailure()) {
                Log.d("IAB","購入失敗");
                return;
            }

            Log.d("IAB", "購入成功");

            if (purchase.getSku().equals(PRODUCT_ID)) {
                Log.d("IAB", "あなたの商品：" + PRODUCT_ID + "を購入しました。");
                Log.d("IAB","orderIdは：" + purchase.getOrderId());
                Log.d("IAB","INAPP_PURCHASE_DATAのJSONは：" + purchase.getOriginalJson());
            }
        }
    };

    // 購入結果をActivityが受け取るための設定
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (mHelper != null && !mHelper.handleActivityResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }


```

以上でIAB v3による購入処理の実装は完了です。
Googleのサンプルから引用したIabHelperがほとんど処理してくれるため、
非常に簡単に購入処理の開始と、結果の受け取りが可能になっています。


##実装時の注意点
・実購入テストで「購入しようとしたアイテムが見つかりませんでした。」が表示される場合はGoogle Play Developer Consoleでアプリをベータ版で公開する
参考：http://qiita.com/uebo/items/52d27e871d37518fa304

・IABv3を使うActivityのlaunchModeはsingleInstanceにしてはいけないようです。
購入結果がActivityにintentで来たときに処理できません。
参考：http://y-anz-m.blogspot.jp/2013/06/in-app-billing-v3-singleinstance.html

・課金ボタンを連打するとアプリが落ちてしまう対策
IabHelper内で課金処理は１つしか行えません。
前の処理が完了していないうちに、次の課金処理が来るとIllegalStateExceptionを投げます。
IabHelper.launchPurchaseFlowをtry/catchで囲んで、アプリが落ちないようにします。

##まとめ
Googleのサンプルを利用すれば、IABv3への移行は簡単です。
IABv2を利用されている方はv3への移行を2015年2月1日までにお早めに。

新しく課金システムの導入を検討されている方はご参考に。


##参考：
公式リファレンス：
http://developer.android.com/google/play/billing/billing_subscriptions.html#payment
http://developer.android.com/google/play/billing/billing_integrate.html#billing-service
http://developer.android.com/training/in-app-billing/preparing-iab-app.html