rc-plan-changes について
RevenueCat で Google Play の有効なサブスクリプションを変更する Android 開発者向けガイドです。置き換えモード、CustomerInfo からの現行商品取得、変更後の状態確認を説明します。
主な機能
- 01Google の置き換えモード選択
- 02CustomerInfo から商品 ID を取得
- 03アップグレードとダウングレード時期の説明
- 04RevenueCat によるトークンチェーン処理
- 05変更後のエンタイトルメント確認
活用シーン
- 01月額プランから年額プランへの変更
- 02更新時に適用するダウングレード
- 03無料トライアルから有料プランへの移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ revenuecat/ ai-toolkit --skill rc-plan-changesインストール後、Claude Code で/rc-plan-changesを入力して読み込まれたか確認します。
詳細
RevenueCat を使って Android のプランを変更する
ユーザーがすでに有効な Google Play サブスクリプションを持っており、別の SKU への変更(アップグレード、ダウングレード、クロスグレード、またはトライアルからの切り替え)が必要な場合に、このスキルを使用します。RevenueCat は単一の PurchaseParams ビルダーを通じて Google の置換モードを公開し、linkedPurchaseToken のチェーンをサーバー側で解決するため、トークンをつなぐコードを自分で書く必要はありません。
フェーズ 1:前提条件
プラン変更を実行する前に、次の点を確認してください。
- 置き換える対象として、ユーザーが有効な Google Play サブスクリプションを 1 つだけ持っていること。
Purchases.sharedInstance.awaitCustomerInfo()から最新のCustomerInfoを取得していること、または最近のリスナーコールバックからキャッシュ済みの値を取得していること。offerings.currentから対象のPackageを解決済みであること(fetch-offeringsスキルを参照)。- RevenueCat SDK 経由で、Google Play Billing Library 7+ がクラスパスに含まれていること。
ユーザーに有効なサブスクリプションがない場合は、このスキルを使用しないでください。新規購入には、代わりに make-purchase を使用してください。
フェーズ 2:計画(置換モードを選択する)
GoogleReplacementMode は Google の課金モードに 1 対 1 で対応しています。ユーザーの意図に基づいて選択してください。
| シナリオ | モード | 課金への影響 |
|---|---|---|
| 標準的なアップグレード(月額から年額へ) | WITH_TIME_PRORATION | 即時に切り替わり、残りの利用期間分が充当されます |
| 既存の課金日を維持したアップグレード | CHARGE_PRORATED_PRICE | 即時に切り替わり、日割り計算された料金がその場で請求されます |
| プリペイドプランへの、またはプリペイドプランからの切り替え | CHARGE_FULL_PRICE | 即時に切り替わり、全額がその場で請求されます |
| 有効な無料トライアル中のアップグレード | CHARGE_PRORATED_PRICE | 即時に切り替わり、日割り計算された料金がその場で請求されます |
| ダウングレード(年額から月額へ) | DEFERRED | 次回更新時に切り替わります |
トライアル中のアップグレードで、デフォルトとして WITHOUT_PRORATION を使用しないでください。WITHOUT_PRORATION は新しいプランを即時に適用しますが、次回更新まで料金を請求しません。そのため、ユーザーは料金を支払わずにプレミアム機能を利用できてしまいます。アップグレード料金をその場で請求するには、CHARGE_PRORATED_PRICE を使用してください。
モードを指定しない場合、PurchaseParams はデフォルトで WITHOUT_PRORATION になります。毎回、モードを明示的に設定してください。
DEFERRED が有効なのはダウングレードの場合のみです。Google はアップグレードに対する遅延切り替えを拒否します。
フェーズ 3:実行
CustomerInfo から currentProductId を導出してください。ハードコードされた SKU は、ユーザーがプラン間を移行した場合に正しく動作しません。
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
// activeSubscriptions entries are "productId:basePlanId", strip the base plan suffix
val currentProductId = customerInfo.activeSubscriptions
.firstOrNull()
?.substringBefore(":")
?: return // nothing active, route to make-purchase instead
val newPackage = offerings.current
?.availablePackages
?.firstOrNull { it.identifier == "premium_annual_package" }
?: return
val params = PurchaseParams.Builder(activity, newPackage)
.googleProductChangeInfo(
GoogleProductChangeInfo(
oldProductId = currentProductId,
replacementMode = GoogleReplacementMode.WITH_TIME_PRORATION,
)
)
.build()
try {
val result = Purchases.sharedInstance.awaitPurchase(params)
// result.customerInfo reflects the new subscription
} catch (e: PurchasesTransactionException) {
if (!e.userCancelled) showError(e.error.message)
}
oldProductId に関する注意事項:
- サブスクリプションのプロダクト ID のみを渡してください。
"basic_monthly:monthly_plan"を渡した場合、SDK が:monthly_planを削除しますが、自分で切り出すほうが意図が明確です。 CustomerInfo.activeSubscriptionsはproductId:basePlanId形式です。substringBefore(":")を使うと、正しい値を取得できます。
フェーズ 4:確認
サスペンド関数の呼び出しが戻った後、更新された CustomerInfo を読み取ります。
customerInfo.activeSubscriptionsに新しいproductId:basePlanIdが含まれていること。- 切り替えの間も
customerInfo.entitlements["pro"]?.isActiveがtrueのままであること。SKU の文字列で UI の表示を制御しないでください。 DEFERREDモードの場合、次回更新までactiveSubscriptionsには引き続き古いプロダクトが報告されます。RevenueCat はサーバー側で保留中の切り替えを追跡し、Google が更新の RTDN を送信した後にエンタイトルメントを切り替えます。
linkedPurchaseToken をたどるコードを書く必要はありません。RevenueCat がチェーンを解決し、古いトークンを置換済みとしてマークし、両方のトークンを同じ App User ID に関連付けます。クライアントコードはエンタイトルメントを読み取り、それを信頼します。
よくある間違い
| 間違い | 修正方法 |
|---|---|
oldProductId を定数としてハードコードする | customerInfo.activeSubscriptions.firstOrNull()?.substringBefore(":") から導出します |
"productId:basePlanId" を oldProductId として渡す | substringBefore(":") でベースプランを切り取ります |
トライアルからのアップグレードに WITHOUT_PRORATION を使う | ユーザーに今すぐ課金するため、CHARGE_PRORATED_PRICE を使います |
アップグレードに DEFERRED を使う | DEFERRED はダウングレード専用です。Google は保留中のアップグレードを拒否します |
linkedPurchaseToken を追跡するバックエンドコードを書く | RevenueCat がサーバー側で処理するため、そのコードは削除します |
| UI の状態を判断するために SKU を読み取る | 代わりに entitlements[...]?.isActive を読み取ります |