/stripe-guides

How to implement Stripe Elements in React?

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.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to implement Stripe Elements in React?

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.

) : (
{error}
)}
); }; 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.

) : (
{error}
)}
); }; 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
{error}
)}
); }; 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:

  1. Replace your test Stripe keys with production keys
  2. Set up proper error logging
  3. Implement webhook handling for asynchronous payment events
  4. Set up proper security measures (HTTPS, CSP headers, etc.)
  5. Test thoroughly with real cards in Stripe's test mode

 

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.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022