Rapid Friday Sale is Live!

Shop Now
MarketPlace Logo

Dec 01, 2025

Dot

4 min read

Your Guide to Stripe React Native Payments

Author

Sanket Sahu

Your Guide to Stripe React Native Payments

Taking payments in a mobile app used to be a massive headache, but with Stripe and React Native, it's become surprisingly straightforward. The official @stripe/stripe-react-native SDK gives you a suite of pre-built UI components and hooks that do all the heavy lifting. This means you can build a secure, polished checkout experience for both iOS and Android from a single codebase. Honestly, it's the go-to stack for a reason.

Building Your Foundation for Mobile Payments

Before we jump into the code, let's get the foundation right. A successful Stripe integration is more than just installing a package; it's about understanding how all the pieces securely talk to each other. Nailing this now saves you from major security headaches later on.

The whole payment dance involves three key players: your React Native app (the client), your own backend server, and the Stripe API. They work together in a specific, secure sequence so your app never has to directly touch sensitive credit card details.

This diagram breaks down the server-driven flow we're aiming for.

Diagram illustrating Stripe payment flow from a React Native app through a server to the Stripe API.

As you can see, the app's job is really just to collect payment info and show the UI. Your server handles the sensitive work of creating the payment and talking to Stripe.

Essential Prerequisites

To make this guide as smooth as possible, you’ll want to have a few things ready.

  • A Stripe Account: If you don't have one, head over to the Stripe Dashboard and create a free account. This is your command center for managing payments and grabbing your API keys.
  • A React Native Environment: You'll need a working project, of course. This guide has you covered whether you're using Expo or a bare React Native setup. If you're just starting out, our comprehensive Expo React Native tutorial is a great place to get your bearings.
  • Node.js and npm/yarn: These are essential for running your React Native project and for the simple backend server we'll build later in the guide.

Got all that? Perfect. Let's talk concepts.

Understanding Core Stripe Concepts

Stripe’s world revolves around a few key ideas. Getting a handle on these terms now will make the code we write later click into place.

The most critical concept is the PaymentIntent. Think of it as a state machine for a single transaction. It tracks the entire lifecycle of a customer's payment—from the moment it's created on your server to when it's successfully confirmed by the user, including any extra steps like 3D Secure authentication. Your server creates the PaymentIntent, and your app uses a unique client_secret from it to finalize the charge.

You'll also be working with two types of API keys:

  • Publishable Keys: These start with pk_ and are designed to be used in your client-side React Native code. They're safe to expose.
  • Secret Keys: These start with sk_ and are for your server only. You must guard these like a password. They grant powerful access to your Stripe account, so never, ever put one in your mobile app's code.

By using this server-driven approach with PaymentIntents, your app becomes PCI compliant without you having to do much. Your app never directly handles raw credit card numbers, which massively reduces your security burden. Stripe's SDK tokenizes the data on the device, ensuring that sensitive info only ever touches Stripe’s own secure servers.

This separation of concerns—client-side UI and server-side logic—is the bedrock of a secure and modern Stripe integration. It lets you create a fantastic user experience without compromising on the robust security that financial transactions demand.

Setting Up a Secure Backend for Transactions

While your React Native app is the face of the operation, the real muscle behind your payment flow has to live on a server. Trying to manage payments directly from the client is a disaster waiting to happen—it exposes your secret API keys and immediately throws you out of PCI compliance. Your backend needs to be the trusted middleman between your app and Stripe.

We're going to spin up a lightweight server using Node.js and Express, a classic and straightforward combo. This server has one primary job: to create a PaymentIntent for your app. This whole approach is designed to keep your sk_ secret key locked down in your server environment, which is the absolute cornerstone of a safe integration.

A laptop displaying 'Payment' on screen, a charging smartphone, notebook, and pen on a wooden desk.

Building the Server with Node and Express

First things first, let's get the project structure in place. Just create a new directory for the server, initialize a Node.js project, and pull in the dependencies we'll need.

