Android におけるサブスクリプションのマネタイズは、ここ数年で大きく進化してきました。2022 年にベースプランとオファーが導入されたことで、Google は開発者に対し、サブスクリプション商品をより柔軟に設計できる仕組みを提供しました。そして今回、Google は Subscription with Add-ons(マルチラインサブスクリプション、またはサブスクリプション向けマルチプロダクトチェックアウトとも呼ばれます)という新たな大きな一歩を踏み出しました。この機能により、複数のサブスクリプション商品を 1 回の購入にまとめて提供でき、ユーザーと開発者の双方にとって、よりシンプルで分かりやすい体験を実現できます。

この記事では、マルチラインサブスクリプションとは何かを整理したうえで、その効果的な活用戦略を紹介し、Play Billing Library を直接使った実装方法を順を追って解説します。最後に、RevenueCat を使うことで、この一連のプロセスをどのようにシンプルにできるのかを見ていきます。

マルチラインサブスクリプションとは?

Subscription with Add-ons とは、複数のサブスクリプション商品を 1 つにまとめ、単一のサブスクリプションとして購入・請求・管理できるようにする機能です。ベースとなるサブスクリプションと追加のプレミアム機能を、それぞれ別々に購入させる必要はなく、すべてを 1 回のチェックアウトフローで提供できます。

たとえば、ベースとなる「Premium」プランを提供している音楽ストリーミングアプリを考えてみましょう。これまでは、「HiFi オーディオ」や「オフライン再生」といった追加機能を有料アドオンとして提供する場合、ユーザーはそれぞれを個別に購入・管理する必要がありました。その結果、決済は複数回に分かれ、更新日もバラバラになり、サブスクリプション管理画面には複数の項目が並ぶことになります。Subscription with Add-ons を使えば、ユーザーは「Premium」プランをベースに、必要なアドオンを自由に組み合わせて選択し、1 回の購入で完了できます。表示される価格は 1 つにまとまり、チェックアウトも 1 回だけ、更新日はすべて同一の日付に同期されます。

なお、用語は少し分かりづらいかもしれません。Google では正式名称として 「Subscription with Add-ons」 を使っていますが、1 回の購入に複数の明細(ラインアイテム)が含まれることから 「マルチラインサブスクリプション」、あるいはチェックアウト体験に焦点を当てて 「マルチプロダクトチェックアウト」 と呼ばれることもあります。いずれも、同じ機能を指しています。

バンドルの仕組み

ユーザーが Subscription with Add-ons を購入すると、商品リストの先頭にあるアイテムがベースアイテムとなり、それ以降に続くすべてのアイテムはアドオンとして扱われます。この区別は重要で、ベースアイテムがバンドル全体の挙動を決定します。たとえば、ベースとなるサブスクリプションが解約されると、紐づいているすべてのアドオンも自動的に解約されます。アドオンは、ベースサブスクリプションなしでは単独で存在することはできません。

請求サイクルの調整といった複雑な処理は、Google Play が自動的に行います。既存のサブスクリプションに新しいアドオンを追加した場合、Google Play はアドオンの更新日がベースアイテムと一致するように、日割り計算による請求額を算出します。その結果、最初の調整期間が終わると、バンドル内のすべてのアイテムが同じ更新日にまとめて更新されるようになります。同様に、ユーザーがアドオンを削除した場合でも、そのアドオンは現在の請求期間の終了までは引き続き利用できますが、次回の更新は行われません。

理解しておくべき主な制約

