Problem Overview:
I’m facing an issue where Stripe.js fails to load in the Android WebView in my React Native app, preventing the payment flow from functioning. The integration works perfectly on iOS devices, but on Android, the script doesn’t load.
Setup:
I’m trying to run the following HTML inside a WebView to set up a Stripe payment method:
<!DOCTYPE html>
<html>
<head>
<title>Stripe Setup Intent</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://js.stripe.com/v3/"></script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.container {
width: 100%;
max-width: 500px;
padding: 20px;
background: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
text-align: center;
overflow-y: auto;
}
h2 { margin-bottom: 20px; }
p { margin-bottom: 40px; }
.spinner {
display: inline-block;
width: 50px;
height: 50px;
border: 3px solid rgba(195, 195, 195, 0.6);
border-radius: 50%;
border-top-color: #636767;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container" role="alert">
<h2>Let's setup your payment method</h2>
<p id="status-message">Connecting to Stripe...</p>
<div class="spinner" aria-hidden="true"></div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const stripe = Stripe('<%= stripePublishableKey %>');
const clientSecret = '<%= clientSecret %>';
stripe.confirmAcssDebitSetup(clientSecret, {
payment_method: {
billing_details: {
name: '<%= user.fullName %>',
email: '<%= user.email %>',
},
},
}).then(function(result) {
if (result.error) {
document.querySelector('.container').innerHTML = `
<p>There was an error setting up your payment method. Please try again.</p>
<p>If the problem persists, please visit the "Contact Us" screen in the app or email us at <a href="mailto:[email protected]">[email protected]</a>.</p>
`;
} else {
document.querySelector('.container').innerHTML = `
<p>Your payment method has been successfully set up!</p>
<p>You will be redirected shortly.</p>`;
}
});
});
window.addEventListener('resize', function() {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
document.activeElement.scrollIntoView({ behavior: 'smooth' });
}
});
</script>
</body>
</html>
The webview in React Native (work perfectly fine on iOS):
import { View } from 'react-native';
import React from 'react';
import StripeTos from '@Components/StripeTos';
import BackIcon from '@Components/BackIcon';
import { WebView } from 'react-native-webview';
import { styles } from './styles';
import { height, width } from '@Utils/dimensions';
interface Props {
isVisible: boolean;
close: () => void;
htmlStr: string;
}
const StripeWebview = ({ isVisible = false, close, htmlStr = '' }: Props) => {
return (
<View>
<StripeTos
close={close}
onSubmit={() => {}}
open={isVisible}
isWebView
isSwipable={false}
avoidKeyboard={false}
containerStyle={{
width: width(100),
height: height(90),
borderWidth: 0,
backgroundColor: 'white',
}}>
<View style={styles.container}>
<View style={styles.backBtn}>
<BackIcon onPress={close} />
</View>
{isVisible && (
<WebView
source={{ html: htmlStr }}
style={{
flex: 1,
}}
scalesPageToFit={true}
javaScriptEnabled={true}
domStorageEnabled={true}
mixedContentMode="always"
onMessage={event => {
console.log(event.nativeEvent.data);
}}
onError={syntheticEvent => {
const { nativeEvent } = syntheticEvent;
console.warn('WebView error: ', nativeEvent);
}}
onHttpError={syntheticEvent => {
const { nativeEvent } = syntheticEvent;
console.warn('WebView HTTP error: ', nativeEvent);
}}
/>
)}
</View>
</StripeTos>
</View>
);
};
export default StripeWebview;
The Problem:
The Stripe.js script fails to load specifically in the Android WebView. Instead, I am just seeing the "Connecting to Stripe…" and spinner, basically the following part (even after waiting for an hour):
<div class="container" role="alert">
<h2>Let's setup your payment method</h2>
<p id="status-message">Connecting to Stripe...</p>
<div class="spinner" aria-hidden="true"></div>
</div>
The same setup works perfectly fine on iOS devices using WebView.
I noticed that it is the stripe.js script loading part where it is stuck, so I tested a couple of other scripts to see if it can load them. It seems like it can load the jQuery script fine in the Android WebView, but Lodash also doesn’t seem to load.
What I Have Tried:
User-Agent Testing: I’ve tried changing the userAgent to emulate an iOS Safari browser and other modern browsers, but the issue persists.
External Script Testing:
jQuery loads without issues in the Android WebView.
Lodash and Stripe.js fail to load, indicating that it might be related to how specific scripts are handled in the WebView.
WebView Configuration: I’ve ensured that WebView settings are correctly configured, including:
javaScriptEnabled: true
domStorageEnabled: true
mixedContentMode: "always"
Promises and TLS 1.2: Both are required by Stripe.js and are supported by the device.
Edit: After trying the debugging method suggested by @Pompey, take a look at the error in the alert:
Stripe.js Initialization Issue
2
Answers
I found a solution.
Previously, I was creating the WebView with source={{ html: htmlStr }}.
It seems if instead of getting the HTML from my server and then passing it here, I pass the URI to the WebView to get the HTML, it works. I am not sure why, but it works.
So basically source={{ uri: ..., headers: ...(authorization) }}.
IMO this shouldn't really make a difference, since it's the same HTML. But for some reason if it's the HTML directly it doesn't work in Android.
I tried to reproduce the issue on React Native WebView by loading a different Stripe.js page and it works fine for me: https://4242.io/payment-element
Most likely the issue is within your HTML code. If the issue persists, I would suggest you reach out to Stripe support about that along with an example app that they can use to reproduce the issue – https://support.stripe.com/?contact=true