何が変わり、なぜ今なのか

Google は、すべての Android アプリに課金コードを最新に保つことを求めています。2026 年 8 月 31 日までに、 新規アプリと既存アプリのすべてのアップデートは Google Play Billing Library v8 以降を使う必要があります (2026 年 5 月にリリースされた v9 も条件を満たします)。Play Console では、2026 年 11 月 1 日までの一度きりの延長を申請できます。 これは公開のゲートです。インストール済みのアプリはそのまま動きますが、v8 以降にしない限り、アップデートは一切公開できません。

やっかいなのは、これが毎年起きることです。2026 年は v8 が下限、2027 年は v9、と続きます。 バージョンが上がるたびに API が削除され、コードの変更を迫られます。Google 自身の Play Billing コードラボもいまだに v5 のままで、 「非推奨」バナーが付いています。このスピード感がよくわかる例です。

このコードラボでは、長く効く解決策を示します。RevenueCat へ移行すれば、RevenueCat が Billing Library を管理してくれるので、二度とバージョンを追いかけずに済みます。次の作業を進めます。

  • v8 と v9 が削除したものを正確に確認します(手作業で BillingClient にパッチを当てる場合でも役立ちます)。
  • RevenueCat を追加し、launchBillingFlow による購入コードを置き換えます。
  • 既存のサブスクライバーを移行し、有料ユーザーがアクセスを失わないようにします。
最初にやること:RevenueCat を Google Play に接続する。 このコードラボは、Google Play Store に接続済みの RevenueCat プロジェクトがあり、サービスアカウントの認証情報が構成され、商品がインポート済みであることを前提としています。 まだセットアップしていない場合は、先に RevenueCat Google Play 連携コードラボを完了してから、ここへ戻ってきてください。
対象読者:現在、素の Google Play Billing Library (BillingClient) を直接組み込んでいる Android アプリの開発者です。例はすべて Kotlin です。

移行の 2 つの道

期限をクリアする道は 2 つあります。どちらも有効ですが、このコードラボでは 2 つ目をおすすめします。

やること 翌年
自分で BillingClient を v8 にパッチする 削除された API(ステップ 3)を自分の課金コードで書き直す v9、v10 と、また同じことをやる
RevenueCat へ移行する 課金コードを一度だけ置き換える。Billing Library は RevenueCat が持つ RevenueCat SDK を更新する。BillingClient の書き直しは不要

単一の商品しか販売しておらず、ペイウォール、実験、クロスプラットフォーム対応を追加する予定が今後もないなら、 手作業でのパッチでも問題ありません。ただ大半のアプリでは、1 つ目の道にかかる毎年のコストこそが、2 つ目を選ぶ理由になります。

一度にすべてやる必要はありません。 RevenueCat は段階的な移行(オブザーバーモード)に対応しており、 まず既存の課金コードと並行して動かせます。これはステップ 9 で扱います。

v8 と v9 で壊れたもの

参考までに、素の Billing Library が v8(2025 年 6 月 30 日リリース)で削除したものを挙げます。手作業でパッチを当てるなら、 これが作業リストです。RevenueCat へ移行するなら、逆にこのコードを削除することになります。

v8 で削除 置き換え先
querySkuDetailsAsync()SkuDetails queryProductDetailsAsync()ProductDetailsQueryProductDetailsParams
queryPurchaseHistoryAsync()PurchaseHistoryRecord queryPurchasesAsync()(アクティブなサブスクリプションと未消費の買い切り購入のみ。無効化された履歴にはサーバーの Voided Purchases API が必要)
enablePendingPurchases()(引数なし) enablePendingPurchases(PendingPurchasesParams)
ProrationMode setSubscriptionReplacementMode() 経由の ReplacementMode
setSkuDetails() setProductDetailsParamsList()

v9(2026 年 5 月 19 日)の変更は小さいものの、それでもコードに影響します。ブロックされた Play Store のエラーが ERROR から BILLING_UNAVAILABLE に変わり、外部支払いのフィールドが nullable になり、 ターゲット SDK が 35 に引き上げられました。(onProductDetailsResponse リスナーのシグネチャ変更と、新しい課金サブレスポンスコードは v8 の時点で入っています。)最小 SDK は v8.1 で API 23 (Android 6.0) に上がりました。

このパターンは終わりません。 この多くが、アプリ本体とは無関係な配管作業だという点に注目してください。 それこそ RevenueCat が吸収してくれる作業です。

RevenueCat が終わらせる仕組み

RevenueCat Android SDK は、Google Play Billing Library を同梱して管理します。現行の SDK は推移的依存として Billing Library 8.3.0 を同梱しているので、com.android.billingclient:billing を自分で追加する必要はなくBillingClient を呼び出すこともまったくありません。

