界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
开发与工程 · SKILL.md

revenuecat

revenuecat

【开发与工程】Use when integrating RevenueCat in-app subscriptions in Flutter apps — entitlements, offerings, paywalls, purchases_flutter SDK, or Stripe backend sync. Load before writing any RevenueCat or IAP code. Query the RevenueCat MCP server for API verification.

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

技能说明

GitHub

name: revenuecat description: Use when integrating RevenueCat in-app subscriptions in Flutter apps — entitlements, offerings, paywalls, purchases_flutter SDK, or Stripe backend sync. Load before writing any RevenueCat or IAP code. Query the RevenueCat MCP server for API verification. allowed-tools: Bash, Read, Write, Edit metadata: triggers: RevenueCat, in-app purchase, IAP, entitlement, offering, paywall, subscription, StoreKit, Google Play Billing, purchases_flutter related-skills: stripe, flutter-mobile domain: payments role: specialist scope: implementation output-format: code last-reviewed: "2026-03-30"

Iron Law

NO ENTITLEMENT CHECK WITHOUT REVENUECAT SDK — never roll your own subscription validation; always delegate to RevenueCat's server-side receipt verification

RevenueCat In-App Subscription Skill

MCP Server

The RevenueCat MCP server is configured in .mcp.json as a remote HTTP MCP at https://mcp.revenuecat.ai/mcp.

Setup command (already configured in .mcp.json):

claude mcp add --transport http revenuecat https://mcp.revenuecat.ai/mcp --header "Authorization: Bearer ${REVENUECAT_API_V2_SECRET_KEY}"

Authentication options:

  • OAuth (recommended for Claude) — seamless browser-based auth, no API key management
  • API v2 Secret Key — pass via Authorization: Bearer <key> header (configured in .mcp.json)

Claude Code Plugin (alternative):

curl -fsSL https://raw.githubusercontent.com/RevenueCat/rc-claude-code-plugin/main/install.sh | bash

Provides slash commands: /rc:status, /rc:apikey, /rc:create-product, /rc:create-app Plus agents: Project Bootstrap Agent, Troubleshooting Agent

Recommended: Create a dedicated API v2 secret key for the MCP server to keep credentials organized.

Fallback: context7 MCP → resolve purchases_flutter library docs.

Available MCP Tools (26 total)

Project Management (1 tool):

ToolDescription
get_projectRetrieve RevenueCat project details

App Management (6 tools):

ToolDescription
list_appsList all apps in a project (with pagination)
get_appRetrieve details for a specific app
create_appCreate a new app in the project
update_appUpdate an existing app's details
delete_appDelete an app from the project
list_app_api_keysList public API keys for an app

Product Management (2 tools):

ToolDescription
list_productsList all products in a project
create_productCreate a new product

Entitlement Management (7 tools):

ToolDescription
list_entitlementsList all entitlements in a project
get_entitlementRetrieve details for a specific entitlement
create_entitlementCreate a new entitlement
update_entitlementUpdate an existing entitlement
delete_entitlementDelete an entitlement
list_entitlement_productsList products attached to an entitlement
attach_products_to_entitlementAttach products to an entitlement
detach_products_from_entitlementDetach products from an entitlement

Offering & Package Management (5 tools):

ToolDescription
list_offeringsList all offerings in a project
create_offeringCreate a new offering
update_offeringUpdate an existing offering
list_packagesList packages for an offering
create_packageCreate a new package for an offering

Customer Management (additional tools):

ToolDescription
get_customerRetrieve subscriber/customer info
list_customer_active_entitlementsList active entitlements for a customer

Context

RevenueCat manages mobile subscription entitlements for Flutter apps. A typical setup might include:

  • Primary app — Pro and Growth subscription tiers
  • Companion app — Free tier (included with primary subscription)
  • Partner/vendor app — Free tier with optional premium features

Stripe is the payment processor; RevenueCat sits on top as the entitlement/subscription management layer. RevenueCat handles App Store and Google Play receipt validation, cross-platform entitlement sync, and subscription analytics.

