RevenueCatのPaywallは、完全なネイティブビューとしてレンダリングされる高度にカスタマイズ可能なペイウォールを提供します。しかし、エンタイトルメントやオファー、パッケージの管理にはRevenueCatを活用しつつ、独自のカスタムペイウォールを実装したいケースもあるでしょう。本記事では、RevenueCatのPaywallとカスタムペイウォールを効果的に併用する方法について解説します。
なぜ複数のペイウォールを運用するのか?
なぜ複数のペイウォールを同時に管理する必 要があるのか、疑問に思うかもしれません。以下は、このアプローチが特に有効なケースです。
- 移行期間:これまで独自のカスタムペイウォールを構築してきたが、段階的にRevenueCatのペイウォールへ移行したい場合。
- A/Bテスト:既存のハードコードされたペイウォールと、RevenueCatで新たに構築したペイウォールを比較テストしたい場合。
- ターゲット別オファー:特定のユーザーセグメント向けに、独自の価格設定やプロモーションオファーを提示する必要があり、高度にカスタマイズされた表示が求められる場合。
- 高度なロジック:複雑なオンボーディングフローや特定のユーザージャーニーに対応する必要がある場合。
アプリ側でどのペイウォールUIを表示するかを決定しつつ、すべての購入処理は引き続きRevenueCat SDKを通して行います。これにより、ロジックの重複や価格の不一致、エンタイトルメント関連のバグを防ぎながら、必要に応じて表示部分の完全なコントロールを維持できます。
エンドツーエンドのフロー例
全体像として、2種類のペイウォールタイプを持つアプリは次のように動作します。
- ユーザーがペイウォールの分岐ポイントに到達する
- 例:オンボーディングの終了、機能のゲート、プロモーションの入口など
- アプリが特定の Placement に対して Offerings をリクエストする
- Placement は、ユーザージャーニーのどこでペイウォールを表示するかを表します
- RevenueCat が適切な Offering を返す
- 返される Offering は、ターゲティングルールに応じてユーザーごとに異なる場合があります
- アプリが Offering のメタデータを確認する
- メタデータによって、どのペイウォールを表示すべきかが決まります
- アプリが表示するペイウォール UI を選択する
- RevenueCat のペイウォール UI
- または、完全にカスタム実装したペイウォール UI
- ユーザーがプロダクトを選択する
- プロダクト情報と価格データは常に RevenueCat から取得します
- RevenueCat SDK 経由 で購入を開始する
- ペイウォール UI がどちらでも、購入フローは同一です
- RevenueCat がエンタイトルメントを更新する
- アプリが CustomerInfo に基づいてコンテンツを解放/制限する
この仕組みにより、表示は柔軟にしつつ、サブスクリプションシステムは一元化して整合性を保つことができます。さらに RevenueCat の Experiment 機能を使えば、異なる Placement やペイウォール間でコンバージョン成果をテスト・比較することも可能です。
RevenueCat Paywallの理解
RevenueCatのペイウォールは、ユーザーにプロダクトを表示するプロセスを簡素化するよう設計されています。RevenueCat Paywallsのドキュ メントにある通り、RevenueCatダッシュボード上で直接、さまざまなペイウォールデザインを作成・管理・テストすることが可能です。プロダクト情報の取得も自動で処理され、ユーザーフレンドリーな形で表示されます。しかも、ペイウォールを変更するたびにアプリの新しいリリースをストアに提出する必要はありません。
ほとんどの新規アプリにとって、RevenueCatでペイウォールを実装するのが最適な選択です。ストアにアップデートを提出することなく、ペイウォールを継続的に改善・更新できるからです。
カスタム実装のペイウォールを実装する
前述のとおり、RevenueCatのPaywallだけでは要件を満たせないケースもあります。たとえば、アプリ内の重要なデザイン要素をペイウォールにも表 示したい場合です。ゲーミフィケーションされたアプリであれば、途切れてしまった連続記録(broken streak)の可視化をペイウォール上に出して、「サブスクに登録すればこの途切れた記録を修復できる」ことをユーザーに伝えたい、といったケースが考えられます。
カスタム実装のペイウォールを採用する場合、UI/UXは基本的に自分たちでコントロールすることになります。一方で、バックエンド側の処理(プロダクト情報の取得や購入処理)については、引き続きRevenueCat SDKに依存します。
ステップ1:プロダクト情報を取得する
カスタム実装のペイウォールを作る最初のステップは、RevenueCatからプロダクト情報を取得することです。これは、ユーザーに正しいサブスクリプションの選択肢、価格、導入オファー(初回オファー)を表示するために不可欠です。詳細な手順は、Displaying Products のドキュメントを参照してください。ctions.
Swift では通常、 Purchases.shared.getOfferings() メソッドを使って設定済みの Offerings を取得します。同様に Kotlin では、Purchases.sharedInstance.getOfferingsWith() を使用します。
これにより Offering オブジェクトが取得でき、必要なプロダクト詳細がすべて含まれています。
1 Purchases.shared.getOfferings { (offerings, error) in
2 if let packages = offerings?.current?.availablePackages {
3 self.display(packages)
4 }
5 }
1Purchases.sharedInstance.getOfferingsWith({ error ->
2 // An error occurred
3}) { offerings ->
4 offerings.current?.availablePackages?.takeUnless { it.isNullOrEmpty() }?.let {
5 // Display packages for sale
6 }
7}
ステップ2:購入処理を実行する
ユーザーがカスタム実装のペイウォール上でプロダクトを選択したら、RevenueCatのSDKを使って購入を開始します。この手順については、Making Purchases のドキュメントで詳しく解説されています。
iOSでは Purchases.shared.purchase(package:)を使用し、AndroidではPurchases.sharedInstance.purchaseWith()を使用します。購入完了時の処理や、発生し得るエラーのハンドリングを忘れずに実装してください 。
1Purchases.shared.purchase(package: package) { (transaction, customerInfo, error, userCancelled) in
2 if customerInfo.entitlements["your_entitlement_id"]?.isActive == true {
3 // Unlock "pro" content
4 }
5}
1Purchases.sharedInstance.purchaseWith(
2 PurchaseParams.Builder(this, aPackage).build(),
3 onError = { error, userCancelled -> /* No purchase */ },
4 onSuccess = { storeTransaction, customerInfo ->
5 if (customerInfo.entitlements["my_entitlement_identifier"]?.isActive == true) {
6 // Unlock "pro" content
7 }
8 }
9)
ステップ3:Entitlement(権限)の管理
RevenueCatのペイウォールを使用する場合でも、カスタム実装のペイウォールを使用する場合でも、entitlement(権限)の管理はRevenueCatが処理します。購入が成功すると、RevenueCatはユーザーの CustomerInfoを更新します。これを利用して、プレミアム機能へのアクセスを付与または制限できます。Swiftでは、 Purchases.shared.customerInfo()を使用して CustomerInfo を取得できます。
1Purchases.shared.getCustomerInfo { (customerInfo, error) in
2 // access latest customerInfo
3}
Kotlinで
1Purchases.sharedInstance.getCustomerInfoWith(
2 onError = { error -> /* Optional error handling */ },
3 onSuccess = { customerInfo -> /* Access latest customerInfo */ },
4