これが毎年の期限にとって意味するのは次のことです。

  • 今: RevenueCat SDK を一度だけ導入します(このコードラボの残りの内容)。
  • 翌年、v9 や v10 が必須になったとき: RevenueCat SDK のバージョンを上げるだけです。BillingClient の書き直しも、削除された API 探しもありません。

さらに、本来なら自分で作ることになるものも手に入ります。単一の情報源となる customerInfo.entitlements、 サーバー側のレシート検証、Webhook、そして後から使える Paywalls、Experiments、Customer Center です。

購入の管理者は 1 つだけ。 RevenueCat が Billing Library を持つため、Google への購入の承認も RevenueCat が代わりに行います。 同じ購入を自分のコードでも承認してはいけません(Google は 3 日以内に承認されない購入を自動で返金します)。 2 つの連携を同時に走らせないための方法は、ステップ 9 で扱います。

RevenueCat を追加して構成する

アプリモジュールの Gradle ファイルに SDK を追加します(最新バージョンを使ってください)。

kotlin
// app/build.gradle.kts
dependencies {
    implementation("com.revenuecat.purchases:purchases:10.10.0")
    // Optional: paywalls + Customer Center (requires minSdk 24)
    // implementation("com.revenuecat.purchases:purchases-ui:10.10.0")
}
// Remove your old: implementation("com.android.billingclient:billing:...")

この SDK は minSdk 23 (Android 6.0) が必要です。Application クラスで一度だけ構成します。

kotlin
// MainApplication.kt
class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Purchases.logLevel = LogLevel.DEBUG
        Purchases.configure(
            PurchasesConfiguration.Builder(this, "goog_YOUR_PUBLIC_SDK_KEY")
                // .purchasesAreCompletedBy(PurchasesAreCompletedBy.REVENUECAT) // default: full migration
                .build()
        )
    }
}
公開鍵を使う。 Google Play の公開 SDK キーは goog_ で始まり、RevenueCat ダッシュボードの Project Settings > API keys にあります。シークレットキーをアプリに埋め込んではいけません。 デフォルトの PurchasesAreCompletedBy.REVENUECAT は、RevenueCat が購入を完了(承認)することを意味します。 MY_APP に切り替えるのは、ステップ 9 の段階的な道の場合だけにしてください。

課金の概念を対応づける

RevenueCat は、ハードコードした SKU と独自の「プレミアムかどうか」の判定を、小さくて長く使えるモデルに置き換えます。次の要素をダッシュボードで構成します。

