Androidアプリ開発

Google Play Billing Library 5(6,7)対応

この記事は約41分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaでの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)

この記事のテーマ


Google Play Billing Library 5で非推奨となった querySkuDetailsAsync の代わりに
launchBillingFlow に引き渡す billingFlowParams を生成する


2024年9月以降のアプリ内課金の実装は、Billing Library 6以降の使用が必須となります。
ここでは、Billing Library 5への移行について説明します。

2024年5月現在、Google Play Billing Library 7が公開されています。
Billing Library 5へ移行したソースのままで、動作しています。

Google Play Billing Library 7enablePendingPurchases()が非推奨となりました。enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())に変更する必要があります。

非推奨となった querySkuDetailsAsync の対応

◎ポイント
Google Play Billing Library 5( com.android.billingclient:billing:5 ) では、querySkuDetailsAsync、SkuDetails などが非推奨になり、替わりに queryPurchasesAsync、ProductDetails を使用した実装に変更する必要があります。

対応前

querySkuDetailsAsync を呼び出して、SkuDetails のリストを取得します。
SkuDetails のリストを引数に
BillingFlowParams を生成して、launchBillingFlow を呼び出す実装を行います。

Java 対応前コーディング(参考)

◎アプリ内課金クラス(InAppBilling)
購入可能リスト取得では、querySkuDetailsAsync を使用して、SkuDetails を取得します。
SkuDetails を引数として、購入フローを起動します。

public class InAppBilling {
    private BillingClient               billingClient;
    private final ArrayList<String>     errorMessage = new ArrayList<>();
    private List<Purchase>              arrayListPurchase = new ArrayList<>();
    private List<SkuDetails>            arraySkuDetails = new ArrayList<>();
    private final int                   responseCode = Integer.MAX_VALUE;
    // インタフェース
    public interface OnBillingClientStateListener {
        void onBillingClientState(int responseCode);
    }
    public interface OnConsumeAsyncListener {
        void consumeAsync(int consume);
    }
    public interface OnPurchasesUpdatedListener {
        void purchasesUpdated(int purchase);
    }
    public interface OnPurchasesResponseListener {
        void queryPurchasesResponse(int responseCode);
    }
    public interface OnSkuDetailsResponseListener {
        void skuDetailsResponse(int responseCode);
    }
    // コンストラクタ
    public InAppBilling(Context context,
                        OnBillingClientStateListener onBillingClientStateListener,
                        OnPurchasesUpdatedListener onPurchasesUpdatedListener,
                        OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient = BillingClient.newBuilder(context).setListener((billingResult, list) -> {
            switch (billingResult.getResponseCode()) {
                case BillingClient.BillingResponseCode.OK:
                    if (null != list) {
                        for (Purchase purchase : list) {
                            if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
                                billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
                                    if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                                        errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
                                    onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
                                });
                            }
                        }
                    }
                    break;
                case BillingClient.BillingResponseCode.USER_CANCELED:
                case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
                    break;
                case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
                default:
                    errorMessage.add(String.format("BillingResult : %s", billingResult.getDebugMessage()));
                    break;
            }
            onPurchasesUpdatedListener.purchasesUpdated(billingResult.getResponseCode());
     }).enablePendingPurchases().build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
                    onBillingClientStateListener.onBillingClientState(responseCode);
            }
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                onBillingClientStateListener.onBillingClientState(billingResult.getResponseCode());
            }
        });
    }
    // エラーメッセージ取得
    public ArrayList<String> getErrorMessage() {
        return errorMessage;
    }
    public int getConnectionState() {
        return billingClient.getConnectionState();
    }
    // BillingClient切断
    public void endConnection() {
        if (getConnectionState() ==  BillingClient.ConnectionState.CONNECTED)
            billingClient.endConnection();
    }
    // 購入済みリスト取得
    public void queryPurchasesAsync(OnPurchasesResponseListener onPurchasesResponseListener) {
        arrayListPurchase = new ArrayList<>();
        billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, (result, list) -> {
            arrayListPurchase.addAll(list);
            onPurchasesResponseListener.queryPurchasesResponse(result.getResponseCode());
        });
    }
    public List<Purchase> getArrayListPurchase() {
        return arrayListPurchase;
    }
    // 購入可能リスト取得
    public void querySkuDetailsAsync(String sku, OnSkuDetailsResponseListener onSkuDetailsResponseListener) {
        List<String> skuList = new ArrayList<>();
        arraySkuDetails = new ArrayList<>();
        skuList.add(sku);
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
        billingClient.querySkuDetailsAsync(params.build(), (billingResult, list) -> {
            if (list != null) arraySkuDetails.addAll(list);
            onSkuDetailsResponseListener.skuDetailsResponse(billingResult.getResponseCode());
        });
    }
    public List<SkuDetails> getArraySkuDetails() {
        return  arraySkuDetails;
    }
    // 購入フロー起動
    public int launchBillingFlow(Activity activity, SkuDetails skuDetails) {
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
        return billingClient.launchBillingFlow(activity, billingFlowParams).getResponseCode();
    }
    // 消費
    public void consumeAsync(Purchase purchase, OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
            if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
            onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
        });
    }
}