Architecture Constraints

  • RevenueCat SDK in Flutter only — purchases_flutter package in all 3 apps
  • Stripe as backend processor — RevenueCat connects to Stripe for web subscriptions; App Store/Google Play for mobile
  • Server-side entitlement checks — backend services verify entitlements via RevenueCat REST API, not client claims
  • Single source of truth — RevenueCat is the canonical source for "does this user have access to X?"
  • No direct StoreKit/BillingClient code — RevenueCat SDK abstracts all store interactions
  • API v2 Secret Key — use REVENUECAT_API_V2_SECRET_KEY for MCP server and server-side API calls
  • Environment variables — REVENUECAT_API_KEY_IOS, REVENUECAT_API_KEY_ANDROID, REVENUECAT_API_V2_SECRET_KEY (server-side)

Entitlement Model

Entitlement IDGrants Access ToTiers
pro_landlordFull property management, AI triage, vendor matchingPro, Growth
premium_vendorPriority queue, analytics dashboardVendor Premium
basic_accessCore features, limited propertiesFree (default)

Offerings Structure

default_offering/
├── $rc_monthly/
│   ├── landlord_pro_monthly      ($9/unit/mo)
│   └── landlord_growth_monthly   ($7/unit/mo)
├── $rc_annual/
│   ├── landlord_pro_annual       ($86.40/unit/yr — 20% discount)
│   └── landlord_growth_annual    ($67.20/unit/yr — 20% discount)
└── vendor_premium/
    └── vendor_premium_monthly    (TBD pricing)

Code Conventions

Flutter (Client-Side)

// pubspec.yaml — pin exact version
// purchases_flutter: 8.x.x

import 'package:purchases_flutter/purchases_flutter.dart';

// Initialize in app startup (main.dart or app initialization)
Future<void> initRevenueCat() async {
  final configuration = PurchasesConfiguration(
    Platform.isIOS
        ? const String.fromEnvironment('REVENUECAT_API_KEY_IOS')
        : const String.fromEnvironment('REVENUECAT_API_KEY_ANDROID'),
  );
  await Purchases.configure(configuration);
}

// Identify user after authentication
Future<void> identifyUser(String userId) async {
  await Purchases.logIn(userId);
}

// Check entitlement
Future<bool> hasProAccess() async {
  final customerInfo = await Purchases.getCustomerInfo();
  return customerInfo.entitlements.all['pro_landlord']?.isActive ?? false;
}

// Present paywall
Future<void> showPaywall(BuildContext context) async {
  final offerings = await Purchases.getOfferings();
  final current = offerings.current;
  if (current == null) {
    logger.error('No current offering available');
    // Fallback: direct user to web subscription page
    return;
  }
  // Navigate to paywall screen with offering data
  context.push('/paywall', extra: current);
}

// Purchase a package
Future<bool> purchasePackage(Package package) async {
  try {
    final customerInfo = await Purchases.purchasePackage(package);
    return customerInfo.entitlements.all['pro_landlord']?.isActive ?? false;
  } on PurchasesErrorCode catch (e) {
    if (e != PurchasesErrorCode.purchaseCancelledError) {
      logger.error('Purchase failed', error: e);
    }
    return false;
  }
}

Riverpod Provider Pattern

// Subscription state provider
@riverpod
class SubscriptionState extends _$SubscriptionState {
  @override
  Future<CustomerInfo> build() async {
    return Purchases.getCustomerInfo();
  }

  Future<void> refresh() async {
    state = const AsyncLoading();
    state = AsyncData(await Purchases.getCustomerInfo());
  }
}

// Entitlement check provider
@riverpod
bool hasProEntitlement(HasProEntitlementRef ref) {
  final customerInfo = ref.watch(subscriptionStateProvider);
  return customerInfo.whenOrNull(
    data: (info) => info.entitlements.all['pro_landlord']?.isActive ?? false,
  ) ?? false;
}

Python (Server-Side Verification)

# Backend entitlement verification via RevenueCat REST API v2
import httpx

REVENUECAT_API_SECRET = settings.REVENUECAT_API_V2_SECRET_KEY
REVENUECAT_BASE_URL = "https://api.revenuecat.com/v2"

