rc-subscription-states について
Android 開発者向けに、RevenueCat のサブスクリプションアクセス判定と UI への状態反映を説明するスキルです。権利情報、キャッシュまたは最新の CustomerInfo、更新リスナーの使い方を扱います。
主な機能
- 01権利の有効状態によるアクセス制御
- 02更新、期限、請求状態の確認
- 03キャッシュまたは最新の CustomerInfo の利用
- 04顧客情報リスナーによる UI 更新
- 05識別済みユーザーと匿名ユーザーの処理
活用シーン
- 01サブスクリプションに応じたプレミアムコンテンツ表示
- 02請求問題や有効期限の通知
- 03サーバー側の付与後のアクセス状態更新
- 04購入や復元後のアクセス UI 同期
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ revenuecat/ ai-toolkit --skill rc-subscription-statesインストール後、Claude Code で/rc-subscription-statesを入力して読み込まれたか確認します。
詳細
サブスクリプションの状態
Android で CustomerInfo と EntitlementInfo を使って、ユーザーがアクセス権を持っているかどうかを判定し、状態に応じた UI を表示します。
フェーズ 1:確認
コードを書く前に、実際に何を確認するのかを把握します。
- 機能の利用条件となるエンタイトルメント ID は何ですか?(例:
pro_access) - アクセスの有無を示す単純なブール値だけが必要ですか?それとも、アクセス権の有無の理由(請求の問題、キャンセル済みだが支払い期間中、一時停止中)も説明する必要がありますか?
- キャッシュされた値(高速ですが、古い可能性があります)と、最新の値(サーバーが正とする値)のどちらが必要ですか?
- 起動時に UI を一度だけ更新しますか?それとも、購入、復元、バックグラウンド更新などのリアルタイムな変更に反応させますか?
アクセスの有無だけが必要なら、必要なのは isActive だけです。それ以外はすべて任意の補足情報です。
フェーズ 2:計画
Google の 7 つの状態と RevenueCat のブール値
Google Play Developer API を使って独自のトラッカーを実装すると、7 つのサブスクリプション状態を対応付け、どの状態でアクセス権を付与するかを決める必要があります。
| Google の状態 | アクセス権を付与するか |
|---|---|
| ACTIVE | はい |
| IN_GRACE_PERIOD | はい |
CANCELED(expirationDate より前) | はい |
| ON_HOLD | いいえ |
| PAUSED | いいえ |
| EXPIRED | いいえ |
| PENDING | いいえ |
RevenueCat はバックエンドで Google の SubscriptionPurchaseV2 リソースからこの状態を計算し、1 つのフィールド EntitlementInfo.isActive として公開します。状態機械を実装する代わりに、ブール値を読み取れます。
何をいつ読み取るか
| 必要な情報 | フィールド |
|---|---|
| ユーザーは現在アクセス権を持っていますか? | entitlement.isActive |
| サブスクリプションは期間終了時に更新されますか? | entitlement.willRenew |
| 有料アクセスはいつ終了しますか? | entitlement.expirationDate |
| トライアル、導入価格、前払い、通常期間のどれですか? | entitlement.periodType |
| 支払いに問題がありますか? | entitlement.billingIssueDetectedAt |
| ユーザーはキャンセル済みですが、まだ期間が残っていますか? | entitlement.unsubscribeDetectedAt |
| エンタイトルメントを発行したストアはどれですか? | entitlement.store |
判断ルール
- 機能の利用可否は
isActive == trueで判定します。それ以外は使いません。 - 支払い方法の修正を促すには、
billingIssueDetectedAt != nullを使います。 - アクセス権がまだ有効な間に更新を促すには、
unsubscribeDetectedAt != nullとexpirationDateを使います。 - 請求の問題も明示的なキャンセル日時もない場合、更新されないことを通知するには
!willRenewを使います。
フェーズ 3:実装
CustomerInfo を読み取り、アクセス権を確認する
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
val hasAccess = customerInfo.entitlements["pro_access"]?.isActive == true
awaitCustomerInfo() はディスクキャッシュをすぐに返し、その後バックグラウンドでネットワークから更新します。オフラインでもエンタイトルメントの確認は高速です。
必要な場合に最新の値を取得する
サーバー側で権限を付与した後(例:サポート担当者がプロモーションを発行した後)に使います。
val fresh = Purchases.sharedInstance.awaitCustomerInfo(
fetchPolicy = CacheFetchPolicy.FETCH_CURRENT
)
状態に応じた UI を表示する
アクセス権は isActive で判定し、その他のフィールドで状況を把握します。
fun updateUI(entitlement: EntitlementInfo?) {
if (entitlement == null || !entitlement.isActive) {
showSubscribeScreen(); return
}
showPremiumContent()
when {
entitlement.billingIssueDetectedAt != null -> showBillingIssueWarning()
entitlement.unsubscribeDetectedAt != null ->
entitlement.expirationDate?.let { showExpiryNotice(it) }
!entitlement.willRenew -> showNonRenewingNotice()
}
}
状態に応じたメッセージのガイド
| 有効なエンタイトルメントのシグナル | 表示するメッセージ |
|---|---|
billingIssueDetectedAt != null | 支払いの問題が発生しました。お支払い方法を更新してください |
unsubscribeDetectedAt != null | expirationDate にアクセス権が終了します |
willRenew == false(他のシグナルがない場合) | 今回の期間終了後は更新されません |
periodType == TRIAL または INTRO | トライアルまたは導入価格が適用されています |
複数デバイスでユーザーを識別する
val result = Purchases.sharedInstance.awaitLogIn("your_user_id")
val customerInfo = result.customerInfo
val createdNewUser = result.created
awaitLogIn() は匿名購入を識別済みユーザーに統合します。logOut() は新しい匿名セッションを開始します。
フェーズ 4:確認
CustomerInfo の更新をリッスンする
リスナーを登録すると、手動でポーリングしなくても、購入、復元、バックグラウンド更新に UI が反応します。
Purchases.sharedInstance.updatedCustomerInfoListener =
UpdatedCustomerInfoListener { info ->
val active = info.entitlements["pro_access"]?.isActive == true
updateAccessGate(active)
}
SDK の起動時にキャッシュがヒットし、変更がない場合、リスナーは呼び出されません。リスナーの設定に加えて、起動時には必ず awaitCustomerInfo() を呼び出してください。
テストマトリクス
各ケースを確認し、UI が正しく応答することを確かめます。
| ケース | 期待される isActive | 期待される UI |
|---|---|---|
| 初回購入 | true | プレミアムコンテンツ |
| 猶予期間(請求の問題があるが、引き続き利用可能) | true | プレミアムコンテンツ + 請求に関する警告 |
キャンセル済み、expirationDate より前 | true | プレミアムコンテンツ + 有効期限のお知らせ |
| 保留中 | false | 購読画面 |
| 一時停止中 | false | 購読画面 |
| 期限切れ | false | 購読画面 |
| 保留中(まだ支払いなし) | false | 購読画面 |
確認項目
- 購入後にリスナーが呼び出されたとき、アクセス制御が正しく切り替わること。
- バックエンドで付与された場合(プロモーション、返金、サポート対応)に、
FETCH_CURRENTがCustomerInfoを更新すること。 logOut()の後、CustomerInfoに匿名ユーザーが反映され、isActiveがそれに応じてリセットされること。- オフラインで起動した場合でも、ネットワークを呼び出さずにキャッシュされた
CustomerInfoを返し、アクセスを制御すること。