実装に進む前に、この機能の使い方を左右する重要な制約がいくつかあります。

  1. すべてのアイテムは同一の請求期間である必要があります。Subscription with Add-ons に含めるすべてのアイテムは、同じ請求期間でなければなりません。年額のベースサブスクリプションに月額のアドオンを組み合わせる、またはその逆はできません。ベースプランが月額課金の場合、すべてのアドオンも月額である必要があります。これは、Google Play がすべてのアイテムの更新日を同期させる必要があるためです。
  2. 自動更新サブスクリプションのみ対応しています。この機能は自動更新型サブスクリプションでのみ利用できます。期間が固定され、自動更新されないプリペイドサブスクリプションは、ベースアイテムとしてもアドオンとしても使用できません。
  3. 1 回の購入あたり最大 50 アイテムまでという上限があります。Subscription with Add-ons の 1 回の購入に含められるアイテム数は最大 50 件です。多くのアプリではこの上限に達することはありませんが、高度にモジュール化されたサブスクリプション設計を検討している場合は把握しておく必要があります。
  4. すべての地域で利用できるわけではありません。現時点のドキュメントでは、インドと韓国では Subscription with Add-ons がサポートされていません。これらの地域のユーザーには、代替となる購入フローを用意する必要があります。
  5. 一時停止と再開はサポートされていません。アドオンを含むサブスクリプションでは、サブスクリプションの一時停止および再開機能を利用できません。アプリがこの機能に大きく依存している場合、アドオンによるメリットがこの制約を上回るかどうかを慎重に検討する必要があります。

マルチラインサブスクリプションの活用戦略

仕組みを理解することと、この機能を効果的に活用することは別物です。実際に価値を最大化するには、マネタイズ戦略全体を踏まえて考える必要があります。ここでは、Subscription with Add-ons を活用するうえで参考になる、いくつかのアプローチを紹介します。

モジュール型機能戦略

最も分かりやすい活用方法のひとつが、ユーザーが必要な機能だけを選んで構成できるモジュール型サブスクリプションです。Basic / Standard / Premium といった事前定義された複数のプランを用意する代わりに、ベースとなるサブスクリプションと、ユーザーが自由に組み合わせられるアドオンの一覧を提供します。

たとえば、生産性向上アプリであれば、タスク管理や基本的なコラボレーション機能を含むベースサブスクリプションを用意し、アドオンとして高度なレポート機能、チーム管理機能、外部サービス連携、ストレージ容量の追加などを提供できます。高度なレポートだけが必要なユーザーはその機能のみを追加し、パワーユーザーは複数のアドオンを組み合わせる、といった使い方が可能です。このアプローチでは、「本当に必要なものにだけ支払っている」という感覚をユーザーに与えられるため、コンバージョン率の向上が期待できます。

この戦略を成功させるうえで重要なのは、ベースサブスクリプション単体でも十分な価値を提供できていることです。ベースが物足りなかったり不完全に感じられると、アドオンが柔軟性ではなく「小刻みな追加課金(ニッケル・アンド・ダイム)」として受け取られてしまう可能性があります。

プレミアムアップグレード戦略

もうひとつのアプローチは、アドオンを使ってユーザーを段階的にアップグレードしていく戦略です。まずはベースサブスクリプションで利用を開始してもらい、その後、利用状況やユーザー体験の節目に応じて、アドオンをアップセルとして提案します。

たとえば写真編集アプリの場合、ベースサブスクリプションには標準的な編集ツールを含め、利用が進むにつれて、プロ向けプリセット、高度なレタッチ機能、編集後の写真を保存するためのクラウドストレージといったアドオンを提供できます。従来のプラン階層を切り替えるアップグレードと異なり、アドオンであれば、ユーザーは既存の機能を維持したまま新しい機能を追加できます。そのため、「今のプランを失う」という感覚が生まれにくい点が大きな利点です。

この戦略は、パーソナライズされたレコメンデーションと組み合わせることで、特に効果を発揮します。ユーザー行動を分析することで、そのユーザーが価値を感じやすいタイミングで、適切なアドオンを提示できるようになります。

バンドル割引戦略(Bundle and Save)

Subscription with Add-ons では機能を個別に選択できる一方で、魅力的なバンドルを提供するという使い方も可能です。価格設定を工夫することで、アドオンを単体で購入するよりも、まとめて購入したほうがお得に感じられる構成を作れます。

たとえば、ベースサブスクリプションが月額 $9.99、アドオンがそれぞれ月額 $4.99 のものが3つある場合、個別に購入すると合計は $14.97 になります。これを、3つのアドオンをまとめたバンドルとして月額 $11.99 で提供すれば、複数の機能を求めるユーザーにとっては明確にお得な選択肢になります。このように、ユーザーはより多くの機能を割安で利用でき、事業者側は ARPU(ユーザーあたりの平均収益)を引き上げる ことができます。また、1つだけアドオンを購入するユーザーよりも高い収益を確保できる点も、この戦略のメリットです。

