【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

この記事からわかること

  • Android Studio/Kotlinアプリ内課金実装する方法
  • Google Play Billing Library使い方
  • アプリ内課金をテストするには?

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Google Play Billing Library

公式リファレンス:Billing Library

Google Play Billing Library」はAndroidアプリにアプリ内課金を実装するためのライブラリです。公式がサポートしている純正のライブラリであり、Google Play Consoleに登録した課金アイテム情報を取得&購入できるようになるのでこのライブラリを使用しないとアプリ内課金の実装はできません。アプリ内課金は単発購入やサブスクリプションといった購入形態で実装することが可能になっています。

またこのライブラリはほぼ毎年新しいバージョンがリリースされていきます。しかもライブラリバージョンごとのサポート期間は2年であり、古いライブラリのままだとアプリ自体のアップデートができなくなるので注意してください。

2025年5月現在は「2024-05-14」にリリースされた「7系」が最新になっているようです。

アプリ内課金の種類

Androidアプリのアプリ内課金は大きく2つの種類に分かれています。

Google Play Consoleに課金アイテム(商品)を登録する

公式リファレンス:アプリ内アイテムの作成

最初にGoogle Play Consoleに課金アイテム(商品)を登録する必要があります。ここでアイテム名や値段、タイプ、IDなどを設定することができます。ID(※)は作成後に変更できないのでテストで作成する場合でも慎重に決める必要があります。
※ IDは先頭を必ず数字または英小文字とし、全体を数字(0~9)、小文字(a~z)、アンダースコア(_)、ピリオド(.)のみで構成する

アイテム登録手順

Google Play Consoleの対象アプリへ移動し、「Google Play で収益化する」 > [商品」 > 「アプリ内アイテム」へ遷移します。初めての場合は「販売アカウント」をセットアップしていないとアクセス権限がないので先にそちらを対応しておきます。お支払いプロファイル(氏名や住所など)を登録するだけです。

【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

販売アカウント」のセットアップが完了したら再度「アプリ内アイテム」メニューをタップすると今度は「アプリ内アイテムを追加するには、請求権限を APK に追加する必要があります」と表示されました。

【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

これはどうやら対象アプリの「AndroidManifest.xml」にcom.android.vending.BILLINGを追加してあるAPKをクローズドテストにアップしておかないといけないみたいです。


<uses-permission android:name="com.android.vending.BILLING" />

アップさえすればアイテムを追加できるようになったので「アイテムを作成」をクリックし、アイテムIDやアイテム名、説明、価格を入力して作成、有効化すれば準備は完了です。

【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

購入テスト用のアカウント登録

公式リファレンス:アプリ ライセンスを使用したアプリ内課金のテスト

アプリ内課金を実装してテストを行う際に料金の支払いが都度発生していたら困ります。料金が発生しないようにテストを行うためには「アプリライセンス」にアカウントを登録しておく必要があります。

Google Play Consoleの「設定」 > 「ライセンステスト」で対象のアカウント(メールアドレス)を登録することができるのでここのメールリストに追加してあげればOKです。

またアプリ内課金の動作テストはエミュレーターではIllegalStateExceptionエラーが発生し商品情報を取得できないので実機で行う必要があります。

java.lang.IllegalStateException: Billing Setup failed: Billing service unavailable on device

テスト方法

登録したアカウントでログインしている実機にアプリをインストールしてテストします。他のアカウントでもログインしている場合はうまく動作しないことがあるのでログアウトして該当アカウントのみにしておいてください。アプリはAndroid Studioからのビルドでも配布ビルドでも問題ありません。

テストアカウントで購入処理を進めようとすると「これはテスト用の注文です。課金は発生しません。」と表示されていれば正常にテストアカウントとして認識されています。このまま購入を進めれば実際に購入テストができ、しばらくは購入履歴にも残りますが一定時間が経過すると購入履歴からなくなるようです。

【Kotlin/Android】Google Play Billing Libraryでアプリ内課金の実装方法

アプリ内課金を実装するための手順

  1. Google Play Billing Libraryの導入 & パーミッションの定義
  2. BillingClientの生成
  3. Google Playの課金サービスへ接続する
  4. 商品の詳細情報を取得する
  5. 購入処理

1.Google Play Billing Libraryの導入 & パーミッションの定義

ライブラリを導入するために「build.gradle」にcom.android.billingclient:billingを追加します。Kotlinを使用する場合は拡張機能とコルーチンのサポートがcom.android.billingclient:billing-ktxに含まれているため一緒に導入しておきます。


dependencies {
    // Google Play Billing Library 7系
    def billing_version = "7.1.1"
    implementation "com.android.billingclient:billing:$billing_version"
    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

さらにインターネットアクセスの権限が必要になるため「AndroidManifest.xml」にandroid.permission.INTERNETを追加しておきます。


<uses-permission android:name="android.permission.INTERNET"/>

2.BillingClientの生成

続いて課金機能を操作するためのBillingClientインスタンスを生成します。setListenerにはPurchasesUpdatedListenerを継承したインスタンスを渡します。

billingClient = BillingClient.newBuilder(context)
    .setListener(PurchaseUpdateHandler)
    .enablePendingPurchases()
    .build()

PurchasesUpdatedListenerではonPurchasesUpdated購入の結果(成功やエラー、キャンセルなど)が取得できます。

object PurchaseUpdateHandler : PurchasesUpdatedListener {

    /** 購入の結果 */
    override fun onPurchasesUpdated(
        billingResult: BillingResult,
        purchases: MutableList<Purchase>?
    ) { }
}

3.Google Playの課金サービスへ接続する

BillingClientインスタンスが作成できたらまずはstartConnectionメソッドでGoogle Playの課金サービスへ接続する処理を実装します。接続が成功すれば商品情報の取得や購入状況などを取得できるようになります。

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(result: BillingResult) {
        if (result.responseCode == BillingClient.BillingResponseCode.OK) {
          // 接続成功
          // 後述する商品取得処理を実装
        } else {
          // 接続失敗
        }
    }

    override fun onBillingServiceDisconnected() {
        // BillingServiceから切断された
    }
})

