Expo + RevenueCat:5 步設定訂閲並測試
Expo + RevenueCat: Subscriptions in 5 Steps (and How to Test Them)
Expo + RevenueCat 訂閲整合可按 5 步設定,測試則先用 RevenueCat Test Store,再進行 Apple/Google 沙盒測試。
TL;DR
- Real purchases don't work in Expo Go. RevenueCat only runs a mock Preview API Mode there. Use a development build.
- Configure the SDK once at startup with a per-platform public key.
- Gate features on an entitlement (
pro), never on product IDs. -
purchasePackagethrows when the user cancels. Checke.userCancelledand move on. -
restorePurchasesonly from a button tap, never on launch. - Test with RevenueCat's Test Store first, then Apple/Google sandboxes before review.
1. Install and rebuild
npx expo install react-native-purchases react-native-purchases-ui expo-dev-client
eas build --profile development --platform all
Skip the rebuild and you'll get `new NativeEventEmitter()` requires a non-null argument, which RevenueCat's docs call out directly.
2. Configure once
// app/_layout.tsx
useEffect(() => {
if (__DEV__) Purchases.setLogLevel(LOG_LEVEL.VERBOSE);
Purchases.configure({
apiKey: Platform.OS === 'ios'
? process.env.EXPO_PUBLIC_RC_APPLE_KEY!
: process.env.EXPO_PUBLIC_RC_GOOGLE_KEY!,
});
}, []);
Public SDK keys are fine in EXPO_PUBLIC_ vars. Secret keys never go in the app. If you have your own auth, call Purchases.logIn(userId) after sign-in.
3. Paywall
Lazy option, RevenueCat's prebuilt paywall, only shown if the user isn't already pro:
import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';
const result = await RevenueCatUI.presentPaywallIfNeeded({
requiredEntitlementIdentifier: 'pro',
});
const hasPro = [
PAYWALL_RESULT.PURCHASED,
PAYWALL_RESULT.RESTORED,
PAYWALL_RESULT.NOT_PRESENTED, // already subscribed
].includes(result);
Custom UI option:
const offerings = await Purchases.getOfferings();
const packages = offerings.current?.availablePackages ?? [];
async function buy(pkg: PurchasesPackage) {
try {
const { customerInfo } = await Purchases.purchasePackage(pkg);
if (customerInfo.entitlements.active['pro']) unlock();
} catch (e: any) {
if (!e.userCancelled) showError(e);
}
}
Render pkg.product.priceString, not a hard-coded "$4.99". Prices are per country.
4. One hook for access
export function useIsPro() {
const [isPro, setIsPro] = useState(false);
useEffect(() => {
const update = (info: CustomerInfo) =>
setIsPro(typeof info.entitlements.active['pro'] !== 'undefined');
Purchases.getCustomerInfo().then(update).catch(() => {});
Purchases.addCustomerInfoUpdateListener(update);
return () => { Purchases.removeCustomerInfoUpdateListener(update); };
}, []);
return isPro;
}
The listener means renewals, expiries and restores update the UI live. Still check on your backend for anything valuable server-side.
If you're still building the screens that sit behind the paywall, RapidNative generates a plain Expo Router + TypeScript project from a prompt that you can export and drop this code into. (I work on it.)
5. Restore button
const info = await Purchases.restorePurchases();
RevenueCat's docs say to call this only from a user action, because it can pop an OS sign-in prompt. Put the button on the paywall and in settings.
Testing
| Stage | Use |
|---|---|
| Building | RevenueCat Test Store key. No store accounts needed. |
| Pre-review | Platform keys + Apple sandbox / Google Play testing |
| Production | Platform keys. Never the Test Store key. |
Android gotcha: products won't load unless a signed build is on a closed testing track, your account is a license tester, and you've opened the opt-in link. Sandbox renewals are fast (monthly every 5 min, yearly every 30 min, max six renewals), which is great for testing expiry.
Debug cheatsheet
| Symptom | Cause |
|---|---|
| Empty offerings | Products not attached to the offering, or not active in the store |
| Nothing happens in Expo Go | Preview API Mode. Use a dev build. |
| Android products missing | Not on a testing track / not a license tester |
| Subscriber sees paywall | Checking product IDs, or missing logIn
|
What's been your biggest subscriptions headache in React Native: store setup, sandbox testing, or syncing access with your backend?
來源:DEV Community · dev.to