複雑さの管理

アドオンの活用におけるリスクのひとつは、選択肢が多くなりすぎてしまうことです。あまりにも多くのオプションが並ぶと、ユーザーは何を選べばよいのか分からなくなり、意思決定ができずに購入そのものをやめてしまう可能性があります。そのため、アドオンの数は通常 3〜5個程度の扱いやすい範囲に絞ることを検討するとよいでしょう。また、それぞれのアドオンについて「何が含まれているのか」「どのようなユーザーに向いているのか」を明確に説明することが重要です。さらに、おすすめのバンドルや「すべて選択」といったオプションを用意すれば、フル機能を求めるユーザーにとって、選択の手間を減らしつつ、スムーズに購入へ進めるようになります。

RevenueCat を使わずに Subscription with Add-ons を実装する

ここからは、Google Play Billing Library を直接使用して Subscription with Add-ons を実装する方法を順に見ていきましょう。この実装には Play Billing Library v5 以上 が必要です。ただし、すべての機能とセキュリティアップデートに確実にアクセスするために、最新バージョン(現時点では v8) を使用することをおすすめします。

Google Play Console でプロダクトの設定を行う

コードを書く前に、Google Play Console でサブスクリプションプロダクトを設定する必要があります。朗報なのは、既存のサブスクリプションプロダクトは特別な設定なしでアドオンとして提供できるという点です。別途「アドオン用」のプロダクトタイプを作成する必要はありません。自動更新サブスクリプションであれば、ベースアイテムにもアドオンにもなり得ます。

プロダクトを作成する際は、バンドルに含めたいすべてのアイテムが同じ請求期間でそろっている必要があることを忘れないでください。月額と年額の両方を提供したい場合は、請求期間ごとに別々のベースプランを作成する必要があります。

プロダクト詳細を取得する

実装の最初のステップは、Google Play から利用可能なプロダクトを問い合わせることです。提供したいすべてのサブスクリプションプロダクトの詳細を取得するために、 queryProductDetailsAsync メソッドを使用します。

1class BillingManager(private val context: Context) {
2    private lateinit var billingClient: BillingClient
3
4    private val productIds = listOf(
5        "premium_base_monthly",
6        "hifi_addon_monthly",
7        "offline_addon_monthly",
8        "family_addon_monthly"
9    )
10
11    fun initialize() {
12        billingClient = BillingClient.newBuilder(context)
13            .setListener { billingResult, purchases ->
14                handlePurchasesUpdated(billingResult, purchases)
15            }
16            .enablePendingPurchases()
17            .build()
18
19        billingClient.startConnection(object : BillingClientStateListener {
20            override fun onBillingSetupFinished(billingResult: BillingResult) {
21                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
22                    querySubscriptionProducts()
23                }
24            }
25
26            override fun onBillingServiceDisconnected() {
27// Implement retry logic here
28            }
29        })
30    }
31
32    private fun querySubscriptionProducts() {
33        val productList = productIds.map { productId ->
34            QueryProductDetailsParams.Product.newBuilder()
35                .setProductId(productId)
36                .setProductType(BillingClient.ProductType.SUBS)
37                .build()
38        }
39
40        val params = QueryProductDetailsParams.newBuilder()
41            .setProductList(productList)
42            .build()
43
44        billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
45            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
46// Store product details for later use when launching purchase flow
47                handleProductDetails(productDetailsList)
48            }
49        }
50    }
51}

このクエリは、各サブスクリプションプロダクトに対応する ProductDetails オブジェクトを返します。これらのオブジェクトには、ユーザーに価格を表示したり、購入フローを開始したりするために必要なすべての情報が含まれています。具体的には、利用可能なベースプランやオファー、そのオファーに紐づくオファートークンなどが含まれます。

複数アイテムを含む購入フローの開始

ユーザーが希望するサブスクリプション構成(ベースサブスクリプション+選択したアドオン)を選択したら、複数の ProductDetailsParams オブジェクトを指定して購入フローを起動します。ここで重要なのは、リストの先頭にあるアイテムがベースアイテムとして扱われるという点です。そのため、必ずベースとなるサブスクリプションを最初に追加する ようにしてください。