You'll want a few packages:

  • express for the web server itself.
  • stripe for the official Node.js library.
  • dotenv to keep your secret keys out of your code.
  • cors to let your mobile app talk to the server.

Fire up your terminal and run these commands:

mkdir stripe-server cd stripe-server npm init -y npm install express stripe dotenv cors

And that's it. This simple foundation is all you need to create a robust endpoint for your stripe react native app. The beauty of this model is its simplicity—it does one thing and does it well.

Creating the Payment Intent Endpoint

Now for the fun part: the code. We're going to create a single POST endpoint, /create-payment-intent, that your app will call when it's time to kick off a transaction.

This endpoint will take the payment amount from your app, use it to create a PaymentIntent with the Stripe API, and then send back the client_secret. Think of the client_secret as a one-time-use token that gives your app permission to confirm that one specific payment on the front end.

Here’s what that looks like in practice, using TypeScript for some extra type safety and clarity:

// server.ts import express from 'express'; import Stripe from 'stripe'; import dotenv from 'dotenv'; import cors from 'cors';

dotenv.config();

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-04-10', typescript: true, });

const app = express(); app.use(express.json()); app.use(cors()); // Enable CORS for your app's domain in production

app.post('/create-payment-intent', async (req, res) => { const { amount } = req.body; // Amount in the smallest currency unit (e.g., cents)

try { const paymentIntent = await stripe.paymentIntents.create({ amount: amount, currency: 'usd', // Or your desired currency automatic_payment_methods: { enabled: true, }, });

res.json({ clientSecret: paymentIntent.client_secret });

} catch (error) { console.error("Error creating PaymentIntent:", error); res.status(500).json({ error: 'Failed to create PaymentIntent' }); } });

const PORT = process.env.PORT || 4242; app.listen(PORT, () => console.log(Server running on port ${PORT}));

A Note on Amounts: One of the most common trip-ups is getting the amount format wrong. Stripe expects this value in the smallest currency unit. For USD, that means cents. So, if you want to charge $19.99, you have to pass the integer 1999 to the API. You should always do this conversion on the server to prevent anyone from messing with the price on the client side.

Don't Skip Security: Use Environment Variables

Hardcoding your Stripe secret key into your files is a massive security foul. If you accidentally commit it to a public repository, your account could be drained in minutes. The dotenv package solves this by letting you store keys in a local .env file that you exclude from version control.

Just create a .env file in your server's root directory:

STRIPE_SECRET_KEY=sk_test_...your_secret_key_here

Then, make absolutely sure you add .env to your .gitignore file. This simple step is non-negotiable for any real-world application. This secure, server-driven architecture is exactly how Stripe is able to handle mind-boggling transaction volumes. To give you some perspective, Stripe processed over $31 billion during the Black Friday to Cyber Monday weekend in late 2024 alone, a huge leap from $18.6 billion in 2023. You can see more Stripe stats on Chargeflow.io.

With this secure backend in place, you now have a reliable service that creates payment sessions on demand. Your React Native app doesn't need to know any secrets—just how to ask for a client_secret and use it to finalize the payment, which is exactly what we’ll tackle next.

With your server ready to create PaymentIntents, it's time to bring the payment experience to life inside your React Native app. This is where the magic happens for the user, and a slick, trustworthy UI is absolutely non-negotiable.

Let's walk through installing the official Stripe SDK, getting it configured for your project, and building out that essential card input field.

<iframe width="100%" style="aspect-ratio: 16 / 9;" src="https://www.youtube.com/embed/1r-F3FIONl8" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>

The library we'll be using is @stripe/stripe-react-native. This is the modern, officially supported way to handle payments. It replaces the older, community-maintained packages and gives you a clean, hook-based API and pre-styled UI components that seriously simplify the whole process.

Installing the SDK for Your Project Type

Getting the Stripe SDK installed is pretty straightforward, but the exact steps change slightly depending on whether you're in an Expo Managed Workflow or a bare React Native project. I'll cover both so you're good to go, no matter your setup.

For Expo Managed Workflows

If you're all-in on Expo, life is good. The process is incredibly simple because Expo's build system and config plugins handle all the tricky native configuration for you.

Just pop open your terminal and run this command in your project's root:

npx expo install @stripe/stripe-react-native

Using expo install here is key. It's much safer than npm or yarn because it guarantees you get a version of the SDK that plays nicely with your specific Expo SDK version, which helps you dodge those frustrating native code mismatches.

For Bare React Native Projects

For those of you running a bare React Native setup, the process is a familiar two-step: install the package, then link the native dependencies.

First, add the package using your favorite manager:

npm install @stripe/stripe-react-native

Or if you prefer Yarn:

yarn add @stripe/stripe-react-native

Once that's done, you need to link the native iOS modules. Just navigate into your ios directory and run the pod installer:

cd ios && pod install

On the Android side, the library will usually be auto-linked. A quick rebuild of your app should finish the job and make sure all the native components are ready for your JavaScript code to use.

Before we dive into the code, let's quickly compare what you're signing up for with each approach.

Stripe Integration Paths for React Native

Feature Expo Managed Workflow Bare React Native
Initial Setup A single npx expo install command handles everything. Requires npm/yarn install plus manual pod install for iOS.
Native Config Fully automated by Expo's config plugins. You're responsible for managing native project files (Xcode, Gradle).
Updates expo upgrade manages SDK and dependency compatibility. You manually update packages and resolve native conflicts.
Maintenance Lower overhead; Expo abstracts away native complexities. Higher overhead; requires native iOS and Android expertise.

Ultimately, Expo offers a much smoother ride for setup and maintenance, while the bare workflow gives you maximum control if you need to dive deep into native code.

Configuring the StripeProvider

With the SDK installed, you need to initialize it with your publishable key (the one that starts with pk_test_). The cleanest way to handle this is by wrapping your entire application—or at least the payment-related parts—in the StripeProvider component.

This provider cleverly uses React's Context API to make the Stripe instance available to any child component. This means you can use powerful hooks like useConfirmPayment anywhere in your payment flow without the headache of prop drilling.

Here’s what a typical setup looks like in your main App.tsx file:

import { StripeProvider } from '@stripe/stripe-react-native'; import MyPaymentScreen from './screens/MyPaymentScreen';

export default function App() { return ( <StripeProvider publishableKey="pk_test_YOUR_PUBLISHABLE_KEY" // You'll need this for Apple Pay later on merchantIdentifier="merchant.com.your-app-identifier" > <MyPaymentScreen /> </StripeProvider> ); }

Pro Tip: Please, don't hardcode your keys directly in the code! A much better practice is to store them in an environment variable file (like .env) and load them at runtime. This makes it a breeze to switch between your test and live keys without touching your source code.

This architecture gives you a solid foundation for building out the rest of your payment flow. It's a clean separation of concerns that will pay dividends as your app grows.

Implementing the CardField UI Component

Now for the fun part. With the provider in place, you can start using Stripe's pre-built UI components. The most fundamental of these is <CardField />.

This single component is a powerhouse. It renders a complete credit card input form—card number, expiration date, CVC, and postal code—while handling all the validation, formatting, and security for you right out of the box.

Dropping it into your payment screen is as simple as adding the component to your JSX. You can then use a simple useState hook to keep track of the details the user enters.

Here's a practical example:

import React, { useState } from 'react'; import { View, Button, Alert } from 'react-native'; import { CardField, CardFieldInput } from '@stripe/stripe-react-native';

const CheckoutScreen = () => { // We'll use this state to track if the card form is complete const [cardDetails, setCardDetails] = useState<CardFieldInput.Details | null>(null);

const handlePayPress = () => { // A simple check before trying to process a payment if (!cardDetails?.complete) { Alert.alert("Please fill in the card details completely."); return; } // Logic to confirm the payment will go here console.log("Card details are complete and valid. Ready to pay!"); };

return ( <View> <CardField postalCodeEnabled={false} // You can disable this if you don't need it style={{ height: 50, width: '100%', marginVertical: 20, }} onCardChange={(details) => { setCardDetails(details); }} /> <Button title="Pay Now" onPress={handlePayPress} // The button is disabled until the form is valid disabled={!cardDetails?.complete} /> </View> ); };

The onCardChange callback is what makes this component so great. It fires on every keystroke, giving you a state object that includes a complete boolean. This makes it ridiculously easy to enable or disable the "Pay" button, preventing users from trying to submit incomplete or invalid information.

To jumpstart your UI work, you might want to check out some pre-built React Native Expo templates. They often come with beautifully styled payment screens already integrated, which can save you a ton of time and let you focus on your core business logic instead of wrestling with stylesheets.

Okay, you've got your UI hooked up, and your server is ready to churn out PaymentIntents. This is the moment of truth: actually taking the payment. This is where all the client-side pieces click into place to make a secure, successful transaction happen inside your stripe react native app.

Close-up of hands interacting with a smartphone screen, with a laptop displaying a blue interface in the background.

The star of the show here is the useConfirmPayment hook from the SDK. It does all the heavy lifting for the client-side confirmation flow. It takes the tokenized card details from your <CardField /> and the client_secret we generated, sends them securely to Stripe, and then tries to authorize the payment.

Kicking Off the Payment

The useConfirmPayment hook gives you back two things: a confirmPayment function and a loading state. You'll call that function right when your user hits the "Pay" button. It’s a clean, promise-based API that will either resolve with the payment details on success or an error object if things go wrong.

Let's plug this into the CheckoutScreen component we were working on earlier.

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

const CheckoutScreen = () => { const { confirmPayment, loading } = useConfirmPayment(); const [cardDetails, setCardDetails] = useState<CardFieldInput.Details | null>(null);

const handlePayPress = async () => { if (!cardDetails?.complete) { Alert.alert("Please fill out your card details."); return; }

// First, we grab the client_secret from our server.
const response = await fetch('YOUR_SERVER_URL/create-payment-intent', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: 1999 }), // e.g., $19.99
});
const { clientSecret } = await response.json();

// Now, we confirm the payment on the client.
const { paymentIntent, error } = await confirmPayment(clientSecret, {
  paymentMethodType: 'Card',
});

// Finally, handle the result.
if (error) {
  Alert.alert(`Payment failed: ${error.message}`);
} else if (paymentIntent) {
  Alert.alert("Payment successful!");
  console.log('Payment succeeded with ID:', paymentIntent.id);
}

};

// ... rest of the component };

This whole flow is incredibly solid. One of the best parts is that if the payment requires something like 3D Secure authentication, the SDK automatically handles it by popping up the necessary native modal. You don’t have to write a single extra line of code to deal with those complex Strong Customer Authentication (SCA) rules.

Go Next-Level with Apple and Google Pay

A simple card field is a must-have, but adding digital wallets like Apple Pay and Google Pay is a massive upgrade. They offer a checkout experience that's faster, more familiar, and often more secure—a combination that can give your conversion rates a serious boost. And honestly, integrating them with the Stripe SDK is surprisingly pain-free.

The first thing you need to do is check if the user's device can even handle these payment methods. The useApplePay and useGooglePay hooks give you isApplePaySupported and isGooglePaySupported booleans, which makes it easy to conditionally show the right payment button.

Offering one-tap payment options isn't a luxury anymore; it’s a core user expectation. People trust these native systems, and removing the friction of digging out a credit card is probably the single biggest improvement you can make to your checkout flow.

Once you know the device is good to go, you can trigger the native payment sheet.

Weaving in Digital Wallets

Let's walk through adding an Apple Pay button. The process for Google Pay is almost identical, so you can easily adapt this logic.

  1. Check for Support: Use the isApplePaySupported flag to decide if the Apple Pay button should even be visible.
  2. Show the Payment Sheet: Call the presentApplePay function, passing in details about the cart like the total and currency.
  3. Confirm the Payment: After the user authenticates with Face ID or Touch ID, Stripe gives you a paymentMethod. You'll use this to confirm the PaymentIntent you created earlier.

Here's what that flow looks like in a simplified example:

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

const { isApplePaySupported, presentApplePay, confirmApplePayPayment } = useApplePay();

const handleApplePay = async () => { if (!isApplePaySupported) return;

const { error: presentError } = await presentApplePay({ cartItems: [{ label: 'My Awesome Product', amount: '19.99' }], country: 'US', currency: 'USD', });

if (presentError) { // This means the user cancelled or something else went wrong. return; }

// Now get the client_secret from your server. const { clientSecret } = await fetchClientSecret();

const { error: confirmError } = await confirmApplePayPayment(clientSecret);

if (confirmError) { Alert.alert('Payment failed', confirmError.message); } else { Alert.alert('Payment successful!'); } };

Adding these digital wallet options makes your payment screen feel so much more polished and user-friendly. A cohesive, native-feeling UI is crucial for building trust, and if you're looking to speed up that process, our guide on building a React Native design system is packed with useful insights.

Handling Post-Payment Events with Webhooks

So, your React Native app pops up a "Payment Successful!" message. Job done, right? Not so fast.

Relying only on what the app thinks happened is a recipe for disaster. What if the user kills the app right after paying? Or their Wi-Fi cuts out at that exact moment? You've just created a silent failure—a paid-for order that never gets fulfilled. This is how you get angry customers.

The only truly reliable way to handle this is with Stripe Webhooks. Think of a webhook as an automated message that Stripe sends to your server the instant something important happens, like a payment succeeding. This behind-the-scenes communication is the foundation of a professional post-payment system.

A hand holds a smartphone displaying a banking app with numbers and a checkmark, beside a blue passbook on a wooden table.

This approach separates your critical business logic—like updating inventory or sending a receipt—from the user's interaction in the app. It’s how you guarantee that every successful payment triggers the correct action on your backend, regardless of what's happening on the user's phone.

Creating a Secure Webhook Endpoint

First things first, you'll need to set up a new endpoint on your server that does nothing but listen for these pings from Stripe. This endpoint has to be publicly accessible, of course, so Stripe’s servers can find it.

But you can't just trust any random request that shows up. You have to be certain it’s actually coming from Stripe. Thankfully, Stripe makes this easy by including a special stripe-signature header in every webhook request. Your server can use this signature, along with a secret key you get from your Stripe dashboard, to cryptographically verify that the request is legit.

Here’s a practical example of how you'd set up a verified listener using Express and the official Stripe Node.js library:

// Add a separate route for webhooks // IMPORTANT: Use express.raw for the body parser on this route app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET!;

let event;

try { event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); return; }

// Handle the event switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object; console.log('PaymentIntent was successful!', paymentIntent.id); // Fulfill the order, send receipt, etc. break; // ... handle other event types default: console.log(Unhandled event type ${event.type}); }

res.json({received: true}); });

Pay close attention to that express.raw({type: 'application/json'}) middleware. This is the secret sauce. The signature verification needs the raw, untouched request body. If you use the standard express.json() parser, it'll modify the body, and the signature check will fail every single time. It's a common "gotcha."

Processing Events and Idempotency

Inside your handler, a switch statement is perfect for routing different event types. For most basic payment flows, the one you care about most is payment_intent.succeeded. Once you receive that, you know the money is good, and you can confidently kick off your backend processes:

  • Database Updates: Mark the order as 'paid' in your database.
  • Email Receipts: Tell your email service to send out a confirmation.
  • Fulfillment: Ping your shipping or logistics system to get the product out the door.

A critical concept here is idempotency. It’s a fancy word for a simple idea: if you receive the same event multiple times, you should only process it once. Stripe can sometimes resend a webhook if it doesn't get a confirmation. Your code needs to be smart enough to handle this without, for example, shipping the same product twice. A quick check to see if the order has already been fulfilled is all it takes.

This webhook-driven architecture isn't just a "nice-to-have"; it's what makes an e-commerce app robust enough for the real world. Stripe's flexibility in mobile frameworks like React Native is a huge plus. While app stores enforce strict rules and high fees for digital in-app purchases, using Stripe for physical goods or services gives you total control. If you're curious about the nuances, Adapty has a great breakdown on using Stripe for in-app purchases.

Got Questions? We've Got Answers

When you're dealing with payments, a lot of specific questions pop up. Let's tackle some of the most common ones developers have when they're working with the Stripe React Native SDK to help you sidestep any potential headaches.

Can I Use Stripe for In-App Purchases on iOS and Android?

This is a big one, and getting it wrong can get your app rejected. The short answer is: it depends on what you're selling.

While you technically can wire it up, using Stripe to sell digital goods—think unlocking premium features, buying in-game currency, or app subscriptions—is almost always a violation of both Apple's and Google's app store rules.

For anything digital that's consumed inside the app, you absolutely have to use their native In-App Purchase APIs. Where Stripe shines, and where it’s fully compliant, is for:

  • Selling physical goods that you'll ship to a customer.
  • Charging for real-world services like consultations, event tickets, or class bookings.
  • Processing B2B payments for services or products.

Stick to these lines, and you’ll sail through the app review process without a problem.

How Does the SDK Handle 3D Secure Authentication?

This is one of the best parts about using the modern SDK. The @stripe/stripe-react-native library handles Strong Customer Authentication (SCA), including 3D Secure, pretty much automatically.

When your app calls the confirmPayment function, Stripe's systems do the heavy lifting. They analyze the transaction's risk level and check if the card issuer requires an extra layer of security.

If it does, the SDK natively pops up the right modal or web view for the user to verify themselves. You don't have to write a single line of client-side code to manage this flow—it's all orchestrated by the PaymentIntents API. It's a huge time-saver that ensures you're meeting today's tough security standards.

The seamless, automatic handling of 3D Secure is a core benefit of using the modern PaymentIntents flow. It takes a really complex security requirement off your plate so you can focus on building a great user experience.

What Is the Best Way to Test My Stripe Integration?

Testing a payment flow isn't just a good idea; it's non-negotiable. Your go-to tool here should always be Stripe's test mode. It's a full-fledged sandbox environment that lets you mimic the entire payment lifecycle without a single real dollar changing hands.

First thing's first: make sure you're using your test API keys (the ones starting with pk_test_... and sk_test_...) in both your app and on your server. From there, Stripe gives you a whole list of special test card numbers you can use to trigger different outcomes:

  • A perfectly successful payment.
  • A card declined for insufficient funds.
  • A card that requires 3D Secure authentication.
  • Cards from various brands like Visa, Mastercard, and Amex.

For the backend, the Stripe CLI is your best friend. You can use it to fire off mock webhook events directly to your server's endpoint. This is absolutely critical for making sure your post-payment logic—like sending email receipts, updating your database, or kicking off a shipping process—is working exactly as expected before you go live.


Ready to build your app faster? theappmarket offers a massive collection of production-ready React Native templates that can save you hundreds of hours. Get started today at https://theappmarket.io.

Our website uses cookies to improve your browsing experience. You can choose to enable or disable cookies, except for the essential ones. To manage permissions, Please click on "Manage Preferences." Read more about how we use cookies on our Cookie Policy page.

Note: For details on our ethical information use, visit our Privacy Policy page.