How it works
Payments are two-sided: your server creates a PaymentIntent with the secret key and returns a clientSecret; your app uses the publishable key to present the native sheet with that secret. Never put the secret key in the app.
1. Backend — create the PaymentIntent
// Your server (Node) — never create PaymentIntents on the device.
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// POST /payment-sheet
export async function createPaymentSheet(amount) {
const paymentIntent = await stripe.paymentIntents.create({
amount, // in the smallest unit, e.g. 1999 = $19.99
currency: 'usd',
});
return { clientSecret: paymentIntent.client_secret };
}2. App — present the Payment Sheet
Wrap the app in StripeProvider once, then init and present the sheet from the checkout button:
// npx expo install @stripe/stripe-react-native
import { StripeProvider, useStripe } from '@stripe/stripe-react-native';
import { Button, Alert } from 'react-native';
function CheckoutButton() {
const { initPaymentSheet, presentPaymentSheet } = useStripe();
async function pay() {
// 1. get a clientSecret from YOUR backend
const res = await fetch('https://your-api.com/payment-sheet', { method: 'POST' });
const { clientSecret } = await res.json();
// 2. init + present the native sheet
await initPaymentSheet({ merchantDisplayName: 'My App', paymentIntentClientSecret: clientSecret });
const { error } = await presentPaymentSheet();
Alert.alert(error ? 'Payment failed' : 'Success', error?.message ?? 'Thanks!');
}
return <Button title="Pay $19.99" onPress={pay} />;
}
// Wrap your app once, near the root:
export function Root({ children }) {
return <StripeProvider publishableKey="pk_test_...">{children}</StripeProvider>;
}Key APIs
| Prop | Type | Default | Description |
|---|---|---|---|
| StripeProvider | component | — | Wrap the app; takes your publishable key. |
| initPaymentSheet | fn | — | Configure the sheet with the clientSecret. |
| presentPaymentSheet | fn | — | Show the native sheet; resolves with { error } or success. |
| merchantDisplayName | string | — | Shown to the user in the sheet. |
| amount | number | — | Set on the SERVER, in the smallest currency unit (cents). |
Gotchas
- Never create PaymentIntents or use the secret key on the device. That endpoint must live on your server.
amountis in the smallest unit —1999means $19.99, not $1,999.- Payment Sheet doesn’t run in the web preview or Expo Go for real charges — test on a device with a dev build. Use Stripe test cards (4242 4242 4242 4242).
- Apple Pay / Google Pay need extra config (merchant ID, entitlements). Card payments work with just the keys.
FAQ
How do I add Stripe to a React Native app? Install @stripe/stripe-react-native, wrap the app in StripeProvider, and present the Payment Sheet with a clientSecret from your backend.
Do I need a backend for Stripe? Yes — a small endpoint to create the PaymentIntent. The secret key can never live in the app.