1fun launchSubscriptionWithAddons(
2    activity: Activity,
3    baseProductDetails: ProductDetails,
4    baseOfferToken: String,
5    addonProductDetailsList: List<Pair<ProductDetails, String>>
6) {
7    val productDetailsParamsList = mutableListOf<BillingFlowParams.ProductDetailsParams>()
8
9// Add the base subscription first - this is crucialval baseParams = BillingFlowParams.ProductDetailsParams.newBuilder()
10        .setProductDetails(baseProductDetails)
11        .setOfferToken(baseOfferToken)
12        .build()
13    productDetailsParamsList.add(baseParams)
14
15// Add each selected add-onfor ((addonDetails, offerToken) in addonProductDetailsList) {
16        val addonParams = BillingFlowParams.ProductDetailsParams.newBuilder()
17            .setProductDetails(addonDetails)
18            .setOfferToken(offerToken)
19            .build()
20        productDetailsParamsList.add(addonParams)
21    }
22
23    val billingFlowParams = BillingFlowParams.newBuilder()
24        .setProductDetailsParamsList(productDetailsParamsList)
25        .build()
26
27    val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
28
29    if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
30// Handle error - perhaps show a message to the user
31        handleBillingError(billingResult)
32    }
33}

リスト内の各アイテムは 一意である必要があります。同じプロダクト ID を持つ ProductDetailsParams オブジェクトを複数含めることはできません。また、オファートークンは、そのアイテムに対してどのベースプランまたはオファーを使用するかを指定するものです。 ProductDetails.subscriptionOfferDetails() メソッドから取得した 有効なオファートークン を必ず指定する必要があります。

購入の処理

ユーザーが購入フローを完了すると、 PurchasesUpdatedListener が結果を受け取ります。Subscription with Add-ons の処理は、基本的には単一のサブスクリプションを処理する場合と同じですが、ひとつ重要な違いがあります。それは、この購入によって 複数のアイテム分のエンタイトルメントが付与される という点です。

1private fun handlePurchasesUpdated(
2    billingResult: BillingResult,
3    purchases: List<Purchase>?
4) {
5    when (billingResult.responseCode) {
6        BillingClient.BillingResponseCode.OK -> {
7            purchases?.forEach { purchase ->
8                processPurchase(purchase)
9            }
10        }
11        BillingClient.BillingResponseCode.USER_CANCELED -> {
12// User canceled the purchase flow
13        }
14        else -> {
15// Handle other error codes
16        }
17    }
18}
19
20private fun processPurchase(purchase: Purchase) {
21// For Subscription with Add-ons, getProducts() returns all product IDsval purchasedProductIds = purchase.products
22
23// Verify the purchase with your backend server
24    verifyPurchaseWithBackend(purchase) { isValid ->
25        if (isValid) {
26// Grant entitlements for all purchased productsfor (productId in purchasedProductIds) {
27                grantEntitlement(productId)
28            }
29
30// Acknowledge the purchase if not already acknowledgedif (!purchase.isAcknowledged) {
31                acknowledgePurchase(purchase)
32            }
33        }
34    }
35}
36
37private fun acknowledgePurchase(purchase: Purchase) {
38    val params = AcknowledgePurchaseParams.newBuilder()
39        .setPurchaseToken(purchase.purchaseToken)
40        .build()
41
42    billingClient.acknowledgePurchase(params) { billingResult ->
43        if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
44// Handle acknowledgment failure
45        }
46    }
47}

purchase.products プロパティには、その購入に含まれるすべてのプロダクト ID のリストが返されます。それぞれのプロダクトに対してエンタイトルメントを付与するとともに、必ずバックエンドサーバーで購入の検証を行ったうえで処理するようにしてください。

既存の Subscription with Add-ons を変更する

ユーザーは、既存のサブスクリプションに 新しいアドオンを追加したり、不要になったアドオンを削除したりしたい場合があります。既存の Subscription with Add-ons を変更する際には、現在の購入トークン(purchase token)を指定し、あわせて 置き換えモード(replacement mode) を設定する必要があります。

