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

この記事からわかること
- Android Studio/Kotlinでアプリ内課金を実装する方法
- Google Play Billing Libraryの使い方
- アプリ内課金をテストするには?
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Meerkat
- Kotlin:2.0.21
- Billing Library:7.x
Google Play 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 で収益化する」 > [商品」 > 「アプリ内アイテム」へ遷移します。初めての場合は「販売アカウント」をセットアップしていないとアクセス権限がないので先にそちらを対応しておきます。お支払いプロファイル(氏名や住所など)を登録するだけです。

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

これはどうやら対象アプリの「AndroidManifest.xml」にcom.android.vending.BILLING
を追加してあるAPKをクローズドテストにアップしておかないといけないみたいです。
<uses-permission android:name="com.android.vending.BILLING" />
アップさえすればアイテムを追加できるようになったので「アイテムを作成」をクリックし、アイテムIDやアイテム名、説明、価格を入力して作成、有効化すれば準備は完了です。

購入テスト用のアカウント登録
公式リファレンス:アプリ ライセンスを使用したアプリ内課金のテスト
アプリ内課金を実装してテストを行う際に料金の支払いが都度発生していたら困ります。料金が発生しないようにテストを行うためには「アプリライセンス」にアカウントを登録しておく必要があります。
Google Play Consoleの「設定」 > 「ライセンステスト」で対象のアカウント(メールアドレス)を登録することができるのでここのメールリストに追加してあげればOKです。
またアプリ内課金の動作テストはエミュレーターではIllegalStateException
エラーが発生し商品情報を取得できないので実機で行う必要があります。
java.lang.IllegalStateException: Billing Setup failed: Billing service unavailable on device
テスト方法
登録したアカウントでログインしている実機にアプリをインストールしてテストします。他のアカウントでもログインしている場合はうまく動作しないことがあるのでログアウトして該当アカウントのみにしておいてください。アプリはAndroid Studioからのビルドでも配布ビルドでも問題ありません。
テストアカウントで購入処理を進めようとすると「これはテスト用の注文です。課金は発生しません。」と表示されていれば正常にテストアカウントとして認識されています。このまま購入を進めれば実際に購入テストができ、しばらくは購入履歴にも残りますが一定時間が経過すると購入履歴からなくなるようです。

アプリ内課金を実装するための手順
- Google Play Billing Libraryの導入 & パーミッションの定義
- BillingClientの生成
- Google Playの課金サービスへ接続する
- 商品の詳細情報を取得する
- 購入処理
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) {
// 購入フローの開始に失敗
}
購入フローの結果は先述したPurchasesUpdatedListener
のonPurchasesUpdated
から受け取ることができます。
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
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。