Report: State of in-app subscriptions in the US 2023 Get a report

Androidアプリ内購入、パート 1: 設定とプロジェクトへの追加

Vlad Guriev

Updated: 3月 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

定期購入の作成

始める前に、次のことを確認してください。

1. Google Playデベロッパーアカウントを持っている

2. すべての契約書に署名し、作業を開始する準備が完了している

それでは、本題に入り、最初のアイテムを作成しましょう。

デベロッパーアカウントに切り替えて、アプリを選択します。

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

次に、左側のメニューで [アイテム] セクションの [定期購入] を選択して、[定期購入を作成] をクリックします。

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

次に、定期購入の設定ページが表示されます。ここでは、いくつかの重要な点があります。

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb

1. アプリで使用されるIDを作成します。定期購入の期間などの情報をIDに追加すると、アイテムを識別しやすくなり、販売統計の分析が容易になります。

2. ストアでユーザーに表示される定期購入の名前。

3. 定期購入の説明。ユーザーにも表示されます。

60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

下にスクロールして、定期購入期間を選択します。この場合は 1 週間です。価格を設定します。

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

通常、基本通貨で価格を設定すると、システム上で自動的に価格が換算されます。ただし、特定の国の価格を手動で編集することもできます。

Google では、すべての国の税金が表示されるためご注意ください。App Store Connect では表示されません。

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

下にスクロールして選択します (必要な場合)。

1. 無料試用期間。

2. お試し価格。最初の支払い期間の特別価格です。

3. 猶予期間 (grace period)。ユーザーに支払いの問題がある場合でも、数日間は有料プランを提供できます。

4. キャンセル後、アプリからではなく、Play ストアから再登録する機会。

Play Console と App Store Connect での購入プロセスの比較

iOS では定期購入がより効果的に収益化されるという事実に関わらず、Play Console の管理ボードの方が便利で使いやすく、適切にローカライズされており、動作スピードも優れています。

アイテムの作成プロセスは可能な限りシンプルです。iOS でアイテムを作成する方法については、こちらをご覧ください。

アプリ内のアイテムのリストを取得する

アイテムが作成されたら、購入を受付して処理するためのアーキテクチャに取り組みましょう。通常のプロセスは次のとおりです。

1. 請求ライブラリを追加する

2. Google Play のアイテムを操作するためのクラスを開発する

3. 購入を処理するすべてのメソッドを実装する

4. 購入のサーバー検証を追加する

5. 分析を収集する

このパートでは、最初の2つの手順について詳しく見ていきましょう。

Billing Libraryをプロジェクトに追加する:

implementation "com.android.billingclient:billing:4.0.0"

この記事の執筆時点での最新バージョンは 4.0.0 です。いつでも他のバージョンに置き換えることができます。

Google Playとの操作のロジックに対応するラッパークラスを作成し、その中のBilling LibraryからBillingClientを初期化します。このクラスをBillingClientWrapperと呼びます。

このクラスはPurchasesUpdatedListenerインターフェイスを実装します。そのためのメソッドonPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?)をオーバーライドします。これは購入直後に必要ですが、実装プロセスについては次の記事で説明します。

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

Googleでは、購入に関するコールバックが何度も実行されるのを防ぐために、BillingClientとGoogle Playの間に複数の有効な接続がないようにすることを推奨しています。したがって、シングルトンクラスには1つの一意のBillingClientが必要です。この例のクラスはシングルトンではありませんが、このように (たとえば、DaggerKoinを活用して) 依存性の注入を使用して、ある時点で1つのインスタンスのみが存在できるようにすることができます。

Billing Libraryでリクエストを行うには、BillingClientはリクエストが行われた時点でGoogle Playとの有効な接続を確立している必要がありますが、接続が失われる可能性があります。便宜上、接続が有効な場合にのみリクエストを実行できるようにするラッパーを作成しましょう。

アイテムを取得するには、マーケットで設定したIDが必要です。ただし、それだけではリクエストできず、アイテムタイプ (定期購入または1回限りの購入) も必要となります。そのため、2つのリクエストの結果を「統合」することで、アイテムの一般的なリストを取得できます。 

アイテムのリクエストは非同期であるため、アイテムのリストを提供するか、エラーモデルを返すコールバックが必要です。エラーが発生すると、Billing LibraryはいずれかのBillingResponseCodesとdebugMessageを返します。コールバックインターフェイスとエラーのモデルを作成しましょう。

interface OnQueryProductsListener {
  fun onSuccess(products: List < SkuDetails > )
  fun onFailure(error: Error)
}

class Error(val responseCode: Int, val debugMessage: String)

次に示すのは、特定のタイプのアイテムに関するデータを取得するためのプライベートメソッドと、2つのリクエストの結果を「統合」してユーザーにアイテムの最終的なリストを提供するか、エラーメッセージを表示するパブリックメソッドのコードです。

fun queryProducts(listener: OnQueryProductsListener) {
   val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

   queryProductsForType(
       skusList,
       BillingClient.SkuType.SUBS
   ) { billingResult, skuDetailsList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           val products = skuDetailsList ?: mutableListOf()
           queryProductsForType(
               skusList,
               BillingClient.SkuType.INAPP
           ) { billingResult, skuDetailsList ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                   products.addAll(skuDetailsList ?: listOf())
                   listener.onSuccess(products)
               } else {
                   listener.onFailure(
                       Error(billingResult.responseCode, billingResult.debugMessage)
                   )
               }
           }
       } else {
           listener.onFailure(
               Error(billingResult.responseCode, billingResult.debugMessage)
           )
       }
   }
}

private fun queryProductsForType(
   skusList: List<String>,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

このようにして、アイテムに関する貴重な情報 (SkuDetails) を取得できました。ローカライズされた名前、価格、アイテムタイプ、請求期間、定期購入のお試し価格と試用期間 (該当する場合) に関する情報を確認できます。最終的なクラスは次のようになります。

import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
   interface OnQueryProductsListener {
       fun onSuccess(products: List<SkuDetails>)
       fun onFailure(error: Error)
   }
   class Error(val responseCode: Int, val debugMessage: String)
   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()
   fun queryProducts(listener: OnQueryProductsListener) {
       val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
       queryProductsForType(
           skusList,
           BillingClient.SkuType.SUBS
       ) { billingResult, skuDetailsList ->
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               val products = skuDetailsList ?: mutableListOf()
               queryProductsForType(
                   skusList,
                   BillingClient.SkuType.INAPP
               ) { billingResult, skuDetailsList ->
                   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                       products.addAll(skuDetailsList ?: listOf())
                       listener.onSuccess(products)
                   } else {
                       listener.onFailure(
                           Error(billingResult.responseCode, billingResult.debugMessage)
                       )
                   }
               }
           } else {
               listener.onFailure(
                   Error(billingResult.responseCode, billingResult.debugMessage)
               )
           }
       }
   }
   private fun queryProductsForType(
       skusList: List<String>,
       @BillingClient.SkuType type: String,
       listener: SkuDetailsResponseListener
   ) {
       onConnected {
           billingClient.querySkuDetailsAsync(
               SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
               listener
           )
       }
   }
   private fun onConnected(block: () -> Unit) {
       billingClient.startConnection(object : BillingClientStateListener {
           override fun onBillingSetupFinished(billingResult: BillingResult) {
               block()
           }
           override fun onBillingServiceDisconnected() {
               // Try to restart the connection on the next request to
               // Google Play by calling the startConnection() method.
           }
       })
   }
   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

今回は以上です。次の記事では、購入の実装、テスト、エラー処理について説明します。