1fun modifySubscriptionAddons(
2    activity: Activity,
3    currentPurchaseToken: String,
4    baseProductDetails: ProductDetails,
5    baseOfferToken: String,
6    newAddonsList: List<Pair<ProductDetails, String>>
7) {
8    val productDetailsParamsList = mutableListOf<BillingFlowParams.ProductDetailsParams>()
9
10// Include the base subscription
11    productDetailsParamsList.add(
12        BillingFlowParams.ProductDetailsParams.newBuilder()
13            .setProductDetails(baseProductDetails)
14            .setOfferToken(baseOfferToken)
15            .build()
16    )
17
18// Include all add-ons (both existing ones to keep and new ones to add)for ((addonDetails, offerToken) in newAddonsList) {
19        productDetailsParamsList.add(
20            BillingFlowParams.ProductDetailsParams.newBuilder()
21                .setProductDetails(addonDetails)
22                .setOfferToken(offerToken)
23                .build()
24        )
25    }
26
27// Configure the subscription updateval subscriptionUpdateParams = BillingFlowParams.SubscriptionUpdateParams.newBuilder()
28        .setOldPurchaseToken(currentPurchaseToken)
29        .setSubscriptionReplacementMode(
30            BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE
31        )
32        .build()
33
34    val billingFlowParams = BillingFlowParams.newBuilder()
35        .setProductDetailsParamsList(productDetailsParamsList)
36        .setSubscriptionUpdateParams(subscriptionUpdateParams)
37        .build()
38
39    billingClient.launchBillingFlow(activity, billingFlowParams)
40}

サブスクリプションを変更する際は、変更後も有効なままにしたいすべてのアイテムをリストに含める必要があります。たとえば、ベースサブスクリプションと既存のアドオンを維持したまま新しいアドオンを追加したい場合は、その 3つすべてをプロダクトリストに含める必要があります。既存のアドオンをリストから除外すると、そのアドオンは削除されます。

置き換えモード(replacement mode) は、請求の切り替えを Google Play がどのように処理するかを決定します。 CHARGE_PRORATED_PRICE は、新しく追加されたアイテムの更新日がベースサブスクリプションと揃うように、日割り計算による請求を行います。ユースケースに応じて、 CHARGE_FULL_PRICEWITHOUT_PRORATION といった他のモードを使用することも可能です。.

サーバーサイドでの検証とリアルタイム通知

本番環境のアプリでは、バックエンドサーバーで購入を検証し、Real-Time Developer Notifications(RTDN) を処理して、Google Play とエンタイトルメント情報を常に同期させる必要があります。

Subscription with Add-ons に関して注意すべき重要な点があります。複数アイテムを含む購入では、RTDN メッセージに subscriptionId フィールドが含まれません。これは、ひとつの購入に複数のサブスクリプションが含まれるためです。その代わりに、通知に含まれる purchaseToken を使用して Google Play Developer API に問い合わせ、付与されているすべてのアイテムの一覧を取得する必要があります。

1// This is for your backend serverfun handleRealTimeNotification(notification: DeveloperNotification) {
2    val purchaseToken = notification.subscriptionNotification.purchaseToken
3
4// Query the Google Play Developer API for full purchase detailsval subscriptionPurchase = playDeveloperApi
5        .purchases()
6        .subscriptionsv2()
7        .get(packageName, purchaseToken)
8        .execute()
9
10// The lineItems field contains all items in the subscriptionval lineItems = subscriptionPurchase.lineItems
11
12    for (item in lineItems) {
13        val productId = item.productId
14        val expiryTime = item.expiryTime
15        val autoRenewingPlan = item.autoRenewingPlan
16
17// Update your entitlement database based on each item's status
18        updateEntitlement(purchaseToken, productId, expiryTime, autoRenewingPlan)
19    }
20}

API レスポンスに含まれる lineItems リストには、各サブスクリプションアイテムの詳細が含まれています。具体的には、プロダクト ID、有効期限、更新ステータスなどの情報です。これにより、サブスクリプションバンドルを構成する 各アイテムごとにエンタイトルメントを個別に管理・追跡することが可能になります。

エッジケースの取り扱い

Subscription with Add-ons を実装する際には、いくつか注意すべきエッジケースがあります。

