Play 請求サービスでの商品の購入離脱を分析する

1. はじめに

この Codelab では、1 回限りのプロダクトの作成、Play Billing Library(PBL)とアプリの統合、購入の離脱理由の分析に焦点を当てます。

対象

この Codelab は、Play Billing Library(PBL)を使用しているか、PBL を使用して 1 回限りのプロダクトを収益化したいと考えている Android アプリ デベロッパーを対象としています。

学習内容

  • Google Play Console で 1 回限りのアイテムを作成する方法。
  • アプリを PBL と統合する方法。
  • PBL で消費可能アイテムと消費不可アイテムの 1 回限りのアイテムの購入を処理する方法。
  • 購入の離脱を分析する方法。

必要なもの

2. サンプルアプリをビルドする

このサンプルアプリは、次の側面を示す完全なソースコードを備えた、完全に機能する Android アプリとして設計されています。

  • アプリと PBL の統合
  • 1 回限りのアイテムを取得する
  • 1 回限りのアイテムの購入フローを開始する
  • 次の請求レスポンスにつながる購入シナリオ:
    • BILLING_UNAVAILABLE
    • USER_CANCELLED
    • OK
    • ITEM_ALREADY_OWNED

次のデモ動画は、サンプルアプリをデプロイして実行した後の外観と動作を示しています。

前提条件

サンプルアプリをビルドしてデプロイする前に、次の操作を行います。

ビルド

このビルドステップの目的は、サンプルアプリの署名付き Android App Bundle ファイルを生成することです。

Android App Bundle を生成する手順は次のとおりです。

  1. GitHub からサンプルアプリをダウンロードします。
  2. サンプルアプリをビルドします。ビルドする前に、サンプルアプリのパッケージ名を変更してからビルドします。Google Play Console に他のアプリのパッケージがある場合は、サンプルアプリに指定するパッケージ名が一意であることを確認してください。

    : サンプルアプリをビルドすると、ローカル テストに使用できる APK ファイルのみが作成されます。ただし、アプリを実行しても商品と価格は取得されません。これは、商品が Google Play Console で構成されていないためです。この構成は、この Codelab の後半で行います。
  3. 署名付きの Android App Bundle を生成します。
    1. アップロード鍵とキーストアを生成する
    2. アップロード鍵でアプリに署名する
    3. Play アプリ署名を設定する

次のステップは、Android App Bundle を Google Play Console にアップロードすることです。

3. Google Play Console で 1 回限りのアイテムを作成する

Google Play Console で 1 回限りのアイテムを作成するには、Play Console にアプリが必要です。Google Play Console でアプリを作成し、以前に作成した署名付き App Bundle をアップロードします。

アプリを作成する

アプリを作成するには:

  1. デベロッパー アカウントを使用して Google Play Console にログインします。
  2. [アプリを作成] をクリックします。[アプリを作成] ページが開きます。
  3. アプリ名を入力し、デフォルトの言語やアプリ関連のその他の詳細を選択します。
  4. [アプリを作成] をクリックします。これにより、Google Play Console にアプリが作成されます。

これで、サンプルアプリの署名済み App Bundle をアップロードできるようになりました。

署名済みの App Bundle をアップロードする

  1. 署名済みの App Bundle を Google Play Console の内部テストトラックにアップロードします。アップロード後にのみ、Play Console で収益化関連の機能を設定できます。
  2. [テストとリリース] > [テスト] > [内部リリース] > [新しいリリースを作成] をクリックします。
  3. リリース名を入力し、署名付きアプリバンドル ファイルをアップロードします。
  4. [次へ]、[保存して公開] の順にクリックします。

これで、1 回限りのアイテムを作成できるようになりました。

1 回限りのアイテムの作成

1 回限りのアイテムを作成する手順は次のとおりです。

  1. Google Play Console の左側のナビゲーション メニューで、[Google Play で収益化する] > [アイテム] > [1 回限りのアイテム] に移動します。
  2. [1 回限りのアイテムを作成] をクリックします。
  3. 次のアイテムの詳細情報を入力します。
    • アイテム ID: 一意のアイテム ID を入力します。「one_time_product_01」と入力します。
    • (省略可)タグ: 関連するタグを追加します。
    • 名前: 商品名を入力します。例: Product name
    • 説明: 商品の説明を入力します。例: Product description
    • (省略可)アイコン画像を追加する: 商品を表すアイコンをアップロードします。
    注: この Codelab では、[税金、コンプライアンス、プログラム] セクションの設定はスキップできます。
  4. [次へ] をクリックします。
  5. 購入オプションを追加して、地域別の提供状況を設定します。1 回限りのアイテムには、利用資格の付与方法、価格、地域別の提供状況を定義する購入オプションが少なくとも 1 つ必要です。この Codelab では、商品の標準の [購入] オプションを追加します。[購入オプション] セクションに以下の情報を入力します。
    • 購入オプション ID: 購入オプション ID を入力します。例: buy
    • 購入タイプ: [購入] を選択します。
    • (省略可)タグ: この購入オプションに固有のタグを追加します。
    • (省略可)[詳細オプション] をクリックして、詳細オプションを構成します。この Codelab では、詳細オプションの構成はスキップできます。
  6. [提供状況と価格] セクションで、[価格を設定] > [価格を一括編集] をクリックします。
  7. [国 / 地域] オプションを選択します。これにより、すべてのリージョンが選択されます。
  8. [続行] をクリックします。価格を入力するダイアログが開きます。10 米ドルと入力し、[適用] をクリックします。
  9. [保存]、[有効にする] の順にクリックします。これにより、購入オプションが作成され、有効になります。

