Payments· 6 min read

Stripe in React Native: Payment Sheet, Apple Pay and Google Pay

Accept payments in React Native with Stripe: Payment Sheet setup in Expo, the server side, Apple Pay and Google Pay, webhooks, and when Stripe is allowed.

By

Full-Stack, Mobile & AI Engineer

Stripe is the default way many teams take card payments, and its React Native SDK has become good enough that a secure, polished checkout takes a day or two instead of a week. The parts that still trip teams up are not the UI: they're the server side, webhooks, and knowing when Apple and Google require their own billing instead.

This guide shows how to add Stripe to a React Native app with Expo in 2026: the Payment Sheet, the server endpoint it needs, Apple Pay and Google Pay, webhooks that confirm payment, and the store rules that decide whether you can use Stripe at all.

Key takeaways

  • Use Stripe's Payment Sheet (@stripe/stripe-react-native) for a prebuilt, secure checkout that supports cards, Apple Pay and Google Pay.
  • Your server creates the PaymentIntent and sends the app a client secret. The app never decides the amount.
  • Webhooks confirm the payment. Fulfil orders on payment_intent.succeeded, not when the sheet closes.
  • Stripe is allowed for physical goods and real-world services, and for some cases like person-to-person services. Digital content in iOS apps generally needs Apple's in-app purchase, apart from US-storefront external links.
  • Standard US card pricing is 2.9% + 30¢ per successful charge; check Stripe's pricing page for your country.

How Stripe works in a mobile app

Stripe never trusts the app with the important decisions. Your server creates a PaymentIntent for the amount, the app shows Stripe's Payment Sheet so card details go straight to Stripe (never to your servers), and Stripe tells your server the outcome through a webhook. Stripe also offers a lower-level CardField for a custom card form, but the Payment Sheet handles cards, wallets, saved methods and 3D Secure for you, which is why it's the default choice. It works in Expo with a development build.

Step 1: install and configure

npx expo install @stripe/stripe-react-native

Add the config plugin in app.json so the native setup for Apple Pay and Google Pay is applied at build time:

{
  "expo": {
    "plugins": [
      [
        "@stripe/stripe-react-native",
        { "merchantIdentifier": "merchant.com.yourcompany.app", "enableGooglePay": true }
      ]
    ]
  }
}

Wrap your app with the provider:

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

export default function App() {
  return (
    <StripeProvider
      publishableKey={STRIPE_PUBLISHABLE_KEY}
      merchantIdentifier="merchant.com.yourcompany.app"
      urlScheme="yourapp"
    >
      <Root />
    </StripeProvider>
  );
}

Because the SDK includes native code, you'll run it in a development build, not Expo Go.

Step 2: the server endpoint

Your backend creates the customer, an ephemeral key and the PaymentIntent, using your secret key, which never goes in the app:

// POST /payment-sheet (Node, with the official stripe package)
const customer = await stripe.customers.create();
const ephemeralKey = await stripe.ephemeralKeys.create(
  { customer: customer.id },
  { apiVersion: STRIPE_API_VERSION } // the API version your mobile SDK expects
);
const paymentIntent = await stripe.paymentIntents.create({
  amount: order.totalInCents, // calculated on the server from the order
  currency: "usd",
  customer: customer.id,
  automatic_payment_methods: { enabled: true },
  metadata: { orderId: order.id },
});

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

In a real app you'd reuse the Stripe customer for a signed-in user rather than creating one each time, so saved cards appear on their next purchase.

Step 3: present the Payment Sheet

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

export function useCheckout() {
  const { initPaymentSheet, presentPaymentSheet } = useStripe();

  return async function checkout(orderId: string) {
    const { paymentIntent, ephemeralKey, customer } = await api.post("/payment-sheet", { orderId });

    const { error: initError } = await initPaymentSheet({
      merchantDisplayName: "Your Studio",
      customerId: customer,
      customerEphemeralKeySecret: ephemeralKey,
      paymentIntentClientSecret: paymentIntent,
      applePay: { merchantCountryCode: "US" },
      googlePay: { merchantCountryCode: "US", testEnv: __DEV__ },
      returnURL: "yourapp://stripe-redirect",
    });
    if (initError) throw initError;

    const { error } = await presentPaymentSheet();
    if (error) return { ok: false, reason: error.code }; // includes "Canceled"
    return { ok: true }; // now confirm with the server
  };
}

When presentPaymentSheet resolves without an error, the payment has been submitted. Still check the order's status from your server before showing a final confirmation.

Step 4: confirm with webhooks

Stripe sends events to a webhook endpoint you register. Fulfil the order there:

  • payment_intent.succeeded: mark the order paid, send the receipt, book the class.
  • payment_intent.payment_failed: record the failure and let the user retry.

Verify each webhook's signature with your endpoint secret, store event IDs so a repeated event is ignored, and respond quickly, doing slow work in a background job.

From the field: On MOVE, a subscription fitness platform with 100K+ active users, I built the mobile app, the API layer, payments and analytics, with Stripe and RevenueCat in the stack. Across 12+ apps with payments, the pattern doesn't change: the app starts the payment, the server decides the amount, and a verified webhook decides whether it happened.

Apple Pay and Google Pay

The Payment Sheet shows Apple Pay and Google Pay automatically when you pass the applePay and googlePay options and the device supports them. For Apple Pay you also need a merchant identifier in your Apple Developer account and a payment processing certificate, which Stripe's dashboard helps you create. Google Pay needs enableGooglePay in the plugin and a production review in the Google Pay console before going live.

Saved cards and repeat payments

For apps where people pay more than once (bookings, deliveries, top-ups), saving a card makes the second payment a single tap:

  • Reuse one Stripe customer per signed-in user, stored against your user record, instead of creating a new customer for every checkout.
  • Pass that customer and an ephemeral key to the Payment Sheet, and it shows the user's saved cards automatically.
  • Use a SetupIntent when you want to save a card without charging it, for example during onboarding or before a free trial of a real-world service.

Memberships and recurring charges

For recurring payments that are allowed outside in-app purchase, such as gym memberships or service subscriptions, use Stripe Billing on the server: create a subscription for the customer, and let Stripe handle renewals, retries and invoices. Your app only collects the payment method. Listen for invoice events (invoice.paid, invoice.payment_failed) in your webhook to extend or pause access, and send users to a page where they can update their card when a renewal fails.

Testing before you go live

  • Use test-mode keys in development and preview builds, and live keys only in production.
  • Use Stripe's test cards: 4242 4242 4242 4242 succeeds, and 4000 0025 0000 3155 requires 3D Secure authentication, which is the flow most likely to break in a mobile app.
  • Forward webhooks to your machine with the Stripe CLI (stripe listen --forward-to localhost:3000/webhooks) so you can test fulfilment locally.
  • Test cancellations and failures: close the sheet midway, decline a card, and lose connection after paying, then confirm your server still ends up with the right order status.

When you're allowed to use Stripe in an app

What you selliOSAndroid
Physical goods (products shipped to people)Stripe allowedStripe allowed
Real-world services (classes, bookings, rides)Stripe allowedStripe allowed
Real-time person-to-person services (tutoring, training, consultations)Stripe allowed (guideline 3.1.3(d))Check Google's payments policy
Digital content or features used in the appIn-app purchase required (US storefront may link out)Google Play Billing required (alternatives in some regions)

FAQ

How do I accept payments in a React Native app?

For physical goods and services, use a payment provider SDK such as Stripe's: your server creates a PaymentIntent, the app shows the Payment Sheet, and a webhook confirms success. For digital content, use Apple's and Google's in-app purchase systems, often through a service like RevenueCat.

Does Stripe's React Native SDK work with Expo?

Yes. Install @stripe/stripe-react-native with npx expo install, add its config plugin for Apple Pay and Google Pay, and run the app in a development build. It isn't available in Expo Go because it includes native code.

How much does Stripe charge?

Stripe's standard US pricing for domestic cards is 2.9% plus 30¢ per successful transaction, with different rates in other countries and for international cards. Check Stripe's pricing page for your account's country.

Can I use Stripe instead of Apple in-app purchase?

Only for what Apple allows: physical goods, services consumed outside the app, certain person-to-person services, and, on the US storefront, links to external purchases. Digital content and features used inside an iOS app generally require Apple's in-app purchase.

Next steps

If you're adding payments to a React Native app and want the client, server and webhooks done right first time, I can build the whole flow and test it end to end. Get in touch, or see how I work as a React Native developer.

Sources

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related