rc-plan-changes — скилл для Claude Code: что делает и как установить
Скилл помогает настроить смену тарифных планов Google Play через RevenueCat на Android, выбирая верный режим перерасчёта стоимости.
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a claude-codeЧто делает rc-plan-changes
Скилл инструктирует агента, как переводить пользователя с одной подписки Google Play на другую с помощью PurchaseParams.Builder и GoogleProductChangeInfo. Он раскладывает логику для перехода на годовой тариф, даунгрейда и смены планов во время бесплатного пробного периода.
Внутри заложены правила выбора GoogleReplacementMode: немедленный переход с учётом времени (WITH_TIME_PRORATION), немедленное списание пропорциональной цены (CHARGE_PRORATED_PRICE) или отложенное переключение (DEFERRED). Режим DEFERRED допустим только для понижения тарифа, иначе Google отклонит запрос на обновление.
Код извлекает текущий идентификатор через вызов substringBefore(":") из массива customerInfo.activeSubscriptions, отсекая суффикс базового плана. Для работы библиотеке требуется Google Play Billing Library версии 7 или выше в составе RevenueCat SDK.
Какие задачи решает
- перевести активную подписку с ежемесячной на годовую с пересчётом времени
- настроить отложенное понижение тарифа до даты следующего продления
- извлечь чистый идентификатор продукта из activeSubscriptions без суффикса
- исключить бесплатный доступ при переходе на платный план во время триала
Примеры запросов агенту
- Помоги настроить апгрейд с ежемесячной подписки на годовую через GoogleProductChangeInfo в Kotlin
- Напиши код смены подписки в RevenueCat с отложенным переходом через DEFERRED
- Как правильно передать oldProductId при смене тарифа в RevenueCat на Android?
Как установить rc-plan-changes
1Через Skills CLI (Vercel)
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a claude-code2Вручную: положить файлы
~/.claude/skills/rc-plan-changes/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/rc-plan-changes/
1Через Skills CLI (Vercel)
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a codex2Вручную: положить файлы
.agents/skills/rc-plan-changes/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a cursor2Вручную: положить файлы
.cursor/skills/rc-plan-changes/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install RevenueCat/ai-toolkit rc-plan-changes2Вручную: положить файлы
.github/skills/rc-plan-changes/SKILL.md1Через Skills CLI (Vercel)
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a gemini-cli2Командой платформы
gemini skills install https://github.com/RevenueCat/ai-toolkit --path revenuecat-play-billing/skills/rc-plan-changesВручную: положить файлы
.devin/skills/rc-plan-changes/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о rc-plan-changes
Как установить rc-plan-changes в Claude Code?
Выполните в терминале команду npx skills add RevenueCat/ai-toolkit --skill rc-plan-changes -a claude-code.
Что делает скилл rc-plan-changes?
Он даёт агенту правила перехода между подписками в Google Play на Android через RevenueCat. Скилл помогает выбрать правильный GoogleReplacementMode и собрать PurchaseParams.
Почему нельзя использовать режим DEFERRED для апгрейда подписки?
Google Play отклоняет отложенные обновления тарифа. Режим DEFERRED предназначен только для понижения плана, которое вступает в силу при следующем продлении.
Исходный текст
SKILL.md · MIT
---
name: rc-plan-changes
description: Use this skill when implementing subscription upgrades, downgrades, and plan switches via RevenueCat on Android. Covers GoogleProductChangeInfo, picking a Google replacement mode, and letting the SDK manage linkedPurchaseToken chaining.
license: Apache-2.0; see LICENSE
metadata:
author: RevenueCat
source: revenuecat-handbook chapter 7
keywords:
- android
- revenuecat
- upgrade
- downgrade
- google-product-change-info
- replacement-mode
---
# Plan Changes on Android with RevenueCat
You use this skill when a user already has an active Google Play subscription and you need to move them to a different SKU (upgrade, downgrade, cross-grade, or trial conversion). RevenueCat exposes Google's replacement modes through a single `PurchaseParams` builder and resolves the `linkedPurchaseToken` chain server side so you do not write token chaining code.
## Phase 1: Preconditions
Confirm the following before invoking a plan change:
- The user has exactly one active Google Play subscription you intend to replace.
- You have a fresh `CustomerInfo` from `Purchases.sharedInstance.awaitCustomerInfo()` or a cached value from a recent listener callback.
- You have the target `Package` resolved from `offerings.current` (see `fetch-offerings` skill).
- Google Play Billing Library 7+ is on the classpath via the RevenueCat SDK.
Skip this skill if the user has no active subscription. For a fresh purchase, use `make-purchase` instead.
## Phase 2: Plan (pick a replacement mode)
`GoogleReplacementMode` maps one to one onto Google's billing modes. Pick based on the user intent:
| Scenario | Mode | Billing effect |
|---|---|---|
| Standard upgrade (monthly to annual) | `WITH_TIME_PRORATION` | Immediate switch, remaining time credited |
| Upgrade, keep the existing billing date | `CHARGE_PRORATED_PRICE` | Immediate switch, prorated charge now |
| Switch to or from a prepaid plan | `CHARGE_FULL_PRICE` | Immediate switch, full charge now |
| Upgrade during an active free trial | `CHARGE_PRORATED_PRICE` | Immediate switch, prorated charge now |
| Downgrade (annual to monthly) | `DEFERRED` | Switch applies at next renewal |
Do not default to `WITHOUT_PRORATION` for trial upgrades. `WITHOUT_PRORATION` applies the new plan immediately but charges nothing until the next renewal, which gives the user free premium access they did not pay for. Use `CHARGE_PRORATED_PRICE` to charge the upgrade price on the spot.
If you set no mode, `PurchaseParams` defaults to `WITHOUT_PRORATION`. Set the mode explicitly every time.
`DEFERRED` is valid only for downgrades. Google rejects deferred upgrades.
## Phase 3: Execute
Derive `currentProductId` from `CustomerInfo`. Hardcoded SKUs break when a user has migrated between plans.
```kotlin
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)
}
```
Notes on `oldProductId`:
- Pass the subscription product ID only. If you pass `"basic_monthly:monthly_plan"`, the SDK strips `:monthly_plan` for you, but the intent is clearer when you slice it yourself.
- `CustomerInfo.activeSubscriptions` uses the `productId:basePlanId` shape. `substringBefore(":")` gives you the correct value.
## Phase 4: Verify
After the suspending call returns, read the updated `CustomerInfo`:
- `customerInfo.activeSubscriptions` now contains the new `productId:basePlanId`.
- `customerInfo.entitlements["pro"]?.isActive` stays `true` across the switch; do not gate UI on the SKU string.
- For `DEFERRED` mode, `activeSubscriptions` still reports the old product until the next renewal. RevenueCat tracks the pending switch server side and flips the entitlement after Google sends the renewal RTDN.
You do not write `linkedPurchaseToken` traversal code. RevenueCat resolves the chain, marks the old token as replaced, and attributes both tokens to the same App User ID. Client code reads entitlements and trusts them.
## Common mistakes
| Mistake | Fix |
|---|---|
| Hardcoding `oldProductId` as a constant | Derive it from `customerInfo.activeSubscriptions.firstOrNull()?.substringBefore(":")` |
| Passing `"productId:basePlanId"` as `oldProductId` | Slice off the base plan with `substringBefore(":")` |
| Using `WITHOUT_PRORATION` for a trial upgrade | Use `CHARGE_PRORATED_PRICE` so the user is charged now |
| Using `DEFERRED` for an upgrade | `DEFERRED` is downgrade only; Google rejects deferred upgrades |
| Writing backend code to follow `linkedPurchaseToken` | RevenueCat does this server side, delete the code |
| Reading the SKU to decide UI state | Read `entitlements[...]?.isActive` instead |
## References
- [Full chapter](https://www.revenuecat.com/guides/revenuecat-android-sdk/subscription-upgrades-and-downgrades)
© RevenueCat · MIT · RevenueCat/ai-toolkit