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 を呼び出す実装を行います。

Java 対応後コーディング

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

public class InAppBilling {
    private BillingClient               billingClient;
    private final ArrayList<String>     errorMessage = new ArrayList<>();
    private List<Purchase>              arrayListPurchase = new ArrayList<>();
    private final List<ProductDetails>  arrayProductDetails = 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(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()).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(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(), (result, list) -> {
            // List<Purchase>
            arrayListPurchase.addAll(list);
            onPurchasesResponseListener.queryPurchasesResponse(result.getResponseCode());
        });
    }
    public List<Purchase> getArrayListPurchase() {
        return arrayListPurchase;
    }
    // 購入可能リスト取得 //
    public void queryProductDetailsAsync(String product, OnSkuDetailsResponseListener onSkuDetailsResponseListener) {
        List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
        productList.add(QueryProductDetailsParams.Product.newBuilder()
                .setProductId(product)
                .setProductType(BillingClient.ProductType.INAPP)
                .build());
        QueryProductDetailsParams.Builder params = QueryProductDetailsParams.newBuilder();
        params.setProductList(productList);
        billingClient.queryProductDetailsAsync(params.build(), (billingResult, list) -> {
            // List<ProductDetails>
            arrayProductDetails.addAll(list);
            onSkuDetailsResponseListener.skuDetailsResponse(billingResult.getResponseCode());
        });
    }
    public List<BillingFlowParams.ProductDetailsParams> getProductDetailsParams() {
        List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = new ArrayList<>();
        for (ProductDetails productDetails : arrayProductDetails)
            // List<ProductDetailsParams>
            productDetailsParamsList.add(BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(productDetails).build());
        return productDetailsParamsList;
    }
    // 購入フロー起動 //
    public int launchBillingFlow(Activity activity, List<BillingFlowParams.ProductDetailsParams> skuDetails) {
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(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)の購入可能リスト取得では、getProductDetailsParams を使用して、ProductDetails を受け取り、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.queryProductDetailsAsync(code, responseCode2 -> {
                                        if (responseCode2 == BillingClient.BillingResponseCode.OK) {
                                            if (inAppBilling.launchBillingFlow(this, inAppBilling.getProductDetailsParams()) != 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を活用したテストを推奨しています。

Google Play Billing Library 7対応 した Androidアプリです。

今回は、ここまでです。

参考 : Billing Library 5 への移行

データのバックアップにNASを使用していますが、読み書きの速度がイマイチです。
外付けポータブルSSDだと600MB/Sで内蔵HDDと比べて遜色ない性能ですね♪

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、かなりの時間がかかります。
オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適です。まずは無料相談から♪

未経験者からプログラマーを目指すのに最適です。まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています。お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適です。まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪

スポンサーリンク
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました