← All Posts
React NativeStripePayments

Stripe Payments in React Native: The Complete 2025 Integration Guide

April 1, 2025·9 min read·Muhammad Saad

I've wired up Stripe in two production React Native apps — FanGenie and SplitMart — both now live on the App Store. This guide covers the path that actually works in 2025, not the outdated tutorials that still use the old Elements API or manual card field handling.

Setup: The Right Packages

Use @stripe/stripe-react-native. Don't use the older stripe-react-native-sdk or roll your own WebView approach. The official package gives you native 3DS, Apple Pay, and Google Pay out of the box.

bash
npx expo install @stripe/stripe-react-native

Wrap your app root with the StripeProvider:

tsx
import { StripeProvider } from '@stripe/stripe-react-native';

export default function App() {
  return (
    <StripeProvider publishableKey={process.env.EXPO_PUBLIC_STRIPE_KEY!}>
      <RootNavigator />
    </StripeProvider>
  );
}

PaymentSheet: The Only Flow You Need

Forget building a custom card form. Stripe's PaymentSheet handles card entry, Apple Pay, Google Pay, saved cards, and 3DS in one pre-built UI. PCI compliance is Stripe's problem, not yours. Here's the complete flow:

tsx
import { useStripe } from '@stripe/stripe-react-native';

export function CheckoutButton({ amount }: { amount: number }) {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();

  async function handlePayment() {
    // 1. Create PaymentIntent on your backend
    const { paymentIntent, ephemeralKey, customer } = await fetch(
      '/api/create-payment-intent',
      { method: 'POST', body: JSON.stringify({ amount }) }
    ).then(r => r.json());

    // 2. Init the sheet
    const { error: initError } = await initPaymentSheet({
      merchantDisplayName: 'Your App',
      customerId: customer,
      customerEphemeralKeySecret: ephemeralKey,
      paymentIntentClientSecret: paymentIntent,
      allowsDelayedPaymentMethods: false,
    });
    if (initError) return;

    // 3. Present — handles everything including 3DS
    const { error } = await presentPaymentSheet();
    if (!error) {
      // Payment confirmed — update your UI
    }
  }

  return <Button onPress={handlePayment} title="Pay Now" />;
}

The Backend: Node.js PaymentIntent

Your backend needs to create the PaymentIntent and return the three secrets. Never do this client-side — your secret key would be exposed.

ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

app.post('/api/create-payment-intent', async (req, res) => {
  const { amount } = req.body;

  const customer = await stripe.customers.create();
  const ephemeralKey = await stripe.ephemeralKeys.create(
    { customer: customer.id },
    { apiVersion: '2024-06-20' }
  );
  const paymentIntent = await stripe.paymentIntents.create({
    amount, // in cents
    currency: 'usd',
    customer: customer.id,
    automatic_payment_methods: { enabled: true },
  });

  res.json({
    paymentIntent: paymentIntent.client_secret,
    ephemeralKey: ephemeralKey.secret,
    customer: customer.id,
  });
});

Webhooks: The Part Everyone Skips

Don't rely on the client to confirm payment success. The app can close, crash, or lose network after the PaymentSheet resolves. Stripe webhooks are your source of truth. Listen for payment_intent.succeeded and update your database there.

Critical: Always verify payment server-side via webhooks before granting access to paid features. Client-side confirmation can be spoofed or interrupted.

  • payment_intent.succeeded → unlock premium features
  • customer.subscription.created → handle recurring billing
  • invoice.payment_failed → notify user to update card
  • charge.dispute.created → flag for manual review

Apple Pay & Google Pay

Apple Pay is enabled automatically if you pass the merchantDisplayName to initPaymentSheet and add the Apple Pay entitlement. For Expo, add the Stripe plugin to your app.config.ts and run a new build — no native code changes needed.

RevenueCat: When Stripe Isn't the Right Choice

If you're building subscriptions, consider RevenueCat instead of Stripe. RevenueCat handles the App Store and Play Store in-app purchase APIs (which Apple requires for digital goods sold through iOS apps). It also unifies analytics, paywalls, and receipt validation across platforms. For FanGenie's subscription tier, I used RevenueCat and saved two weeks of native billing code.

Common Mistakes

  1. 1.Using test keys in production (yes, this happens — use environment variables)
  2. 2.Not handling 3DS — some European cards require it, your app will silently fail without it
  3. 3.Confirming payment on the client only — always verify with webhooks
  4. 4.Forgetting to add STRIPE_WEBHOOK_SECRET to production environment
  5. 5.Not setting currency correctly — amount is always in the smallest currency unit (cents)

Need help building your app?

I build React Native and iOS apps for startups — from idea to App Store.