Blog
How to
Integrate Stripe into React Native – Complete Guide
Integrating a secure, frictionless checkout experience is a critical
milestone for any mobile application. To integrate Stripe
Integrating a secure, frictionless checkout experience is a critical milestone for any mobile application. To integrate Stripe into React Native, developers rely on the official @stripe/stripe-react-native SDK. This native wrapper allows mobile applications to securely collect payment details, support digital wallets like Apple Pay and Google Pay, and maintain strict PCI-DSS compliance without handling sensitive card data directly. The core integration workflow requires initializing the StripeProvider with your publishable key, fetching a PaymentIntent client secret from your backend server, and presenting the pre-built Payment Sheet or a custom checkout flow to complete the transaction.
The Architecture of Secure Mobile Payments: Stripe & React Native
Before writing code, it is essential to understand how data flows between your React Native frontend, your secure backend server, and Stripe’s API. This three-legged authentication system ensures that sensitive credit card information never touches your servers, minimizing your PCI compliance scope to the simplest level (SAQ A).
The standard payment lifecycle relies on the PaymentIntent API. A PaymentIntent is an object created on Stripe’s servers that tracks the lifecycle of a customer’s transaction, from initiation to successful capture.
| Step | Actor | Action Description | Data Exchanged |
|---|---|---|---|
| 1. Initiate | Mobile App | User clicks “Pay Now” or proceeds to checkout. | Cart items, order total, currency. |
| 2. Create Intent | Backend Server | Requests a PaymentIntent from Stripe’s API. | Amount, currency, metadata. |
| 3. Handshake | Stripe API | Generates the PaymentIntent and returns a Client Secret. | client_secret (e.g., pi_123_secret_abc). |
| 4. Initialize UI | Mobile App | Configures the Stripe SDK with the Client Secret. | Client Secret, customer billing details. |
| 5. Authorize | Stripe SDK | Collects payment details and securely submits them to Stripe. | Tokenized card data, biometric authorization (Apple/Google Pay). |
| 6. Confirm | Stripe API | Processes the transaction with the card network and updates status. | Success/Failure status, webhook event payload. |
By leveraging this architecture, your mobile application acts merely as a secure presenter of Stripe’s tokenized UI components, ensuring that your codebase remains secure, modern, and compliant with global financial regulations.
Prerequisites & Environment Setup
To successfully implement this integration, ensure your development environment meets the following baseline requirements:
- Node.js: Version 16.x or higher.
- React Native: Version 0.65 or higher (this guide focuses on functional components and React Hooks).
- Stripe Account: Access to the Stripe Dashboard to retrieve your Publishable Key and Secret Key.
- CocoaPods: Installed on macOS for iOS build configurations.
- Android Studio & Xcode: Correctly configured for compiling native modules.
1. Installing the Official Stripe SDK
Run the following command in your React Native project root to install the official Stripe SDK wrapper:
npm install @stripe/stripe-react-native
Or, if you prefer using Yarn:
yarn add @stripe/stripe-react-native
2. iOS Configuration Steps
The Stripe SDK requires iOS 13.0 or higher. Navigate to your ios directory and open the Podfile. Ensure the platform version is set correctly:
platform :ios, '13.0'
Run CocoaPods installation to link the native dependencies:
cd ios && pod install && cd ..
If you plan to use Apple Pay, you must also enable the “Apple Pay” capability in Xcode under your project’s Signing & Capabilities tab and link a valid merchant identifier.
3. Android Configuration Steps
Open your Android project in Android Studio or edit the files directly. In android/build.gradle, ensure your minSdkVersion is set to at least 21 (Android 5.0):
buildscript { ext { buildToolsVersion = "31.0.0" minSdkVersion = 21 compileSdkVersion = 31 targetSdkVersion = 31 }}
To support modern material design elements used by Stripe’s native UI, ensure your application theme in android/app/src/main/res/values/styles.xml inherits from a MaterialComponents theme:
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <!-- Customize your theme here --></style>
Initializing the SDK with StripeProvider
The StripeProvider component must wrap your application’s root component (or at least the entire checkout context) to initialize the Stripe SDK. This component injects the global configuration required by all child Stripe hooks and components.
import React from 'react';import { SafeAreaView, StyleSheet } from 'react-native';import { StripeProvider } from '@stripe/stripe-react-native';import CheckoutScreen from './screens/CheckoutScreen';const App = () => { return ( <StripeProvider publishableKey="pk_test_YourPublishableKeyHere" merchantIdentifier="merchant.com.yourcompany.app" // Required for Apple Pay threeDSecureParams={{ backgroundColor: '#FFFFFF', timeout: 5, }} > <SafeAreaView style={styles.container}> <CheckoutScreen /> </SafeAreaView> </StripeProvider> );};const styles = StyleSheet.create({ container: { flex: 1, },});export default App;
Pro Tip: Never hardcode your publishable key in production builds. Utilize environment variables via libraries like react-native-config or expo-constants to dynamically inject keys based on your build scheme (development, staging, or production).
Building the Backend Server: Creating the PaymentIntent
To keep your integrations secure, you must never generate or handle Stripe Secret Keys on the client-side mobile application. Doing so exposes your Stripe account to total compromise. Instead, set up a lightweight backend server (Node.js/Express, Python, or serverless functions) to handle the PaymentIntent creation.
Below is a production-grade Node.js/Express endpoint that creates a PaymentIntent and returns the client_secret to your React Native app:
const express = require('express');const app = express();const stripe = require('stripe')('sk_test_YourSecretKeyHere');app.use(express.json());app.post('/create-payment-intent', async (req, res) => { const { amount, currency, customerEmail } = req.body; try { // Optional: Create or retrieve a Stripe Customer ID to save payment methods const customer = await stripe.customers.create({ email: customerEmail, }); const paymentIntent = await stripe.paymentIntents.create({ amount: amount, // Amount in smallest currency unit (e.g., 2000 cents for $20.00) currency: currency || 'usd', customer: customer.id, automatic_payment_methods: { enabled: true, // Enables card, Apple Pay, Google Pay, etc. dynamically }, }); res.status(200).json({ paymentIntent: paymentIntent.client_secret, ephemeralKey: 'optional_ephemeral_key_if_using_customer_sheet', customer: customer.id, }); } catch (error) { res.status(400).json({ error: error.message }); }});app.listen(3000, () => console.log('Server running on port 3000'));
Implementing Stripe Payment Sheet (The Recommended Way)
Stripe’s Payment Sheet is a pre-built, optimized UI component that presents a native checkout modal to your users. It handles card input validation, 3D Secure authentication (SCA), zip code verification, and native wallets (Apple Pay/Google Pay) automatically, resulting in higher checkout conversion rates.
The Checkout Screen Implementation
Below is the complete implementation of a checkout screen using the usePaymentSheet hook in React Native:
import React, { useState, useEffect } from 'react';import { View, Button, Alert, StyleSheet, Text, ActivityIndicator } from 'react-native';import { usePaymentSheet } from '@stripe/stripe-react-native';const API_URL = 'https://your-backend-api.com'; // Replace with your server URLexport default function CheckoutScreen() { const { initPaymentSheet, presentPaymentSheet, loading } = usePaymentSheet(); const [ready, setReady] = useState(false); const fetchPaymentSheetParams = async () => { const response = await fetch(`${API_URL}/create-payment-intent`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: 4999, // $49.99 represented in cents currency: 'usd', customerEmail: 'buyer@example.com', }), }); const { paymentIntent, customer } = await response.json(); return { paymentIntent, customer }; }; const initializePaymentSheet = async () => { try { const { paymentIntent, customer } = await fetchPaymentSheetParams(); const { error } = await initPaymentSheet({ merchantDisplayName: "Example Corp, Inc.", customerId: customer, paymentIntentClientSecret: paymentIntent, // Set allowsDelayedPaymentMethods to true if you support offline or delayed payment schemes (e.g., SEPA) allowsDelayedPaymentMethods: false, defaultBillingDetails: { name: 'Jane Doe', email: 'buyer@example.com', } }); if (!error) { setReady(true); } else { Alert.alert(`Initialization error: ${error.code}`, error.message); } } catch (e) { Alert.alert('Error', 'Unable to fetch payment details from server.'); console.error(e); } }; const openPaymentSheet = async () => { const { error } = await presentPaymentSheet(); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else { Alert.alert('Success', 'Your order is confirmed!'); setReady(false); // Reset checkout state } }; useEffect(() => { initializePaymentSheet(); }, []); return ( <View style={styles.container}> <Text style={styles.title}>Premium Subscription</Text> <Text style={styles.price}>$49.99</Text> {loading ? ( <ActivityIndicator size="large" color="#0000ff" /> ) : ( <Button onPress={openPaymentSheet} title="Proceed to Payment" disabled={!ready} /> )} </View> );}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 16, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 8, }, price: { fontSize: 20, color: '#888', marginBottom: 24, },});
Designing a Custom Checkout Flow with CardField
If your application design demands a bespoke user interface where payment inputs are embedded directly into your existing view hierarchy rather than a modal sheet, you can utilize the CardField or CardForm components.
The CardField component is a highly optimized input field that securely collects card numbers, expiration dates, and CVCs. It communicates directly with Stripe’s servers to tokenize the data, keeping your application safely outside of PCI-compliance scope.
import React, { useState } from 'react';import { View, Button, StyleSheet, Alert } from 'react-native';import { CardField, useConfirmPayment } from '@stripe/stripe-react-native';export default function CustomCheckoutScreen() { const { confirmPayment, loading } = useConfirmPayment(); const [cardDetails, setCardDetails] = useState(null); const handlePayPress = async () => { if (!cardDetails || !cardDetails.complete) { Alert.alert('Validation Error', 'Please enter valid card details.'); return; } // 1. Fetch client secret from your backend server const response = await fetch('https://your-backend-api.com/create-payment-intent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 1500, currency: 'usd' }), }); const { paymentIntent } = await response.json(); // 2. Confirm the payment on the client side const { error, paymentIntent: confirmedIntent } = await confirmPayment(paymentIntent, { paymentMethodType: 'Card', paymentMethodData: { billingDetails: { email: 'custom-user@example.com', }, }, }); if (error) { Alert.alert('Payment Failed', error.message); } else if (confirmedIntent) { Alert.alert('Payment Successful', `ID: ${confirmedIntent.id}`); } }; return ( <View style={styles.container}> <CardField postalCodeEnabled={true} placeholder={{ number: '1234 5678 1234 5678', }} cardStyle={{ backgroundColor: '#FFFFFF', textColor: '#000000', placeholderColor: '#aab7c4', }} style={styles.cardField} onCardChange={(details) => setCardDetails(details)} /> <Button title={loading ? "Processing..." : "Pay $15.00"} onPress={handlePayPress} disabled={loading} /> </View> );}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', padding: 20, }, cardField: { width: '100%', height: 50, marginVertical: 30, },});
Handling Advanced Payment Scenarios: Apple Pay, Google Pay, and Webhooks
1. Supporting Apple Pay and Google Pay
To enable native digital wallets within the Payment Sheet, you must configure platform-specific credentials:
- Apple Pay: Register a Merchant ID on the Apple Developer Portal, generate a Payment Processing Certificate, and associate it with your Stripe account. Pass the
merchantIdentifierparameter to yourStripeProvider. - Google Pay: Update your
android/app/src/main/AndroidManifest.xmlto enable Google Pay inside the<application>tag:<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" />
2. Securing Transactions with Webhooks
Mobile network connections are inherently unstable. If a user closes your application or loses cellular service immediately after authorizing a payment, your app may never receive the success callback from the client-side SDK. This results in “orphaned transactions” where Stripe captures the customer’s funds, but your backend server never updates the user’s account status.
To solve this, configure a Stripe Webhook. A webhook is an HTTP endpoint on your backend server that Stripe calls directly to notify you of asynchronous events. When a payment succeeds, Stripe sends a payment_intent.succeeded event payload to your webhook. Your server must listen to this event as the absolute source of truth to fulfill orders, provision services, or update databases.
Troubleshooting Common Integration Pitfalls
Mobile payment integrations are prone to configuration mismatches. Use the checklist below to quickly diagnose and resolve common errors:
- Error: “StripeProvider not found”: This occurs when you attempt to call hooks like
usePaymentSheetoruseConfirmPaymentin a component that is not a child of<StripeProvider>. EnsureStripeProvideris positioned at the very top of your application’s component hierarchy. - Error: “No merchant identifier provided” (iOS Apple Pay): Double-check that your Xcode project has the Apple Pay capability enabled and that the merchant identifier matches the one registered in your Apple Developer account and passed to
StripeProvider. - Error: “The PaymentIntent client secret has already been used”: Each
PaymentIntentis designed for a single transaction lifecycle. If a payment fails or is cancelled, you can reuse the intent only under specific state transitions, but creating a freshPaymentIntentfor new checkout attempts is highly recommended. - Android Build Failures (Material Theme): Ensure your application theme inherits from
Theme.MaterialComponents. If you are using a legacy theme, the native Android Stripe SDK will throw runtime compilation errors.
Optimizing the Payment Experience: Performance and Conversion Strategies
Minimizing friction during checkout is the single most effective way to increase conversion rates. Implementing features like saved payment methods, automatic address completion, and local currency display helps drive user trust and higher engagement.
When scaling payment architectures internationally, partnering with experienced systems integrators like XsOne Consultants can prevent costly regulatory and technical missteps. From configuring complex multi-party marketplace payouts (Stripe Connect) to optimizing localized regional payment options, expert consultation ensures your financial infrastructure is built to scale securely.
Frequently Asked Questions (FAQs)
Is Stripe React Native SDK PCI compliant?
Yes. The @stripe/stripe-react-native SDK uses native UI components (such as the Payment Sheet or CardField) that collect payment credentials directly on Stripe’s PCI-compliant servers. Your mobile application only handles tokens, keeping your PCI compliance scope minimal.
Can I test Stripe payments without real money?
Absolutely. Stripe provides a comprehensive suite of test card numbers (e.g., using 4242 4242 4242 4242) to simulate successful payments, card declines, 3D Secure verification flows, and network errors. Ensure you are using your Stripe pk_test_... and sk_test_... keys to run these tests.
How do I handle recurring subscriptions in React Native?
To handle subscriptions, your backend should use the Stripe Billing API to create a Customer and attach a Subscription to them. This returns a PaymentIntent with a client secret representing the first invoice. You pass this client secret to your React Native app’s Payment Sheet to authorize the initial charge and save the payment method for future automated billing cycles.

Editor at XS One Consultants, sharing insights and strategies to help businesses grow and succeed.