注: 2021 年 8 月 2 日以降、すべての新規アプリでは Billing Library バージョン 3 以降を使用する必要があります。2021 年 11 月 1 日までに、既存のアプリのアップデートはすべて Billing Library バージョン 3 以降を使用する必要があります。詳細

AIDL から Google Play 請求サービス ライブラリへの移行ガイド

このトピックでは、Android インターフェース定義言語(AIDL)を使用する請求の統合から移行する方法について説明します。AIDL を使用した Google Play の課金システムへのアクセスはサポートが終了しているため、今後はすべての統合で Google Play Billing Library を使用する必要があります。

移行手順

Google Play 請求サービス ライブラリをインポートする

まず、Google Play 請求サービス ライブラリに依存関係を追加します。Gradle を使用している場合は、アプリの build.gradle ファイルに次のコードを追加できます。

dependencies {
    def billing_version = "3.0.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

以前の参照コードからコピーした可能性がある「グルー」コード(IabHelper など)はすべて削除できます。IabHelper が提供する機能は現在、Google Play 請求サービス ライブラリに含まれています。

com.android.vending.BILLING 権限を削除する

com.android.vending.BILLING 権限は Google Play 請求サービス ライブラリのマニフェスト内に埋め込まれています。この権限をアプリのマニフェスト内で明示的に追加する必要はなくなりました。

Google Play 請求サービスに接続する

接続の管理は Google Play 請求サービス ライブラリの BillingClient が行います。移行する場合、アプリを次のように変更します。

次の例は、アプリの変更前と変更後のコードを示しています。

変更前

mServiceConn = new ServiceConnection() {
    @Override
    public void onServiceDisconnected(ComponentName name) {
      ...
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      ...
    }
};

Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> intentServices = mContext.getPackageManager()
    .queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
    mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
    // Handle errors.
    ...
}

...

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    IabResult result;
    if (requestCode != mRequestCode || data == null) {
        // Handle errors.
        ...
    }

    int responseCode = getResponseCodeFromIntent(data);
    String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
    String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

    if (resultCode != Activity.RESULT_OK || responseCode != BILLING_RESPONSE_RESULT_OK) {
        // Handle errors.
        ...
    }

    // Process successful purchase.
    ...

    return true;
}

変更後

Kotlin

class MyBillingImpl(private var billingClient: BillingClient) : PurchasesUpdatedListener {

    init {
        billingClient = BillingClient.newBuilder(activity).setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult?) {
                // Logic from ServiceConnection.onServiceConnected should be moved here.
            }

            override fun onBillingServiceDisconnected() {
                // Logic from ServiceConnection.onServiceDisconnected should be moved here.
            }
        })
    }

    override fun onPurchasesUpdated(
        billingResult: BillingResult?,
        purchases: MutableList<Purchase>?
    ) {
        // Logic from onActivityResult should be moved here.
    }
}

Java

public class MyBillingImpl implements PurchasesUpdatedListener {
    private BillingClient billingClient;
    ...

    public void initialize() {
        billingClient = BillingClient.newBuilder(activity).setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                // Logic from ServiceConnection.onServiceConnected should be moved here.
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Logic from ServiceConnection.onServiceDisconnected should be moved here.
            }
        });
    }

    @Override
    public void onPurchasesUpdated(
        @BillingResponse int responseCode, @Nullable List<Purchase> purchases) {
        // Logic from onActivityResult should be moved here.
    }
}

購入する

購入ダイアログを起動する方法は次のとおりです。

次の例は、アプリの変更前と変更後のコードを示しています。

変更前

// Query Skus
String skuToSell = "premium_upgrade";
ArrayList<String> skus = new Arraylist<>();
skus.add(skuToSell);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skus);
Bundle skuDetails = mService.getSkuDetails(3,
                                           mContext.getPackageName(),
                                           itemType,
                                           querySkus);

if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
    // Handle errors.
    ...
}

// Launch Buy Flow
Bundle buyIntentBundle = mService.getBuyIntent(3,
                                               mContext.getPackageName(),
                                               skuToSell,
                                               "Inapp",
                                               "");

int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
    // Handle errors.
    ...
}

PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
                               requestCode,
                               new Intent(),
                               Integer.valueOf(0),
                               Integer.valueOf(0),
                               Integer.valueOf(0));

// Purchase is handled in onActivityResult illustrated in the previous section.

変更後

Kotlin

val skuToSell = "premium_upgrade"
val skuList = mutableListOf<String>()
skuList.add(skuToSell)
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build(),
  object : SkuDetailsResponseListener {
    override fun onSkuDetailsResponse(
      billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
        // Process the result.
        }
    })

// SkuDetails object obtained above.
val skuDetails = ...
val purchaseParams = BillingFlowParams.newBuilder()
  .setSkuDetails(skuDetails)
   .build()

billingClient.launchBillingFlow(activity, purchaseParams)

