Browse docs
Your First Project

Charge For It

Put a screen behind a paywall with one component, and understand when Apple requires in-app purchase instead of Stripe.

Updated Aug 2026
On this page

Payments sit behind one PaymentAdapter with three implementations: RevenueCat for in-app purchases, Stripe for web checkout, and noop for when nothing is configured. Your screens never branch on which one is active.

Gate a screen in one line

Take the Notes screen from Build Your First Screen and make it paid:

import { Gated } from '@repo/payments';

export default function NotesScreen() {
  return (
    <Gated entitlement="pro">
      {/* everything you already wrote */}
    </Gated>
  );
}

Without the pro entitlement, the user gets the paywall screen. With it, they get your screen. If you need the raw boolean, use useEntitlement("pro").

What you see before you configure anything

With no RevenueCat key, the paywall renders a placeholder that names the missing key. That is intentional, so the route works while you build. It also means you cannot test a real purchase in a browser: in-app purchases need a development build on a real device plus a store sandbox account.

The in-app purchase demo screen, showing a status panel reading RevenueCat disabled on web, a declarative Gated example rendering an upgrade card, and an imperative useEntitlement example
The IAP demo screen with no keys configured. The status panel says exactly which part is off, and both gating patterns still render, which is what lets you build the screens before you have a store account.

Plan for that. It is the one part of this boilerplate you cannot exercise on your laptop alone.

RevenueCat, for in-app purchases

Subscriptions and one-time purchases go through react-native-purchases. The backend ingests RevenueCat webhooks into an entitlements table, so one user can hold several entitlements at once.

Stripe, for web checkout

For non-IAP payments the boilerplate talks to Stripe's REST API directly rather than bundling the full SDK. The backend verifies the webhook signature and handles repeat deliveries safely.

Leave STRIPE_SECRET_KEY empty and /payments/* returns 501, which keeps the surface off until you want it.

Know Apple's rules before you ship

Digital goods used inside your app generally must use in-app purchase. Stripe is for physical goods, services consumed outside the app, and certain business models. Getting this wrong is a rejection, sometimes after your app is already live. The full decision matrix is in docs/recipes/non-iap-payments.md. Read it before your first submission.

Testing the flow

@repo/payments ships 22 passing tests covering the adapter contract, entitlement resolution, and webhook handling. Run them with pnpm --filter @repo/payments test.

Next: Make It Yours.

Stop reinventing the app shell. Start shipping.

React Native and Expo boilerplate built for Claude Code, Cursor and Copilot. Auth, payments, push and offline sync already wired, with a 37-row map telling your agent where each one lives.

Get it for $59 →