この 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 と統合する手順は次のとおりです。

  1. サンプルアプリに Play Billing Library の依存関係を追加します。
    dependencies {
    val billing_version = "8.0.0"
    
    implementation("com.android.billingclient:billing-ktx:$billing_version")
    }
    
  2. BillingClient を初期化します。BillingClient は、アプリに存在し、Google Play Billing Library と通信するクライアント SDK です。次のコード スニペットは、課金クライアントを初期化する方法を示しています。
    protected BillingClient createBillingClient() {
    return BillingClient.newBuilder(activity)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
        .enableAutoServiceReconnection()
        .build();
    }
    
  3. 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.");
          }
        });
    }
    
  4. 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": []
            }
        ]
    }
    
  5. 請求フローを開始します。
    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);
    }
    
  6. 購入を検出して処理します。この手順では、次のことを行う必要があります。
    1. 購入を確認する
    2. ユーザーに利用資格を付与する
    3. ユーザーに通知する
    4. 購入手続きを Google に通知する
    このうち、ステップ a、b、c はバックエンドで行う必要があるため、この Codelab の範囲外です。次のスニペットは、消費可能な 1 回限りのプロダクトについて 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_CANCELLEDBILLING_UNAVAILABLEOKITEM_ALREADY_OWNED などの限られたシナリオに焦点を当ててきました。ただし、Google Play 課金では、さまざまな現実世界の要因によってトリガーされる13 種類のレスポンス コードが返されることがあります。

このセクションでは、USER_CANCELLED エラー レスポンスと BILLING_UNAVAILABLE エラー レスポンスの原因について詳しく説明し、実装可能な修正措置を提案します。

USER_CANCELED レスポンス エラーコード

このレスポンス コードは、ユーザーが購入フロー UI を完了する前に離脱したことを示します。

考えられる原因

どのような対応が必要ですか?

  • 価格に敏感な購入意向の低いユーザーを示している可能性があります。
  • 購入が保留中であるか、お支払いが承認されなかった。

BILLING_UNAVAILABLE レスポンス エラーコード

このレスポンス コードは、ユーザーの支払いプロバイダまたは選択したお支払い方法に問題があるため、購入を完了できなかったことを意味します。たとえば、ユーザーのクレジット カードの有効期限が切れている場合や、ユーザーがサポートされていない国にお住まいの場合などです。このコードは、Google Play 請求システム自体にエラーがあることを示すものではありません。

考えられる原因

どのような対応が必要ですか?

  • ユーザーのデバイスの Google Play ストア アプリが最新版でない。
  • お客様の国では Google Play がサポートされていません。
  • ユーザーが企業ユーザーであり、企業の管理者によってユーザーによる購入が無効にされている。
  • Google Play がユーザーのお支払い方法に対して請求できない(ユーザーのクレジット カードの有効期限が切れている場合など)。
  • システムの問題と特定の地域での傾向をモニタリングする
  • PBL 8 への移行をご検討ください。より詳細な PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS サブレスポンス コードをサポートしています。このレスポンス コードを受け取った場合は、ユーザーに失敗を通知するか、別の支払い方法を提案することを検討してください。
  • このレスポンス コードは再試行用に設計されているため、適切な再試行戦略を実装できます。
    このようなケースでは、自動再試行を使用できない場合があります。ただし、問題の原因となった事象に対処することで、手動での再試行は可能です。たとえば、ユーザーが Google Play ストアをサポートされているバージョンに更新すれば、最初の処理を手動で再試行できます。

    ユーザーがセッション中でないときにこのレスポンス コードが返された場合は、再試行しても意味がない場合があります。購入フローの結果として `BILLING_UNAVAILABLE` レスポンスを受け取った場合、そのユーザーは購入プロセス中に Google Play からフィードバックを受け取り、エラーの原因を認識している可能性が非常に高いです。このような場合は、エラーの原因を示すエラー メッセージを表示し、[再試行] ボタンでユーザーが問題に対応した後に手動で再試行できるようにします。

レスポンス エラーコードの再試行戦略

Play Billing Library(PBL)の回復可能なエラーに対する効果的な再試行戦略は、ユーザーのセッション中の操作(購入時など)とバックグラウンド オペレーション(アプリの再開時の購入のクエリなど)などのコンテキストによって異なります。これらの戦略を実装することが重要です。BillingResponseCode 値の中には、再試行で解決できる一時的な問題を示すものもあれば、永続的で再試行を必要としないものもあるためです。

ユーザーがセッション中の場合に発生したエラーについては、ユーザー エクスペリエンスへの影響を最小限に抑えるため、試行回数の上限を設定したシンプルな再試行戦略をおすすめします。一方、新規購入の確認など、即時実行を必要としないバックグラウンド オペレーションには、指数バックオフが推奨されます。

特定のレスポンス コードとそれに対応する推奨の再試行手段の詳細については、BillingResult のレスポンス コードを処理するをご覧ください。

6. 次のステップ

リファレンス ドキュメント

7. 完了

おめでとうございます!Google Play Console を操作して、新しい 1 回限りのアイテムの作成、請求レスポンス コードのテスト、購入離脱の分析を完了しました。

アンケート

この Codelab に関するフィードバックをお待ちしております。アンケートへのご協力をお願いいたします。