async def verify_entitlement(user_id: str, entitlement_id: str) -> bool:
    """Server-side entitlement check — never trust client claims."""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{REVENUECAT_BASE_URL}/projects/{PROJECT_ID}/subscribers/{user_id}",
            headers={
                "Authorization": f"Bearer {REVENUECAT_API_SECRET}",
                "Content-Type": "application/json",
            },
        )
        if response.status_code != 200:
            logger.error(f"RevenueCat API error: {response.status_code}")
            return False

        subscriber = response.json()["subscriber"]
        entitlement = subscriber.get("entitlements", {}).get(entitlement_id)
        if not entitlement:
            return False

        # Check expiration
        expires = entitlement.get("expires_date")
        if expires and datetime.fromisoformat(expires) < datetime.now(UTC):
            return False

        return True

Webhook Integration (RevenueCat → Backend)

RevenueCat sends server-to-server webhooks for subscription events:

EventAction
INITIAL_PURCHASECreate/upgrade user tier in Cloud SQL
RENEWALExtend subscription, log payment
CANCELLATIONSchedule downgrade at period end
EXPIRATIONDowngrade to Free tier
BILLING_ISSUEFlag account, trigger dunning email
PRODUCT_CHANGEUpdate tier (Pro ↔ Growth)
TRANSFERHandle family sharing / account transfer
@router.post("/webhooks/revenuecat")
async def revenuecat_webhook(request: Request):
    payload = await request.json()
    # Verify webhook authorization header
    auth = request.headers.get("Authorization")
    if auth != f"Bearer {settings.REVENUECAT_WEBHOOK_SECRET}":
        raise HTTPException(status_code=401, detail="Unauthorized")

    event_type = payload.get("event", {}).get("type")
    app_user_id = payload.get("event", {}).get("app_user_id")

    match event_type:
        case "INITIAL_PURCHASE" | "RENEWAL":
            await activate_subscription(app_user_id, payload)
        case "EXPIRATION" | "CANCELLATION":
            await deactivate_subscription(app_user_id, payload)
        case "BILLING_ISSUE":
            await flag_billing_issue(app_user_id, payload)

    return {"status": "ok"}

Testing

// Mock RevenueCat in Flutter widget tests
class MockPurchases extends Mock implements Purchases {}

// Use RevenueCat sandbox for integration tests
// iOS: App Store Sandbox accounts
// Android: Google Play test tracks
# Mock RevenueCat API in Python tests
@pytest.fixture
def mock_revenuecat(httpx_mock):
    httpx_mock.add_response(
        url="https://api.revenuecat.com/v2/projects/test-project/subscribers/test-user",
        json={
            "subscriber": {
                "entitlements": {
                    "pro_landlord": {
                        "expires_date": "2027-01-01T00:00:00Z",
                        "purchase_date": "2026-01-01T00:00:00Z",
                    }
                }
            }
        },
    )

Security Checklist

  • RevenueCat API keys stored in environment variables only
  • Dedicated API v2 secret key created for MCP server (not shared with other services)
  • Server-side entitlement verification for all gated features (never trust client)
  • Webhook authorization header verified on all RevenueCat webhook endpoints
  • User identification via Purchases.logIn() after authentication
  • Purchases.logOut() on user sign-out to prevent entitlement leaking
  • Sandbox/test keys in dev/staging, production keys in production only
  • No direct StoreKit or Google Play Billing API calls — use RevenueCat SDK only

Reference Files

Paywall UI patterns: See reference/revenuecat-paywalls.md (when populated) for paywall screen templates, A/B testing configuration, promotional offer setup, and conversion tracking. Until then, query the revenuecat MCP or context7 MCP for purchases_flutter docs.

Stripe + RevenueCat sync: See reference/revenuecat-stripe-sync.md (when populated) for cross-platform subscription management, web-to-mobile migration, and entitlement reconciliation patterns. Until then, query the revenuecat MCP for current entitlement/offering configuration.

Process

  1. Query RevenueCat MCP for current API and SDK signatures before writing any integration code — use list_entitlements, list_offerings etc. to verify current state
  2. Configure offerings/entitlements via MCP tools (create_entitlement, create_offering, create_package) or RevenueCat dashboard
  3. Implement server-side verification before client-side feature gates
  4. Test with sandbox accounts — never use production credentials in development
  5. Webhook handler for subscription lifecycle events → sync to Cloud SQL
  6. Use API v2 — all server-side code should use RevenueCat REST API v2, not v1
我的收藏