// Purchase is handled in onPurchasesUpdated illustrated in the previous section

Java

String skuToSell = "premium_upgrade";
List<String> skuList = new ArrayList<> ();
skuList.add(skuToSell);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
        @Override
        public void onSkuDetailsResponse(BillingResult billingResult,
                                         List<SkuDetails> skuDetailsList) {
            // Process the result.
            ...
        }
    });

// SkuDetails object obtained above.
SkuDetails skuDetails = ...;

BillingFlowParams purchaseParams =
    BillingFlowParams.newBuilder()
            .setSkuDetails(skuDetails)
            .build();

mBillingClient.launchBillingFlow(mActivity, purchaseParams);

// Purchase is handled in onPurchasesUpdated illustrated in the previous section.

購入アイテムを消費する

Google Play 請求サービス ライブラリを使用して購入アイテムを消費する方法は次のとおりです。

次の例は、アプリの変更前と変更後のコードを示しています。

変更前

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    int responseCode = data.getIntExtra(RESPONSE_CODE);
    JSONObject purchaseData =
        new JSONObject(data.getStringExtra("INAPP_PURCHASE_DATA"));

    String token = purchaseData.get("purchaseToken");

    ...

    // Consume purchase
    int response = mService.consumePurchase(3, mContext.getPackageName(), token);
    if (response != BILLING_RESPONSE_RESULT_OK) {
        // Handle errors.
        ...
    }

    // Handle successful consumption.
}

変更後

Kotlin

class MyBillingImpl(private val billingClient: BillingClient) :
        ... , ConsumeResponseListener {

  fun consumePurchase(purchaseToken: String) {
    val consumeParams = ConsumeParams
      .newBuilder()
      .setPurchaseToken(purchaseToken)
      .build()
  }

  override fun onConsumeResponse(
    billingResult: BillingResult?,
    purchaseToken: String?) {
      // Handle consumption
    }
 }

Java

public class MyBillingImpl implements ..., ConsumeResponseListener {
    private BillingClient billingClient;
    ...

    public void consumePurchase(String purchaseToken) {
        ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
                .setPurchaseToken(purchaseToken)
                .build();
    }

    @Override
    void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
        // Handle consumption.
        ...
    }
}

購入を承認する

Google Play 請求サービス ライブラリのバージョン 2.0 以降では、アプリですべての購入アイテムを消費するか、すべての購入を承認する必要があります。

3 日以内に購入アイテムを消費するか、購入を承認しないと、Google が自動的に購入を取り消し、ユーザーに払い戻しを行います。詳しくは、購入を承認するをご覧ください。

アプリ外購入を認識する

アプリ外購入の処理を Google Play 請求サービス ライブラリに移行する方法は次のとおりです。

  • アプリが BillingClient.queryPurchases() の呼び出しをアプリの onResume() コールバックで行っていることを確認します。
  • com.android.vending.billing.PURCHASES_UPDATED のブロードキャスト レシーバを削除し、対応するコールバックのコードを PurchasesUpdatedListener に移動します。

以前は、Google Play 請求サービスを AIDL と統合する場合、アプリ外購入を処理するには、アプリで com.android.vending.billing.PURCHASES_UPDATED インテントを受け取るリスナーを登録する必要がありました。

アプリが実行されていない間に行われたすべての購入が認識されるようにするには、最初のステップとして、Google Play 請求サービス ライブラリを使用して、queryPurchases() をアプリの onResume() コールバックで呼び出す必要があります。アプリが実行中は、Google Play 請求サービス ライブラリがアプリ外購入を自動的にリッスンし、PurchasesUpdatedListener を介して通知します。

保留中のトランザクションを処理する

Google Play 請求サービス ライブラリのバージョン 2.0 以降では、購入してから利用権を付与するまでの間に追加の操作が必要な保留中のトランザクションをアプリで処理する必要があります。たとえば、ユーザーが実店舗でアプリ内アイテムを現金で購入するようなケースがあります。その場合は、トランザクションがアプリの外部で完了することになります。このようなシナリオでは、ユーザーがトランザクションを完了した後にのみ利用権を付与する必要があります。

詳しくは、保留中のトランザクションをサポートするをご覧ください。

デベロッパー ペイロード

デベロッパー ペイロードは、不正行為の防止や、適切なユーザーへの購入のアトリビューションなど、さまざまな目的で使用されてきました。これらのユースケースは Google Play 請求サービス ライブラリでサポートされるようになったため、Google Play 請求サービス ライブラリのバージョン 2.2 以降ではデベロッパー ペイロードがサポートされなくなっています。詳しくは、デベロッパー ペイロードをご覧ください。

詳細なエラー メッセージ

Google Play 請求サービス ライブラリのバージョン 2.0 以降では、すべてのエラーに、対応するデバッグ関連のメッセージが用意されています。これらのメッセージを取得するには、BillingResult.getDebugMessage() を呼び出します。