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

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

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

定期購入の作成

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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?) {
       // here come callbacks about new purchases
   }
}

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

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

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

Start for free

Convenient in-app purchases infrastructure.

Adapty SDK has it all:
— server-side purchase validation,
— all side cases covered,
— simple implementation.

Start for free

アイテムのリクエストは非同期であるため、アイテムのリストを提供するか、エラーモデルを返すコールバックが必要です。エラーが発生すると、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,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

このようにして、アイテムに関する貴重な情報 (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?) {
       // here come callbacks about new purchases
   }
}

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

Further reading

Marketing company with $1.5M MRR from mobile apps
Marketing company with $1.5M MRR from mobile apps
November 25, 2021
12 min read, 42 min listen
First steps to better monetization
First steps to better monetization
February 15, 2022
38 min listen
How to integrate Amplitude Analytics into your iOS app
How to integrate Amplitude Analytics into your iOS app
March 8, 2021
8 min read