1. はじめに
この Codelab では、1 回限りのプロダクトの作成、Play Billing Library(PBL)とアプリの統合、購入の離脱理由の分析に焦点を当てます。
対象
この Codelab は、Play Billing Library(PBL)を使用しているか、PBL を使用して 1 回限りのプロダクトを収益化したいと考えている Android アプリ デベロッパーを対象としています。
学習内容
- Google Play Console で 1 回限りのアイテムを作成する方法。
- アプリを PBL と統合する方法。
- PBL で消費可能アイテムと消費不可アイテムの 1 回限りのアイテムの購入を処理する方法。
- 購入の離脱を分析する方法。
必要なもの
- デベロッパー アカウントで Google Play Console にアクセスできること。デベロッパー アカウントをお持ちでない場合は、アカウントを作成する必要があります。
- この Codelab のサンプルアプリ(GitHub からダウンロードできます)。
- Android Studio
2. サンプルアプリをビルドする
このサンプルアプリは、次の側面を示す完全なソースコードを備えた、完全に機能する Android アプリとして設計されています。
- アプリと PBL の統合
- 1 回限りのアイテムを取得する
- 1 回限りのアイテムの購入フローを開始する
- 次の請求レスポンスにつながる購入シナリオ:
BILLING_UNAVAILABLE
USER_CANCELLED
OK
ITEM_ALREADY_OWNED
次のデモ動画は、サンプルアプリをデプロイして実行した後の外観と動作を示しています。
前提条件
サンプルアプリをビルドしてデプロイする前に、次の操作を行います。
- Google Play Console デベロッパー アカウントを作成します。デベロッパー アカウントがすでにある場合は、この手順をスキップします。
- Google Play Console で新しいアプリを作成します。アプリを作成するときに、サンプルアプリのアプリ名を指定できます。
- Android Studio をインストールします。
ビルド
このビルドステップの目的は、サンプルアプリの署名付き Android App Bundle ファイルを生成することです。
Android App Bundle を生成する手順は次のとおりです。
- GitHub からサンプルアプリをダウンロードします。
- サンプルアプリをビルドします。ビルドする前に、サンプルアプリのパッケージ名を変更してからビルドします。Google Play Console に他のアプリのパッケージがある場合は、サンプルアプリに指定するパッケージ名が一意であることを確認してください。
注: サンプルアプリをビルドすると、ローカル テストに使用できる APK ファイルのみが作成されます。ただし、アプリを実行しても商品と価格は取得されません。これは、商品が Google Play Console で構成されていないためです。この構成は、この Codelab の後半で行います。 - 署名付きの Android App Bundle を生成します。
次のステップは、Android App Bundle を Google Play Console にアップロードすることです。
3. Google Play Console で 1 回限りのアイテムを作成する
Google Play Console で 1 回限りのアイテムを作成するには、Play Console にアプリが必要です。Google Play Console でアプリを作成し、以前に作成した署名付き App Bundle をアップロードします。
アプリを作成する
アプリを作成するには:
- デベロッパー アカウントを使用して Google Play Console にログインします。
- [アプリを作成] をクリックします。[アプリを作成] ページが開きます。
- アプリ名を入力し、デフォルトの言語やアプリ関連のその他の詳細を選択します。
- [アプリを作成] をクリックします。これにより、Google Play Console にアプリが作成されます。
これで、サンプルアプリの署名済み App Bundle をアップロードできるようになりました。
署名済みの App Bundle をアップロードする
- 署名済みの App Bundle を Google Play Console の内部テストトラックにアップロードします。アップロード後にのみ、Play Console で収益化関連の機能を設定できます。
- [テストとリリース] > [テスト] > [内部リリース] > [新しいリリースを作成] をクリックします。
- リリース名を入力し、署名付きアプリバンドル ファイルをアップロードします。
- [次へ]、[保存して公開] の順にクリックします。
これで、1 回限りのアイテムを作成できるようになりました。
1 回限りのアイテムの作成
1 回限りのアイテムを作成する手順は次のとおりです。
- Google Play Console の左側のナビゲーション メニューで、[Google Play で収益化する] > [アイテム] > [1 回限りのアイテム] に移動します。
- [1 回限りのアイテムを作成] をクリックします。
- 次のアイテムの詳細情報を入力します。
- アイテム ID: 一意のアイテム ID を入力します。「
one_time_product_01
」と入力します。 - (省略可)タグ: 関連するタグを追加します。
- 名前: 商品名を入力します。例:
Product name
- 説明: 商品の説明を入力します。例:
Product description
- (省略可)アイコン画像を追加する: 商品を表すアイコンをアップロードします。
- アイテム ID: 一意のアイテム ID を入力します。「
- [次へ] をクリックします。
- 購入オプションを追加して、地域別の提供状況を設定します。1 回限りのアイテムには、利用資格の付与方法、価格、地域別の提供状況を定義する購入オプションが少なくとも 1 つ必要です。この Codelab では、商品の標準の [購入] オプションを追加します。[購入オプション] セクションに以下の情報を入力します。
- 購入オプション ID: 購入オプション ID を入力します。例:
buy
- 購入タイプ: [購入] を選択します。
- (省略可)タグ: この購入オプションに固有のタグを追加します。
- (省略可)[詳細オプション] をクリックして、詳細オプションを構成します。この Codelab では、詳細オプションの構成はスキップできます。
- 購入オプション ID: 購入オプション ID を入力します。例:
- [提供状況と価格] セクションで、[価格を設定] > [価格を一括編集] をクリックします。
- [国 / 地域] オプションを選択します。これにより、すべてのリージョンが選択されます。
- [続行] をクリックします。価格を入力するダイアログが開きます。10 米ドルと入力し、[適用] をクリックします。
- [保存]、[有効にする] の順にクリックします。これにより、購入オプションが作成され、有効になります。
この Codelab では、次の商品 ID を使用して 3 つの 1 回限りのアイテムを追加で作成します。
- consumable_product_01
- consumable_product_02
- consumable_product_03
サンプルアプリは、これらのアイテム ID を使用するように構成されています。別の商品 ID を指定することもできます。その場合は、指定した商品 ID を使用するようにサンプルアプリを変更する必要があります。
Google Play Console でサンプルアプリを開き、[Play で収益化] > [商品] > [1 回限りの商品] に移動します。[1 回限りのアイテムを作成] をクリックし、手順 3 ~ 9 を繰り返します。
1 回限りのアイテムの作成に関する動画
次のサンプル動画は、前述の 1 回限りの商品作成手順を示しています。
4. PBL と統合する
次に、アプリを Play Billing Library(PBL)に統合する方法について説明します。このセクションでは、統合の大まかな手順と、各手順のコード スニペットについて説明します。これらのスニペットは、実際の統合を実装するためのガイダンスとして使用できます。
アプリを PBL と統合する手順は次のとおりです。
- サンプルアプリに Play Billing Library の依存関係を追加します。
dependencies { val billing_version = "8.0.0" implementation("com.android.billingclient:billing-ktx:$billing_version") }
- BillingClient を初期化します。BillingClient は、アプリに存在し、Google Play Billing Library と通信するクライアント SDK です。次のコード スニペットは、課金クライアントを初期化する方法を示しています。
protected BillingClient createBillingClient() { return BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .enableAutoServiceReconnection() .build(); }
- Google Play に接続します。次のコード スニペットは、Google Play に接続する方法を示しています。
public void startBillingConnection(ImmutableList<Product> productList) { Log.i(TAG, "Product list sent: " + productList); Log.i(TAG, "Starting connection"); billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Query product details to get the product details list. queryProductDetails(productList); } else { // BillingClient.enableAutoServiceReconnection() will retry the connection on // transient errors automatically. // We don't need to retry on terminal errors (e.g., BILLING_UNAVAILABLE, // DEVELOPER_ERROR). Log.e(TAG, "Billing connection failed: " + billingResult.getDebugMessage()); Log.e(TAG, "Billing response code: " + billingResult.getResponseCode()); } } @Override public void onBillingServiceDisconnected() { Log.e(TAG, "Billing Service connection lost."); } }); }
- 1 回限りのアイテムの詳細を取得します。アプリを PBL と統合したら、1 回限りのアイテムの詳細をアプリに取得する必要があります。次のコード スニペットは、アプリで 1 回限りのアイテムの詳細を取得する方法を示しています。
private void queryProductDetails(ImmutableList<Product> productList) { Log.i(TAG, "Querying products for: " + productList); QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(productList).build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { // check billingResult Log.i(TAG, "Billing result after querying: " + billingResult.getResponseCode()); // process returned productDetailsList Log.i( TAG, "Print unfetched products: " + productDetailsResponse.getUnfetchedProductList()); setupProductDetailsMap(productDetailsResponse.getProductDetailsList()); billingServiceClientListener.onProductDetailsFetched(productDetailsMap); } }); }
ProductDetails
を取得すると、次のようなレスポンスが返されます。{ "productId": "consumable_product_01", "type": "inapp", "title": "Shadow Coat (Yolo's Realm | Play Samples)", "name": "Shadow Coat", "description": "A sleek, obsidian coat for stealth and ambushes", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 1990000, "priceCurrencyCode": "USD", "formattedPrice": "$1.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }, { "productId": "consumable_product_02", "type": "inapp", "title": "Emperor Den (Yolo's Realm | Play Samples)", "name": "Emperor Den", "description": "A fair lair glowing with molten rock and embers", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 2990000, "priceCurrencyCode": "USD", "formattedPrice": "$2.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }
- 請求フローを開始します。
public void launchBillingFlow(String productId) { ProductDetails productDetails = productDetailsMap.get(productId); if (productDetails == null) { Log.e( TAG, "Cannot launch billing flow: ProductDetails not found for productId: " + productId); billingServiceClientListener.onBillingResponse( BillingResponseCode.ITEM_UNAVAILABLE, BillingResult.newBuilder().setResponseCode(BillingResponseCode.ITEM_UNAVAILABLE).build()); return; } ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder().setProductDetails(productDetails).build()); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); }
- 購入を検出して処理します。この手順では、次のことを行う必要があります。
- 購入を確認する
- ユーザーに利用資格を付与する
- ユーザーに通知する
- 購入手続きを Google に通知する
private void handlePurchase(Purchase purchase) { // Step 1: Send the purchase to your secure backend to verify the purchase following // https://developer.android.com/google/play/billing/security#verify // Step 2: Update your entitlement storage with the purchase. If purchase is // in PENDING state then ensure the entitlement is marked as pending and the // user does not receive benefits yet. It is recommended that this step is // done on your secure backend and can combine in the API call to your // backend in step 1. // Step 3: Notify the user using appropriate messaging. if (purchase.getPurchaseState() == PurchaseState.PURCHASED) { for (String product : purchase.getProducts()) { Log.d(TAG, product + " purchased successfully! "); } } // Step 4: Notify Google the purchase was processed. // For one-time products, acknowledge the purchase. // This sample app (client-only) uses billingClient.acknowledgePurchase(). // For consumable one-time products, consume the purchase // This sample app (client-only) uses billingClient.consumeAsync() // If you have a secure backend, you must acknowledge purchases on your server using the // server-side API. // See https://developer.android.com/google/play/billing/security#acknowledge if (purchase.getPurchaseState() == PurchaseState.PURCHASED && !purchase.isAcknowledged()) { if (shouldConsume(purchase)) { ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); } else { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.acknowledgePurchase( acknowledgePurchaseParams, acknowledgePurchaseResponseListener); } } }
5. 購入離脱を分析する
この Codelab では、これまで USER_CANCELLED、BILLING_UNAVAILABLE、OK、ITEM_ALREADY_OWNED などの限られたシナリオに焦点を当ててきました。ただし、Google Play 課金では、さまざまな現実世界の要因によってトリガーされる13 種類のレスポンス コードが返されることがあります。
このセクションでは、USER_CANCELLED
エラー レスポンスと BILLING_UNAVAILABLE
エラー レスポンスの原因について詳しく説明し、実装可能な修正措置を提案します。
USER_CANCELED レスポンス エラーコード
このレスポンス コードは、ユーザーが購入フロー UI を完了する前に離脱したことを示します。
考えられる原因 | どのような対応が必要ですか? |
|
|
BILLING_UNAVAILABLE レスポンス エラーコード
このレスポンス コードは、ユーザーの支払いプロバイダまたは選択したお支払い方法に問題があるため、購入を完了できなかったことを意味します。たとえば、ユーザーのクレジット カードの有効期限が切れている場合や、ユーザーがサポートされていない国にお住まいの場合などです。このコードは、Google Play 請求システム自体にエラーがあることを示すものではありません。
考えられる原因 | どのような対応が必要ですか? |
|
|
レスポンス エラーコードの再試行戦略
Play Billing Library(PBL)の回復可能なエラーに対する効果的な再試行戦略は、ユーザーのセッション中の操作(購入時など)とバックグラウンド オペレーション(アプリの再開時の購入のクエリなど)などのコンテキストによって異なります。これらの戦略を実装することが重要です。BillingResponseCode
値の中には、再試行で解決できる一時的な問題を示すものもあれば、永続的で再試行を必要としないものもあるためです。
ユーザーがセッション中の場合に発生したエラーについては、ユーザー エクスペリエンスへの影響を最小限に抑えるため、試行回数の上限を設定したシンプルな再試行戦略をおすすめします。一方、新規購入の確認など、即時実行を必要としないバックグラウンド オペレーションには、指数バックオフが推奨されます。
特定のレスポンス コードとそれに対応する推奨の再試行手段の詳細については、BillingResult のレスポンス コードを処理するをご覧ください。
6. 次のステップ
- 詳しくは、Google Play 請求の統合を最大限に活用するをご覧ください。
- ユーザーがこれらの商品の購入を開始したら、安全なバックエンドで購入の確認と処理に関するベスト プラクティスに従ってください。
リファレンス ドキュメント
7. 完了
おめでとうございます!Google Play Console を操作して、新しい 1 回限りのアイテムの作成、請求レスポンス コードのテスト、購入離脱の分析を完了しました。
アンケート
この Codelab に関するフィードバックをお待ちしております。アンケートへのご協力をお願いいたします。