猶予期間(グレース期間)やアカウント保留は、サブスクリプションバンドル全体に適用されます。更新時の支払いが失敗した場合、どのアイテムで失敗が発生したかに関係なく、すべてのアイテムが同時にリカバリー期間に入ります。なお、猶予期間の長さは、アクティブなアイテムの中で 最も短い猶予期間設定を持つアイテム によって決まります。

一方で、返金や失効(revoke)はアイテム単位で処理可能です。Google Play Developer API を使用すれば、サブスクリプション全体に影響を与えることなく、特定のアドオンのみを取り消すことができます。これは、ユーザーがベースサブスクリプションは継続したまま、特定のアドオンだけの返金を希望する場合に有用です。

価格変更については、単一アイテムのサブスクリプションと同様のルールが適用されますが、複数アイテムで 未承認の価格改定(オプトイン)が存在する場合 は、追加の複雑さが生じます。未処理のオプトイン価格改定は、すべて 同じ更新日時に新価格が適用される必要があります。もし、あるアイテムにユーザーがまだ承認していない価格改定が存在する場合、その改定と整合しない他のアイテムの新たな価格改定は、無視される可能性があります。

RevenueCat で実装をシンプルにする

Play Billing Library を使って Subscription with Add-ons を直接実装すれば、確かに細かな挙動まで完全にコントロールできます。しかしその一方で、実装と運用の複雑さ、そして 時間的コスト(これも立派なリソースです) を大きく抱えることになります。請求クライアントのライフサイクル管理、クエリのキャッシュ、購入検証、エンタイトルメント管理、リアルタイム通知、そしてこれまでに触れてきた数々のエッジケースへの対応が必要です。

ここで大きな価値を発揮するのが RevenueCat です。RevenueCat は Play Billing Library の複雑さを抽象化し、複数プラットフォームにまたがるサブスクリプション管理を単一の API で提供します。特に Subscription with Add-ons においては、複数アイテム購入の扱い、エンタイトルメントの追跡、サーバーサイドでの検証といった煩雑な処理を、すべて自動で処理してくれます。

RevenueCat でのプロダクト設定

RevenueCat では、ダッシュボード上で Product を作成し、それらを Offerings に整理することでサブスクリプション商品を設定します。Google Play の各サブスクリプションのプロダクト ID は、それぞれ RevenueCat の Product に対応づけられ、関連するプロダクトをまとめて サブスクリプションの階層やバンドルを表す Offering として構成できます。

Subscription with Add-ons を利用する場合は、ベースサブスクリプション用のプロダクトと、各アドオン用のプロダクトをそれぞれ個別に作成します。そのうえで、RevenueCat の エンタイトルメント(権限)システムを使って、これらのプロダクトをアプリ内の特定の機能に紐づけます。ユーザーが購入を行うと、RevenueCat が アクティブなサブスクリプションに基づいて、どのエンタイトルメントを保有しているかを自動的に追跡してくれます。

シンプルな購入フロー

RevenueCat を使えば、購入の開始から結果の処理までを大幅にシンプルにできます。SDK が 請求クライアントの接続管理、購入の検証、エンタイトルメントの更新 をすべて自動で処理してくれます。

1class SubscriptionManager(private val context: Context) {
2
3    fun initialize() {
4        Purchases.configure(
5            PurchasesConfiguration.Builder(context, "your_revenuecat_api_key")
6                .build()
7        )
8    }
9
10    suspend fun getAvailableOfferings(): Offerings {
11        return Purchases.sharedInstance.awaitOfferings()
12    }
13
14    suspend fun purchaseSubscriptionWithAddons(
15        activity: Activity,
16        basePackage: Package,
17        addonPackages: List<Package>
18    ) {
19// RevenueCat handles the complexity of bundling these into a single purchaseval purchaseParams = PurchaseParams.Builder(activity, basePackage)
20            .build()
21
22        try {
23            val (transaction, customerInfo) = Purchases.sharedInstance
24                .awaitPurchase(purchaseParams)
25
26// CustomerInfo automatically reflects all active entitlements
27            updateUIWithEntitlements(customerInfo)
28        } catch (e: PurchasesException) {
29            handlePurchaseError(e)
30        }
31    }
32
33    fun checkEntitlements() {
34        Purchases.sharedInstance.getCustomerInfoWith { customerInfo ->
35// Check which entitlements are activeval hasPremium = customerInfo.entitlements["premium"]?.isActive == true
36            val hasHiFi = customerInfo.entitlements["hifi"]?.isActive == true
37            val hasOffline = customerInfo.entitlements["offline"]?.isActive == true
38
39            updateFeatureAccess(hasPremium, hasHiFi, hasOffline)
40        }
41    }
42}