4.商品の詳細情報を取得する

接続ができたら商品情報を取得します。取得したい商品情報をまずはQueryProductDetailsParamsで構築します。setProductIdにはGoogle Play Console側で指定したプロダクトIDをsetProductTypeにはタイプ(INAPPは買い切り / SUBSはサブスク)を指定します。

// 取得対象の商品情報を構築
val params = QueryProductDetailsParams.newBuilder()
    .setProductList(
        listOf(
            QueryProductDetailsParams.Product.newBuilder()
                .setProductId(productId)
                .setProductType(BillingClient.ProductType.INAPP)
                .build()
        )
    ).build()

実際の問い合わせはqueryProductDetailsAsyncメソッドを使用します。コールバックの引数から問い合わせ結果のステータスと成功すれば商品情報リストがProductDetails型で取得できます。

billingClient.queryProductDetailsAsync(params) { billingResult, productList ->
    // billingResult => 問い合わせ結果のステータス
    // productList => 成功すれば商品情報リスト
    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productList.isNotEmpty()) {
        // 取得した商品情報を保持しておく
        productDetailsList = productList
    } else {
        // 商品情報取得失敗
    }
}

5.購入処理

最後に購入処理です。まずは購入する商品をBillingFlowParamsに設定します。その後launchBillingFlowメソッドを実行することで自動的に購入フローがアプリに表示されユーザーが購入を確定するかキャンセルするのを待機する状態になります。

// 購入対象の商品を構築
val billingParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(
        listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(details)
                .build()
        )
    ).build()

// 購入フローの実行
// 自動的に購入フローを表示し
// ユーザーが購入を確定するかキャンセルするのを待機
val result = billingClient.launchBillingFlow(activity, billingParams)
// 正常にフローが開始されたかどうか
if (result.responseCode != BillingClient.BillingResponseCode.OK) {
  // 購入フローの開始に失敗
}

購入フローの結果は先述したPurchasesUpdatedListeneronPurchasesUpdatedから受け取ることができます。

object PurchaseUpdateHandler : PurchasesUpdatedListener {

    /** 購入の結果 */
    override fun onPurchasesUpdated(
        billingResult: BillingResult,
        purchases: MutableList<Purchase>?
    ) { 
        if (result.responseCode == BillingClient.BillingResponseCode.OK && !purchases.isNullOrEmpty()) {
           // 成功
        } else {
           // 失敗
        }
    }
}

これでアプリ内で商品を購入する実装が完了です。

購入済みの商品を取得する

ユーザーが購入済みのアイテムを取得するにはqueryPurchasesAsyncメソッドを実行します。結果はPurchase型のリスト形式で取得することができます。

val queryPurchasesParams = QueryPurchasesParams.newBuilder()
    .setProductType(ProductType.INAPP)
    .build()
// 購入済みアイテムを取得
billingClient.queryPurchasesAsync(queryPurchasesParams) { result, purchasesList ->
    if (result.responseCode == BillingClient.BillingResponseCode.OK) {
        Log.d("InApp", "購入済みアイテム:$purchasesList")
    } else {
        Log.d("InApp", "購入済みアイテム取得失敗")
    }
}

購入済みかどうかを識別するにはproductsプロパティに該当のIDがあるかどうかを確認すればOKです。

/** アイテムが購入済みかどうか */
public fun isPurchased(id: String): Boolean {
    return purchasesList.firstOrNull { it.products.contains(id) } != null
}

ProductDetailsクラス

商品情報はProductDetailsクラスとして取得することができます。

// 商品ID
productDetails.productId
// 商品タイトル(例:設定したタイトル(アプリ名))
productDetails.title
// 商品名(例:設定したタイトル名)
productDetails.name
// 商品説明(例:設定した説明)
productDetails.description
// 商品タイプ(例:INAPP(一度きりの購入)または SUBS(定期購入))
productDetails.productType

価格は商品タイプによって取得できるプロパティが異なります。


// 通貨(例: JPY)
productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode 
// 金額(例: ¥120)
productDetails.oneTimePurchaseOfferDetails?.formattedPrice
// マイクロ単位の価格(例: 120000000)
productDetails.oneTimePurchaseOfferDetails?.priceAmountMicros

productDetails.subscriptionOfferDetails?.forEach { offer ->
    offer.pricingPhases.pricingPhaseList.forEach { phase ->
        phase.priceAmountMicros
        phase.formattedPrice
        phase.billingPeriod
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article