feat(mobile): integrate react-native-purchases (RevenueCat)

- Purchases.configure() runs with appUserID = restaurantId at every
  point the id becomes known (login resolve, cold-start rehydration,
  onboarding restaurant creation) — this must match what the backend
  webhook expects (see apps/api/src/routes/subscription.ts).
- New account/subscription.tsx paywall: current entitlement status,
  offering packages with real store prices, purchase, restore.
- Linked from the account screen.

Native module — requires an EAS/dev-client build, not plain Expo Go.
This commit is contained in:
AyrisAI
2026-08-20 13:20:54 +03:00
parent 3509388c37
commit b25c742eea
7 changed files with 249 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
import { Platform } from "react-native";
import Purchases, { type CustomerInfo, type PurchasesOffering } from "react-native-purchases";
const API_KEY = process.env.EXPO_PUBLIC_REVENUECAT_API_KEY;
export const PRO_ENTITLEMENT_ID = "Menul.io Pro";
let configuredForRestaurantId: string | null = null;
// appUserID is the restaurant id — the backend webhook (POST
// /subscription/webhook) maps RevenueCat's app_user_id straight to
// subscriptions.restaurant_id, so these must always match.
export function configurePurchases(restaurantId: string) {
if (!API_KEY) {
console.warn("EXPO_PUBLIC_REVENUECAT_API_KEY missing — purchases disabled");
return;
}
if (configuredForRestaurantId === restaurantId) return;
Purchases.configure({ apiKey: API_KEY, appUserID: restaurantId });
if (Platform.OS !== "web") {
Purchases.setLogLevel(__DEV__ ? Purchases.LOG_LEVEL.DEBUG : Purchases.LOG_LEVEL.WARN);
}
configuredForRestaurantId = restaurantId;
}
export async function getCurrentOffering(): Promise<PurchasesOffering | null> {
const offerings = await Purchases.getOfferings();
return offerings.current;
}
export async function getCustomerInfo(): Promise<CustomerInfo> {
return Purchases.getCustomerInfo();
}
export function isProActive(info: CustomerInfo): boolean {
return info.entitlements.active[PRO_ENTITLEMENT_ID] !== undefined;
}