◎アプリ内課金の実装部
アプリ内課金クラス(InAppBilling)の購入可能リスト取得では、戻り値として SkuDetails を受け取り、launchBillingFlow を起動しています。

    // android.test.purchased	        正常に購入出来るテスト用プロダクトID
    // android.test.canceled	        購入のキャンセルをテストできるプロダクトID
    // android.test.refunded	        払い戻しが行われた時のレスポンスをテストできるプロダクトID
    // android.test.item_unavailable	購入希望商品が存在しなかった時をシミュレートできるプロダクトID
    private static final String       code = "android.test.purchased";
    private InAppBilling              inAppBilling;
    :
        inAppBilling = new InAppBilling(context,
                responseCode -> {
                    if (responseCode == BillingClient.BillingResponseCode.OK) {
                        // 購入済みリスト取得
                        inAppBilling.queryPurchasesAsync(responseCode1 -> {
                            if (responseCode1 == BillingClient.BillingResponseCode.OK) {
                                List<Purchase> listPurchase = inAppBilling.getArrayListPurchase();
                                if (listPurchase.size() > 0) {
                                    for (Purchase purchase : listPurchase) {
                                        // 消費リクエスト
                                        inAppBilling.consumeAsync(purchase, consume -> {
                                            :  
                                        });
                                    }
                                } else {
                                    // 購入可能リスト取得
                                    inAppBilling.querySkuDetailsAsync(code, responseCode2 -> {
                                        if (responseCode2 == BillingClient.BillingResponseCode.OK) {
                                            List<SkuDetails> listSkuDetails = inAppBilling.getArraySkuDetails();
                                            if (listSkuDetails.size() > 0) {
                                                for (SkuDetails skuDetails : listSkuDetails) {
                                                    if (inAppBilling.launchBillingFlow(this, skuDetails) != 0) {
                                                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                                                            Log.d(TAG, errorMessage);
                                                        }
                                                    }
                                                }
                                            } else {
                                                :
                                            }
                                        }
                                    });
                                }
                            }
                        });
                    } else {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                purchase -> {
                    if (purchase != BillingClient.BillingResponseCode.OK) {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                consume -> {
                    // 消費リクエスト
                    if (consume == BillingClient.BillingResponseCode.OK) {
                        :
                    } else {
                        :
                    }
                }
        );
    }

テスト用プロダクトID(android.test.purchase)によるテストができなくなりました。
未署名でGoogle Playにアップロードされていないアプリはブロックされます。
ライセンス テスターPlay Billing Labを活用したテストを推奨しています。

対応後

queryPurchasesAsync を呼び出して、ProductDetails のリストを取得します。
ProductDetails
のリストを引数に BillingFlowParams を生成して、launchBillingFlow を呼び出す実装を行います。