素の Billing Library RevenueCat
SKU / ProductDetails(例: premium_monthly プロダクト(RevenueCat にインポートしたストア商品)
(SKU リストをハードコードしていた) オファリング(提示する内容)の中の パッケージ(月額/年額といった役割)
独自の isPremium ブール値 エンタイトルメント(例: pro)。customerInfo 経由で確認

ダッシュボードでは、既存の Google Play 商品をインポートし、エンタイトルメント(たとえば pro)を作成して、各プロダクトをそれに Attach します。

これが移行の半分です。 プロダクトをエンタイトルメントに紐付けると、そのプロダクトを過去に購入したすべての顧客に、 そのエンタイトルメントが付与されます。つまり既存サブスクライバーの購入を RevenueCat が把握すれば(ステップ 8)、 その人の pro アクセスは自動的に復元されます。

購入フローを置き換える

queryProductDetailsAsync / launchBillingFlow / 承認のコードを削除し、 2 つの呼び出しに置き換えます。まず、何を販売するかを取得します。

kotlin
// Coroutines: fetch the current offering and its packages
val offerings = Purchases.sharedInstance.awaitOfferings()
val packages = offerings.current?.availablePackages.orEmpty()
// Show `packages` in your UI (each pkg.product carries the localized price)

次に、ユーザーが選んだパッケージから購入します。

kotlin
try {
    val result = Purchases.sharedInstance.awaitPurchase(
        PurchaseParams.Builder(activity, selectedPackage).build()
    )
    val customerInfo = result.customerInfo
    if (customerInfo.entitlements["pro"]?.isActive == true) {
        // Unlock pro. RevenueCat already acknowledged the purchase with Google.
    }
} catch (e: PurchasesTransactionException) {
    if (!e.userCancelled) {
        // Show a real error; userCancelled just means the user backed out.
    }
}

コルーチンを使っていない場合は、コールバック形式の同等版 (getOfferingsWithpurchaseWith) もあります。

承認コードはもう不要です。 デフォルト構成では、RevenueCat が Google とのトランザクションを完了させます。 二重に承認しないよう、以前の acknowledgePurchase / consumeAsync の呼び出しは削除してください。

RevenueCat の SDK は初めてですか? Android コードラボ商品と価格の取得ガイドで、さらに詳しく解説しています。

既存のサブスクライバーを移行する

ここがもっとも重要な部分です。現在の有料ユーザーがアクセスを失ってはいけません。作業は 2 つあり、両方とも行うのが望ましいです。

1. 新しい購入: 自動で追跡する

Google Play アカウントをすでに接続したうえで(Google Play 連携コードラボを参照)、 Google Play のサーバー通知を設定し、サーバー間通知から新しい購入を追跡をオンにします。すると RevenueCat は、 SDK をまだ含まないアプリバージョンからのものも含め、すべての新しい購入を記録します。

2. 既存の購入: 一度だけ同期する

以前の BillingClient コードですでにサブスクライブしたユーザーには、RevenueCat 対応ビルドの初回起動時に syncPurchases()一度だけ呼び出します。これは OS のサインインを求めることなく、既存の Google Play 購入を RevenueCat に送ります。ステップ 6 のプロダクトとエンタイトルメントの紐付けと組み合わせれば、その人の pro アクセスは自動的に復元されます。

kotlin
// Run ONCE per subscriber, the first time they open the RevenueCat build.
if (!prefs.getBoolean("rc_migrated", false)) {
    Purchases.sharedInstance.syncPurchasesWith(
        onError = { /* retry later */ },
        onSuccess = { _ -> prefs.edit().putBoolean("rc_migrated", true).apply() }
    )
}
同期は毎回ではなく一度だけ。 すべてのユーザーで起動のたびに syncPurchases を呼ぶと、 レイテンシが増え、顧客を意図せず 1 つに統合(エイリアス化)してしまうことがあります。一度きりのフラグで制御してください (または、以前のシステムが「サブスクライブ済み」と言うのに RevenueCat がそうでないときだけ呼びます)。 restorePurchases()(OS のサインインを促すことがあります)は、ユーザーがタップする「復元」ボタンの裏側でのみ使ってください。
安定した App User ID を使う。 アプリに独自のアカウントがあるなら、その user id (appUserID) で RevenueCat を構成するか、同期の前に Purchases.sharedInstance.logIn(userId) を呼び、同期した購入が匿名 ID ではなく 正しい顧客に紐付くようにしてください。
履歴データには制限があります。 現行の SDK では、syncPurchasesアクティブなサブスクリプションと 未消費の買い切り購入だけを同期します。期限切れや過去の購入を埋め戻す(正確なグラフや生涯履歴のため)には、 ダッシュボードで Google Historical Import を実行してください。これは 2023 年 7 月まで遡って履歴を取り込みますが、 90 日以上前に期限切れになった購入トークンには既知の欠落があります。

ライセンステスターでテストする

リリース前に、課金されることなく実機で移行を検証します。

  1. Play Console で Setup > License testing にテスターを追加し、ビルドを Internal または Closed testing トラックに公開します。
  2. 既存のサブスクリプションを持つテスターとして、新しい RevenueCat ビルドをインストールし、一度きりの syncPurchases が実行され、pro エンタイトルメントがアクティブになることを確認します。
  3. 新規のテスターとして、新しいフローで購入を完了し、エンタイトルメントがアンロックされ、トランザクションが RevenueCat に表示されることを確認します。
  4. RevenueCat ダッシュボードで該当の顧客を確認し、購入とエンタイトルメントが記録されているかを確かめます。

段階的なロールアウトがよい? オブザーバーモードを使う

課金コードを 1 回のリリースで削除する準備ができていない場合は、RevenueCat を PurchasesAreCompletedBy.MY_APP で構成します。すると、既存のコードが購入を完了(承認)しつつ、 RevenueCat が購入とエンタイトルメントを記録します。以前の購入コードを削除したら、デフォルトの PurchasesAreCompletedBy.REVENUECAT に切り替えてください。

両方に承認させない。 購入を完了させる側は、自分のコード(オブザーバーモード)か RevenueCat(完全移行)の どちらか一方だけにし、決して両方にしてはいけません。二重の承認と二重の連携は、原因究明が難しい微妙なバグを引き起こします。

まとめと得られるもの

Android アプリを素の Billing Library から RevenueCat へ移行しました。

  • BillingClient(と v8 で削除された API に伴うすべての手直し)を getOfferings + purchase に置き換えました。
  • アクセスの判定を、手作りの SKU ロジックではなく、単一の pro エンタイトルメントとしてモデル化しました。
  • 一度きりの syncPurchases とプロダクトとエンタイトルメントの紐付けで既存サブスクライバーを移行し、Historical Import で履歴を埋め戻しました。

得られるもの: 2026 年 8 月 31 日の期限をクリアし、v9、v10 とそれ以降が必須になっても、RevenueCat SDK を上げるだけで済みます。 毎年の Billing Library 移行はもう不要です。

次に進む