How to Fix Stripe SDK Crashes in React Native (Android & iOS Complete Guide)
Learn how to fix app crashes caused by @stripe/stripe-react-native on Android and iOS. Complete step-by-step practical guide with native fixes, code examples, and theme solutions.
How to Fix Stripe SDK Crashes in React Native (Android & iOS Complete Guide)
There is almost nothing worse in mobile app development than having your checkout flow crash right in front of a paying customer. You have spent weeks perfecting your UI, integrating backend APIs, and setting up products, only for the app to instantly close the moment a user taps "Pay Now".
If you are using @stripe/stripe-react-native, app crashes on iOS and Android are rarely caused by basic JavaScript bugs. Instead, they almost always stem from native library mismatches, missing Android Material themes, outdated CocoaPods, or unhandled asynchronous promise rejections.
In this practical, code-first tutorial, we will break down why @stripe/stripe-react-native crashes on both Android and iOS, and walk through the exact steps and native code fixes required to make your payment flow crash-proof.
Why @stripe/stripe-react-native Crashes in Production
Because React Native relies on native bridges to talk to native iOS (Swift/Objective-C) and Android (Kotlin/Java) SDKs, any mismatch at the native level causes a fatal runtime crash.
The top reasons for Stripe SDK crashes in React Native include:
- Missing Android Material Theme: Stripeβs Android UI components require a MaterialComponents theme parent.
- Outdated CocoaPods & Podfile Mismatches (iOS): Incompatible deployment targets or cached build artifacts.
- Unchecked Client Secrets: Initialising initPaymentSheet() with an undefined or null secret from an unhandled backend error.
- React Native New Architecture & Gradle Incompatibilities: Version mismatches between Kotlin, Gradle, and @stripe/stripe-react-native.
1. Fixing Android Crashes: Theme.MaterialComponents Requirement
The Bug
On Android devices or emulators, tapping the payment button triggers an immediate crash, outputting the following native log in Logcat:
Plaintext
java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.MaterialComponents (or a descendant).
The Practical Solution
Stripeβs Android Native SDK uses Google Material components for its PaymentSheet UI. If your React Native app uses a basic AppCompat or default system theme, Android throws an IllegalArgumentException.
Step 1: Update styles.xml
Navigate to android/app/src/main/res/values/styles.xml in your React Native project and change your AppTheme parent:
XML
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your primary colors here -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
Step 2: Ensure Modern Gradle & Kotlin Versions
Verify your top-level android/build.gradle file matches the required Kotlin and Gradle configurations for modern Stripe releases:
Groovy
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 21
compileSdkVersion = 34
targetSdkVersion = 34
kotlinVersion = "1.9.22"
}
dependencies {
classpath("com.android.tools.build:gradle:8.2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
}
2. Fixing iOS Crashes: CocoaPods & Deployment Target Mismatches
The Bug
On iOS, the app either fails during npx react-native run-ios / Xcode archive, or crashes instantly upon opening the PaymentSheet with Dyld Error: Symbol not found or Module 'Stripe' not found.
The Practical Solution
Recent releases of @stripe/stripe-react-native require a minimum deployment target of iOS 13.0 (or higher).
Step 1: Update your ios/Podfile
Open ios/Podfile and explicitly enforce the deployment target across all target Pods:
Ruby
platform :ios, '13.0'
target 'YourProjectName' do
config = use_native_modules!
# Enable static framework linkage if using modern RN versions
use_frameworks! :linkage => :static
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => flags[:hermes_enabled],
:fabric_enabled => flags[:fabric_enabled],
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
# Enforce iOS 13.0 for all CocoaPods sub-targets
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
end
end
end
end
Step 2: Clean and Re-install Pods
Run this sequence in your terminal to clear out stale CocoaPod builds:
Bash
cd ios
rm -rf Pods Podfile.lock
pod cache clean --all
pod install --repo-update
cd ..
3. Production-Ready React Native Code Implementation
Many crashes occur when developers attempt to initialize the payment sheet before validating backend responses. If your API endpoint throws a 500 error and returns undefined for paymentIntentClientSecret, calling initPaymentSheet will cause an uncaught native exception.
Here is a robust, crash-proof React Native implementation using @stripe/stripe-react-native.
Step 1: Wrap Your Root Component with <StripeProvider>
In your main App.tsx or Root Navigator:
TypeScript
import React from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';
import CheckoutScreen from './src/screens/CheckoutScreen';
const STRIPE_PUBLISHABLE_KEY = 'pk_test_51Mz...YourKeyHere';
export default function App() {
return (
<StripeProvider
publishableKey={STRIPE_PUBLISHABLE_KEY}
merchantIdentifier="merchant.com.yourcompany.app" // Required for Apple Pay
>
<CheckoutScreen />
</StripeProvider>
);
}
Step 2: Safe CheckoutScreen.tsx with Full Error Catching
TypeScript
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Alert } from 'react-native';
import { usePaymentSheet } from '@stripe/stripe-react-native';
export default function CheckoutScreen() {
const { initPaymentSheet, presentPaymentSheet } = usePaymentSheet();
const [loading, setLoading] = useState(false);
// Safely fetch payment parameters from your server
const fetchPaymentParams = async () => {
try {
const response = await fetch('https://your-api-domain.com/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 2500, currency: 'usd' }),
});
if (!response.ok) {
throw new Error(`Server Error Code: ${response.status}`);
}
const data = await response.json();
if (!data.paymentIntent) {
throw new Error('Invalid server response: Missing paymentIntent client secret');
}
return {
paymentIntent: data.paymentIntent,
ephemeralKey: data.ephemeralKey,
customer: data.customer,
};
} catch (error: any) {
console.error('[Stripe Fetch Error]:', error);
Alert.alert('Connection Error', error.message || 'Failed to reach payment server.');
return null;
}
};
// Initialize Payment Sheet with guard checks
const initializePaymentSheet = async () => {
setLoading(true);
const params = await fetchPaymentParams();
if (!params) {
setLoading(false);
return; // Stop execution safely if server call failed
}
const { error } = await initPaymentSheet({
merchantDisplayName: 'My Mobile Store',
customerId: params.customer,
customerEphemeralKeySecret: params.ephemeralKey,
paymentIntentClientSecret: params.paymentIntent,
allowsDelayedPaymentMethods: false,
defaultBillingDetails: {
name: 'John Doe',
},
});
if (error) {
console.error('[Stripe Init Error]:', error.code, error.message);
Alert.alert('Payment Setup Failed', error.message);
}
setLoading(false);
};
useEffect(() => {
initializePaymentSheet();
}, []);
// Open the Stripe Payment Sheet
const handlePayPress = async () => {
const { error } = await presentPaymentSheet();
if (error) {
if (error.code === 'Canceled') {
console.log('User closed payment sheet');
} else {
Alert.alert(`Payment Failed (${error.code})`, error.message);
}
} else {
Alert.alert('Success', 'Your payment was confirmed!');
}
};
return (
<View style={styles.container}>
{loading ? (
<ActivityIndicator size="large" color="#635BFF" />
) : (
<TouchableOpacity style={styles.payButton} onPress={handlePayPress}>
<Text style={styles.buttonText}>Pay $25.00</Text>
</TouchableOpacity>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#F8F9FA',
},
payButton: {
backgroundColor: '#635BFF',
paddingVertical: 14,
paddingHorizontal: 32,
borderRadius: 8,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 3,
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
});
4. Backend Validation (Node.js Example)
To prevent sending malformed payloads to React Native, ensure your backend returns properly structured JSON for PaymentIntents:
JavaScript
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) => {
try {
const { amount, currency } = req.body;
const customer = await stripe.customers.create();
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customer.id },
{ apiVersion: '2023-10-16' }
);
const paymentIntent = await stripe.paymentIntents.create({
amount: amount || 1000,
currency: currency || 'usd',
customer: customer.id,
automatic_payment_methods: { enabled: true },
});
res.status(200).json({
paymentIntent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customer.id,
});
} catch (error) {
console.error('Stripe API Error:', error.message);
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Payment server running on port 3000'));
Summary Checklist to Prevent Future React Native Stripe Crashes
Before publishing your next React Native update to Google Play or the Apple App Store, run through this checklist:
- Android Theme Check: Ensure android/app/src/main/res/values/styles.xml extends Theme.MaterialComponents.
- iOS Pods Check: Ensure IPHONEOS_DEPLOYMENT_TARGET in your Podfile is set to 13.0 or higher.
- Null-Check Client Secrets: Never pass undefined to initPaymentSheet(). Always guard your state variables.
- Test on Physical Devices: Always test Apple Pay / Google Pay flows on physical iOS and Android hardware rather than emulators.
- Keep Package Updated: Frequently check for updates with npm outline @stripe/stripe-react-native and read official release notes on the Official Stripe React Native Repository.
Relatated Link: