アプリ単体で収益化する場合、サブスクの実装は事業の生命線です。
メディカルサークルでは、Apple / Google の課金を RevenueCat 経由で統合し、Firestore と同期させる構成を採用しました。
本記事では、その実装で詰まった点と学びを共有します。
目次
Entitlement Identifier は不変、という最初の学び
RevenueCat の Entitlement Identifier は、ダッシュボードで一度作成すると変更できません。
有料プラン名を「Premium」から「メディカルサークル Pro」に改名した際、合わせて Identifier も変えたかったのですが、不可能でした。
結果として、Identifier は「内部的な識別子」、プラン名は「ユーザーに見せる表示名」と完全に分離し、コード側で定数として一元管理する設計に切り替えました。
この分離は、後から振り返ると正解でした。
課金状態を Firestore とアプリ内 state に二重反映する
RevenueCat のリスナーで課金状態の変化を検知し、変化時には Firestore のユーザードキュメントの isPremium フラグを更新します。
同時にアプリ内の状態管理プロバイダも更新し、UI 側はプロバイダを監視するだけで有料機能のロック/アンロックが即座に切り替わるようにしました。
Firestore 側にもフラグを置いているのは、セキュリティルールで参照する必要があるからです。
「クライアントの自己申告だけでルールを書く」のは危険なため、二重反映は必須でした。
1 つのプロバイダで有料機能の制御を統一する
有料機能のロック/アンロックを画面ごとにバラバラに書くと、改修時に必ずどこかが取り残されます。
メディカルサークルでは、課金状態を返すプロバイダを 1 つ定義し、すべての画面からそれを参照する方式に揃えました。
画面ごとの分岐は「プロバイダの値を見て if/else する」だけで済むため、新規プラン追加や条件変更にも追従しやすい構造になっています。
非会員には検索結果を返さないことで導線を作る
検索は有料機能なので、非会員にはぼかし表示ではなく、そもそもデータを取得させない設計にしました。
非会員が検索タブを開くとアップグレード促進ダイアログが表示され、そこからの導線でしか検索結果を閲覧できません。
結果として、フロントで擬似的に隠す UI を作る必要がなく、セキュリティ面でもクライアントの実装ミスでデータ漏れする余地がなくなりました。
「購入の復元」は機能というよりテストの問題
App Store の審査で必須となる「購入の復元」は、設定画面にボタンを配置し、タップ時に RevenueCat の復元 API を呼んで結果を反映する構成です。
実装自体は数十行で済みますが、本当の論点は「ちゃんと動作するかをどう検証するか」でした。
サンドボックスアカウントを用意し、購入 → アンインストール → 再インストール → 復元、というシナリオを実機で繰り返し検証することで、リリース後のサポート問い合わせを最小化できました。
まとめ
RevenueCat を組み込んだ課金実装は、コードだけ見れば単純です。
しかし「Identifier の不変性」「ルールとの同期」「画面横断の制御」「復元の検証」という、見落としやすい論点が積み重なります。
メディカルサークルでは、最初の数日でこれらをまとめて潰しておいたことで、その後の機能開発を課金まわりに足を引っ張られず進められました。

.webp%3Falt%3Dmedia%26token%3D6ca2c2ef-9413-4453-b992-55b66b11ed54&w=3840&q=75)


.webp%3Falt%3Dmedia%26token%3D900f385d-12a2-449b-8d1e-83a57cef0088&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D0e802fb0-2dda-44a7-bf80-5d39019635ba&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D3fb3dc66-ecca-402e-8fb8-fbec9407f7f5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Ddb21d760-e1ed-4ec2-af28-3462041e31b5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dcce7bd72-f11e-4292-86bf-e6ccf3e7bf32&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D457ff920-e0df-4ff5-95eb-e29f74b73823&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dc21fcc77-7404-458d-9eb5-85b8d84ae1bc&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D92052f12-5280-49df-877a-b514582e95db&w=3840&q=75)

.webp%3Falt%3Dmedia%26token%3Da7c14698-1b08-4fea-89c6-f77a9121f4c5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D899eeefd-f4c9-44a6-9ec2-3ced0b223ffd&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dca25fa6b-e233-43f7-90c3-e68e4c5b0bc5&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D7f18e5f1-cfda-4148-ab86-b3d2e6547262&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D5f10e078-4d87-4c87-928c-21b719cbf1cb&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D957b18b6-9b01-4c94-9207-7b9fca22a787&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3Dd952e11d-4461-47ae-892d-622fc3f2a48a&w=3840&q=75)
.webp%3Falt%3Dmedia%26token%3D532bb657-5670-49b4-9165-5f758062d8dd&w=3840&q=75)