Learn how to securely integrate Stripe Elements in your React app, from setup to backend, with step-by-step code for card payments and digital wallets.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Implement Stripe Elements in React
In this tutorial, I'll guide you through implementing Stripe Elements in a React application, allowing you to securely collect and process payment information.
Step 1: Set Up Your React Project
First, make sure you have a React project set up. If not, create one using Create React App:
npx create-react-app stripe-react-app
cd stripe-react-app
Step 2: Install Required Dependencies
Install the Stripe.js and React Stripe.js packages:
npm install @stripe/stripe-js @stripe/react-stripe-js
Step 3: Set Up Stripe in Your React Application
Create a Stripe instance with your publishable key in your main App or entry file:
// App.js
import React from 'react';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import PaymentForm from './PaymentForm';
import './App.css';
// Make sure to replace this with your actual publishable key
const stripePromise = loadStripe('pk_test_your_publishable_key');
function App() {
return (
Stripe Payment Integration
);
}
export default App;
Step 4: Create the Payment Form Component
Create a new file called PaymentForm.js
to implement the actual payment form using Stripe Elements:
// PaymentForm.js
import React, { useState } from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
}
};
const PaymentForm = () => {
const [error, setError] = useState(null);
const [cardComplete, setCardComplete] = useState(false);
const [processing, setProcessing] = useState(false);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const stripe = useStripe();
const elements = useElements();
const handleCardChange = (event) => {
setError(event.error ? event.error.message : '');
setCardComplete(event.complete);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable form submission until Stripe.js has loaded.
return;
}
if (error) {
elements.getElement('card').focus();
return;
}
if (cardComplete) {
setProcessing(true);
}
// Create payment method and confirm payment intent.
// Use fetch to call your backend to create a PaymentIntent and obtain the client secret
try {
// This would typically be a call to your backend to create a PaymentIntent
// const response = await fetch('/create-payment-intent', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// amount: 1000, // $10.00
// currency: 'usd',
// }),
// });
// const data = await response.json();
// const clientSecret = data.clientSecret;
// For demo purposes, let's simulate a successful payment
setProcessing(false);
setPaymentSuccess(true);
// In a real implementation, you would use the client secret from your backend:
// const payload = await stripe.confirmCardPayment(clientSecret, {
// payment\_method: {
// card: elements.getElement(CardElement),
// billing\_details: {
// name: 'Customer Name',
// },
// },
// });
// if (payload.error) {
// setError(`Payment failed: ${payload.error.message}`);
// setProcessing(false);
// } else {
// setPaymentSuccess(true);
// setProcessing(false);
// }
} catch (err) {
setError(`Payment failed: ${err.message}`);
setProcessing(false);
}
};
return (
{paymentSuccess ? (
Payment Successful!
Thank you for your purchase.
) : (
)}
);
};
export default PaymentForm;
Step 5: Add Some Basic Styling for the Payment Form
Create some basic CSS to style your payment form. Add the following to your CSS file:
/_ Add to App.css or create PaymentForm.css _/
.payment-form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.form-row {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 10px;
font-weight: 600;
color: #32325d;
}
.StripeElement {
display: block;
margin: 10px 0 20px 0;
padding: 12px;
border: 1px solid #e6e6e6;
border-radius: 4px;
background-color: white;
box-shadow: 0 1px 3px 0 #e6ebf1;
transition: box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}
.StripeElement--invalid {
border-color: #fa755a;
}
.card-errors {
color: #fa755a;
margin-top: 10px;
font-size: 14px;
}
button {
background-color: #6772e5;
color: #ffffff;
border: 0;
padding: 12px 16px;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: block;
width: 100%;
}
button:hover {
background-color: #5469d4;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.payment-success {
text-align: center;
color: #32325d;
}
.payment-success h2 {
color: #43a047;
}
Step 6: Set Up Your Backend (Server-Side Implementation)
For a complete implementation, you'll need a backend server to create PaymentIntents and handle the payment process securely. Here's a simple Express server example:
// server.js
const express = require('express');
const app = express();
const cors = require('cors');
const stripe = require('stripe')('sk_test_your_secret_key');
app.use(express.json());
app.use(cors());
app.post('/create-payment-intent', async (req, res) => {
try {
const { amount, currency } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
// Verify your integration by passing this parameter
metadata: { integration_check: 'accept_a\_payment' },
});
// Send publishable key and PaymentIntent details to client
res.send({
clientSecret: paymentIntent.client\_secret,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
To use this server, install the required dependencies:
npm install express cors stripe
Step 7: Update the Payment Form to Use the Backend
Modify your PaymentForm.js
to work with your backend:
// Update the handleSubmit function in PaymentForm.js
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
if (error) {
elements.getElement('card').focus();
return;
}
if (cardComplete) {
setProcessing(true);
}
try {
// Call your backend to create the PaymentIntent
const response = await fetch('http://localhost:4000/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 1000, // $10.00 in cents
currency: 'usd',
}),
});
const data = await response.json();
if (response.ok) {
// Confirm the payment with the client secret
const payload = await stripe.confirmCardPayment(data.clientSecret, {
payment\_method: {
card: elements.getElement(CardElement),
billing\_details: {
name: 'Customer Name', // You can collect this from a form field
},
},
});
if (payload.error) {
setError(`Payment failed: ${payload.error.message}`);
setProcessing(false);
} else {
setPaymentSuccess(true);
setProcessing(false);
}
} else {
setError(`Payment failed: ${data.error}`);
setProcessing(false);
}
} catch (err) {
setError(`Payment failed: ${err.message}`);
setProcessing(false);
}
};
Step 8: Using the Different Stripe Element Types
If you want more control over individual fields instead of using the all-in-one CardElement, you can use separate elements:
// AlternativePaymentForm.js
import React, { useState } from 'react';
import {
useStripe,
useElements,
CardNumberElement,
CardExpiryElement,
CardCvcElement
} from '@stripe/react-stripe-js';
const ELEMENT\_OPTIONS = {
style: {
base: {
fontSize: '16px',
color: '#32325d',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
}
};
const AlternativePaymentForm = () => {
const [error, setError] = useState(null);
const [processing, setProcessing] = useState(false);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
setProcessing(true);
try {
// Call your backend to create the PaymentIntent
const response = await fetch('http://localhost:4000/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 1000,
currency: 'usd',
}),
});
const data = await response.json();
if (response.ok) {
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardNumberElement),
});
if (error) {
setError(error.message);
setProcessing(false);
return;
}
const payload = await stripe.confirmCardPayment(data.clientSecret, {
payment\_method: paymentMethod.id,
});
if (payload.error) {
setError(`Payment failed: ${payload.error.message}`);
setProcessing(false);
} else {
setPaymentSuccess(true);
setProcessing(false);
}
} else {
setError(`Payment failed: ${data.error}`);
setProcessing(false);
}
} catch (err) {
setError(`Payment failed: ${err.message}`);
setProcessing(false);
}
};
return (
{paymentSuccess ? (
Payment Successful!
Thank you for your purchase.
) : (
)}
);
};
export default AlternativePaymentForm;
Add these additional styles for the separate fields:
.form-row-inline {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.form-col {
flex: 1;
padding-right: 10px;
}
.form-col:last-child {
padding-right: 0;
}
Step 9: Implementing Payment Request Button (Apple Pay/Google Pay)
For digital wallets like Apple Pay and Google Pay, you can implement the PaymentRequestButton:
// PaymentRequestForm.js
import React, { useState, useEffect } from 'react';
import { PaymentRequestButtonElement, useStripe } from '@stripe/react-stripe-js';
const PaymentRequestForm = () => {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
const [canMakePayment, setCanMakePayment] = useState(false);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Total',
amount: 1000,
},
requestPayerName: true,
requestPayerEmail: true,
});
// Check if the Payment Request is available
pr.canMakePayment().then(result => {
if (result) {
setCanMakePayment(true);
setPaymentRequest(pr);
}
});
pr.on('paymentmethod', async (ev) => {
// Call your backend to create a payment intent
const response = await fetch('http://localhost:4000/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 1000,
currency: 'usd',
}),
});
const data = await response.json();
// Confirm the PaymentIntent with the payment method
const { paymentIntent, error } = await stripe.confirmCardPayment(
data.clientSecret,
{ payment\_method: ev.paymentMethod.id },
{ handleActions: false }
);
if (error) {
// Report to the browser that the payment failed
ev.complete('fail');
} else {
// Report to the browser that the confirmation was successful
ev.complete('success');
if (paymentIntent.status === 'requires\_action') {
// Let Stripe handle the rest of the payment flow
const { error } = await stripe.confirmCardPayment(data.clientSecret);
if (error) {
// The payment failed -- show an error
console.error('Payment failed:', error);
} else {
// The payment succeeded
console.log('Payment succeeded!');
}
} else {
// The payment succeeded
console.log('Payment succeeded!');
}
}
});
}
}, [stripe]);
if (!canMakePayment) {
return Digital wallet payment methods not available on this device/browser.
;
}
return (
Or pay with a card below
);
};
export default PaymentRequestForm;
Step 10: Integrating Both Payment Methods in One Form
Here's how to combine the card form with digital wallet options:
// CompletePaymentForm.js
import React, { useState } from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import PaymentRequestForm from './PaymentRequestForm';
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
}
};
const CompletePaymentForm = () => {
const [error, setError] = useState(null);
const [cardComplete, setCardComplete] = useState(false);
const [processing, setProcessing] = useState(false);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const stripe = useStripe();
const elements = useElements();
const handleCardChange = (event) => {
setError(event.error ? event.error.message : '');
setCardComplete(event.complete);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
if (error) {
elements.getElement('card').focus();
return;
}
if (cardComplete) {
setProcessing(true);
}
try {
const response = await fetch('http://localhost:4000/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 1000,
currency: 'usd',
}),
});
const data = await response.json();
if (response.ok) {
const payload = await stripe.confirmCardPayment(data.clientSecret, {
payment\_method: {
card: elements.getElement(CardElement),
billing\_details: {
name: 'Customer Name',
},
},
});
if (payload.error) {
setError(`Payment failed: ${payload.error.message}`);
setProcessing(false);
} else {
setPaymentSuccess(true);
setProcessing(false);
}
} else {
setError(`Payment failed: ${data.error}`);
setProcessing(false);
}
} catch (err) {
setError(`Payment failed: ${err.message}`);
setProcessing(false);
}
};
return (
{paymentSuccess ? (
Payment Successful!
Thank you for your purchase.
) : (
<>
Or pay with card
>
)}
);
};
export default CompletePaymentForm;
Add separator styling:
.separator {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
}
.separator::before,
.separator::after {
content: '';
flex: 1;
border-bottom: 1px solid #e6e6e6;
}
.separator span {
padding: 0 10px;
color: #6b7c93;
font-size: 14px;
}
.payment-request-button {
margin-bottom: 20px;
}
Step 11: Error Handling and Validation
Add better error handling to your form:
// Add these functions to your payment form components
const validateForm = () => {
let formIsValid = true;
let validationErrors = {};
// Validate any additional fields (name, email, etc.)
if (!formData.name.trim()) {
validationErrors.name = 'Name is required';
formIsValid = false;
}
if (!formData.email.trim()) {
validationErrors.email = 'Email is required';
formIsValid = false;
} else if (!/\S+@\S+.\S+/.test(formData.email)) {
validationErrors.email = 'Email is invalid';
formIsValid = false;
}
// Check if card details are complete
if (!cardComplete) {
validationErrors.card = 'Please complete your card information';
formIsValid = false;
}
setErrors(validationErrors);
return formIsValid;
};
const displayErrorMessage = (message) => {
return (
{message}
);
};
Step 12: Testing Your Implementation
Use Stripe's test cards to verify your implementation works correctly:
// Test card numbers (for testing in development)
//
// Successful payment: 4242 4242 4242 4242
// Requires authentication: 4000 0025 0000 3155
// Payment declined: 4000 0000 0000 9995
//
// Use any future expiration date, any 3-digit CVC, and any postal code
Step 13: Deploying to Production
Before going live:
Conclusion
You've now successfully implemented Stripe Elements in your React application. This implementation provides a secure way to collect payment information, supports various payment methods, and follows best practices for handling sensitive data. Remember to keep your Stripe API keys secure and never process payments directly from the client side without using your server as an intermediary.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.