Mobile Payments
In-app purchases with RevenueCat for iOS and Android subscriptions, and why Stripe direct doesn't work on mobile.
Mobile Payments
In-app purchases on iOS and Android must go through Apple and Google's payment systems. You cannot use Stripe's checkout directly for digital goods sold inside a mobile app.
Why Not Stripe Direct?
Apple and Google require that digital goods and subscriptions sold within apps use their in-app purchase (IAP) systems:
| Platform | Requirement | Commission |
|---|---|---|
| iOS | Apple IAP required for digital goods | 15-30% |
| Android | Google Play Billing required for digital goods | 15-30% |
Stripe can still be used for: physical goods, services performed outside the app, or web-based subscriptions that don't unlock in-app content.
RevenueCat
RevenueCat is the recommended solution. It wraps both Apple and Google IAP APIs into a single, simple SDK:
npx expo install react-native-purchasesSetup
import Purchases from "react-native-purchases";
import { Platform } from "react-native";
async function initRevenueCat() {
Purchases.configure({
apiKey: Platform.OS === "ios"
? "appl_your_ios_api_key"
: "goog_your_android_api_key",
});
}Call this in your root layout:
// app/_layout.tsx
useEffect(() => {
initRevenueCat();
}, []);Display Products
async function loadOfferings() {
const offerings = await Purchases.getOfferings();
const current = offerings.current;
if (current) {
const monthly = current.monthly; // Monthly package
const annual = current.annual; // Annual package
return { monthly, annual };
}
}Purchase a Subscription
async function purchaseSubscription(packageToPurchase: PurchasesPackage) {
try {
const { customerInfo } = await Purchases.purchasePackage(packageToPurchase);
const isPro = customerInfo.entitlements.active["pro"] !== undefined;
if (isPro) {
// Unlock premium features
router.push("/dashboard");
}
} catch (error: any) {
if (!error.userCancelled) {
Alert.alert("Purchase Failed", error.message);
}
}
}Check Subscription Status
Sync subscription status with your Supabase database using RevenueCat webhooks:
// Check locally first
const customerInfo = await Purchases.getCustomerInfo();
const isPro = customerInfo.entitlements.active["pro"] !== undefined;
// RevenueCat webhook updates your Supabase database
// Configure at: RevenueCat Dashboard > Integrations > Webhooks
// Endpoint: https://your-project.supabase.co/functions/v1/revenucat-webhookSync subscription status with Convex using RevenueCat webhooks:
// Check locally first
const customerInfo = await Purchases.getCustomerInfo();
const isPro = customerInfo.entitlements.active["pro"] !== undefined;
// RevenueCat webhook calls your Convex HTTP action
// Configure at: RevenueCat Dashboard > Integrations > Webhooks
// Endpoint: https://your-project.convex.site/revenucat-webhookRestore Purchases
Required by Apple — users must be able to restore purchases on new devices:
async function restorePurchases() {
try {
const customerInfo = await Purchases.restorePurchases();
const isPro = customerInfo.entitlements.active["pro"] !== undefined;
Alert.alert(isPro ? "Restored!" : "No active subscription found");
} catch (error) {
Alert.alert("Error", "Could not restore purchases");
}
}Paywall Screen
Build a paywall screen:
function PaywallScreen() {
const [offerings, setOfferings] = useState<PurchasesOfferings | null>(null);
useEffect(() => {
Purchases.getOfferings().then(setOfferings);
}, []);
return (
<ScreenLayout>
<Text style={styles.title}>Upgrade to Pro</Text>
<Text style={styles.subtitle}>Unlock all features</Text>
{offerings?.current?.availablePackages.map((pkg) => (
<TouchableOpacity key={pkg.identifier} onPress={() => purchaseSubscription(pkg)}>
<Text>{pkg.product.title}</Text>
<Text>{pkg.product.priceString}/month</Text>
</TouchableOpacity>
))}
<Button title="Restore Purchases" variant="ghost" onPress={restorePurchases} />
</ScreenLayout>
);
}App Store Setup
- Create subscriptions in App Store Connect (iOS) and Google Play Console (Android)
- Add product IDs to RevenueCat dashboard
- Create Entitlements (e.g., "pro") and link them to products
- Configure webhook endpoints for server-side validation