| name | mobile-monetization |
|---|---|
| description | Add in-app purchases, subscriptions, or one-time payments to a React Native/Expo or Flutter app. Covers RevenueCat, StoreKit 2, Google Play Billing, receipt validation, sandbox testing, and subscription lifecycle. Use when the user wants to charge money inside their app. |
| standards-version | 1.6.3 |
Use this skill when the user:
- Wants to add in-app purchases or subscriptions
- Asks about RevenueCat, StoreKit 2, or Google Play Billing
- Needs help with receipt validation or sandbox testing
- Wants to offer a paywall, freemium model, or premium features
- Mentions "monetization", "IAP", "in-app purchase", "subscription", "paywall", or "RevenueCat"
- Monetization model: subscriptions, one-time purchase, consumables, or hybrid
- Products: list of product IDs and price tiers
- Framework: Expo (React Native) or Flutter
-
Choose a monetization SDK. RevenueCat is recommended for most apps:
Option Best for Pricing RevenueCat Cross-platform, analytics, paywalls Free up to $2.5K MTR, then 1% Adapty A/B testing paywalls, higher free tier Free up to $5K MTR, then 0.6% expo-in-app-purchases Simple Expo-only use cases Free (Expo SDK) react-native-iap Direct StoreKit 2 / Play Billing Free (community) -
Install RevenueCat. For Expo:
npx expo install react-native-purchases
For Flutter:
flutter pub add purchases_flutter
Both require a development build (not Expo Go).
-
Create a RevenueCat account and project. At https://app.revenuecat.com:
- Create a new project
- Add your iOS app with the App Store Connect shared secret
- Add your Android app with the Play Console service account JSON
- Create "Entitlements" (e.g.
premium) and attach product IDs - Create "Offerings" to group products into a paywall
-
Configure products in the stores.
App Store Connect:
- Go to Monetization > Subscriptions (or In-App Purchases)
- Create a subscription group and add products (monthly, yearly)
- Set pricing for each region
- Submit for review (products must be reviewed separately from the app)
Google Play Console:
- Go to Monetize > Products > Subscriptions
- Create base plans with pricing
- Activate the products
-
Initialize RevenueCat in your app. Create
lib/purchases.ts:import Purchases from "react-native-purchases"; import { Platform } from "react-native"; const API_KEYS = { ios: process.env.EXPO_PUBLIC_RC_IOS_KEY!, android: process.env.EXPO_PUBLIC_RC_ANDROID_KEY!, }; export async function initPurchases(userId?: string) { const key = Platform.select(API_KEYS)!; Purchases.configure({ apiKey: key, appUserID: userId }); } export async function getOfferings() { const offerings = await Purchases.getOfferings(); return offerings.current; } export async function purchasePackage(pkg: any) { const { customerInfo } = await Purchases.purchasePackage(pkg); return customerInfo.entitlements.active; } export async function checkEntitlement(id: string): Promise<boolean> { const info = await Purchases.getCustomerInfo(); return info.entitlements.active[id] !== undefined; } export async function restorePurchases() { const info = await Purchases.restorePurchases(); return info.entitlements.active; }
-
Build a paywall screen. Display offerings to the user:
import { useEffect, useState } from "react"; import { View, Text, Pressable, ActivityIndicator } from "react-native"; import { getOfferings, purchasePackage } from "@/lib/purchases"; export default function PaywallScreen() { const [offerings, setOfferings] = useState<any>(null); const [loading, setLoading] = useState(true); useEffect(() => { getOfferings().then((o) => { setOfferings(o); setLoading(false); }); }, []); if (loading) return <ActivityIndicator />; if (!offerings) return <Text>No offerings available</Text>; return ( <View> <Text>Upgrade to Premium</Text> {offerings.availablePackages.map((pkg: any) => ( <Pressable key={pkg.identifier} onPress={() => purchasePackage(pkg)} > <Text>{pkg.product.title}</Text> <Text>{pkg.product.priceString}/ {pkg.packageType === "MONTHLY" ? "month" : "year"}</Text> </Pressable> ))} </View> ); }
-
Gate features on entitlements. Check access before showing premium content:
import { useEffect, useState } from "react"; import { checkEntitlement } from "@/lib/purchases"; export function usePremium() { const [isPremium, setIsPremium] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { checkEntitlement("premium").then((active) => { setIsPremium(active); setLoading(false); }); }, []); return { isPremium, loading }; }
-
Handle subscription lifecycle events. RevenueCat webhooks notify your backend of:
- Initial purchase
- Renewal
- Cancellation
- Billing issue (grace period)
- Expiration
- Refund
Configure webhooks in RevenueCat dashboard under Project Settings > Integrations.
- RevenueCat: React Native quickstart
- RevenueCat: Flutter quickstart
- Expo in-app purchases docs
- App Store Review Guidelines: In-App Purchase
- Google Play Billing
User: "I want to add a monthly and yearly subscription with a free trial."
Agent:
- Recommends RevenueCat for cross-platform subscription management
- Installs react-native-purchases with
mobile_installDependency - Creates
lib/purchases.tswith initialization, offering fetch, and purchase logic - Scaffolds a paywall screen showing monthly ($4.99) and yearly ($39.99) options
- Implements
usePremiumhook to gate features on the "premium" entitlement - Walks through App Store Connect and Play Console product creation
- Reminds user to set
EXPO_PUBLIC_RC_IOS_KEYandEXPO_PUBLIC_RC_ANDROID_KEYin.env
| Step | MCP Tool | Description |
|---|---|---|
| Install SDK | mobile_installDependency |
Install react-native-purchases or purchases_flutter |
| Create paywall screen | mobile_generateScreen |
Scaffold paywall with offerings display |
| Check build | mobile_checkBuildHealth |
Verify project builds with native billing modules |
| Validate store listing | mobile_validateStoreMetadata |
Check app.json has required fields before submission |
- Testing with real money - Use sandbox accounts (App Store Connect > Users and Access > Sandbox Testers) and Google Play test tracks. Never test purchases with real cards during development.
- Apple requires the Restore button - Apps with subscriptions or non-consumables must include a "Restore Purchases" button. Apple rejects apps without one.
- Forgetting server-side validation - RevenueCat handles receipt validation automatically. If using raw StoreKit/Play Billing, validate receipts on your server to prevent fraud.
- Pricing localization - App Store and Play Store handle currency conversion automatically. Do not hardcode prices. Always read
priceStringfrom the product object. - Subscription status caching - Check entitlement status on every app launch, not just after purchase. Users can cancel or get refunded between sessions.
- Not handling billing errors - Grace periods, payment failures, and expired cards need UI messaging. RevenueCat provides
BILLING_ERRORevents for this. - Expo Go incompatibility - Native billing modules require a development build. They will crash in Expo Go.
- Mobile App Store Prep - store listing requirements for paid apps
- Mobile Auth Setup - associate purchases with authenticated users
- Mobile Analytics - track conversion rates and revenue metrics