これで完了です。簡単そうに見えますよね?RevenueCat の CustomerInfo オブジェクトは、ユーザーが行ったすべての購入を通じて 現在有効なエンタイトルメントをリアルタイムで把握できるビューを提供します。どのプロダクトがどの機能に対応しているかを手動で管理したり、複数アイテム購入の複雑さを自分で処理したりする必要はありません。購入の検証、レシートの検証、エンタイトルメントの算出といった処理は、すべて SDK と RevenueCat のバックエンドが自動で行います。

クロスプラットフォームでの一貫性

RevenueCat の大きな強みのひとつは、Android・iOS など複数のプラットフォームにわたって一貫した API を提供していることです。アプリが複数のプラットフォームで提供されている場合でも、RevenueCat を使えばユーザーは 統一されたサブスクリプション体験を得られます。たとえば、Android でサブスクリプションを開始したユーザーが後に iOS に切り替えた場合でも、そのエンタイトルメントは自動的に認識され、追加の対応なしで同じ権限を利用できます。

分析とインサイト

RevenueCat のダッシュボードでは、サブスクリプションのパフォーマンスに関する詳細な分析データを確認できます。これには、サブスクリプションバンドルに特化した指標も含まれます。どのアドオンの組み合わせが最も人気なのかを把握したり、Offering ごとのコンバージョン率をモニタリングしたり、価格戦略を最適化するための改善ポイントを特定したりすることが可能です。

Webhook とサーバー連携

サブスクリプションの状態をサーバー側でも把握する必要があるアプリ向けに、RevenueCat はサブスクリプションイベントをサーバーへ通知する Webhook を提供しています。これらの Webhook は、Google Play の RTDN よりも扱いやすいのが特長です。というのも、RevenueCat がデータを正規化し、複数アイテム購入に伴う複雑さも吸収してくれるためです。詳細な実装ガイダンスについては、RevenueCat の Google Play プロダクトに関するドキュメント と、 サブスクリプション管理ガイド を参照してください。

まとめ

Subscription with Add-ons は、Android デベロッパーがサブスクリプションを設計するうえで、新たな価値をもたらします。複数アイテムをまとめて購入でき、かつ請求を同期できるようになったことで、Google は 柔軟でユーザーフレンドリーなマネタイズ戦略の可能性を広げました。ユーザーが自分に合った構成を選べるモジュール型サブスクリプションを提供する場合でも、エンゲージメントの高いユーザー向けにアドオンをアップグレード経路として活用する場合でも、あるいは ARPU(ユーザーあたり平均収益)を高める魅力的なバンドルを作る場合でも、この機能はそのビジョンを実現するための土台を提供してくれます。

一方で、Play Billing Library を使ってこの機能を直接実装するには、商品設定、購入フロー管理、エッジケース対応などに細心の注意を払う必要があります。本記事で紹介したコード例はあくまで出発点であり、本番環境では、追加のエラーハンドリングやリトライロジック、十分なテストが不可欠です。

よりスピーディーに進めたいチームや、クロスプラットフォーム対応が必要な場合には、RevenueCat は非常に有力な選択肢となります。多くの複雑さを抽象化しつつ、統合された分析機能やシンプルなサーバー連携といった付加価値も提供します。

実装を計画する際は、まず マネタイズ戦略を明確に定義することから始めてください。どの機能をベースサブスクリプションに含め、どれをアドオンにするのかを整理し、請求期間が正しく揃っているかを確認し、各オプションの価値をユーザーにどう伝えるかを検討することが重要です。丁寧な設計と確かな実装を行えば、Subscription with Add-ons は、ユーザーとビジネスの双方にとって価値のあるサブスクリプション体験を実現する強力な手段となるでしょう。この機能の公式ドキュメントについては、 Android Developers の Subscription with Add-ons ガイド および RevenueCat Offerings を参照してください。