/stripe-guides

How to set up Stripe with React?

Learn how to set up Stripe with React, from installing packages and creating payment forms to handling webhooks, error states, and going live—step-by-step tutorial.

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 set up Stripe with React?

Setting Up Stripe with React: A Comprehensive Tutorial

 

Step 1: Install the necessary packages

 

First, you need to install the required packages for integrating Stripe with your React application.

npm install @stripe/stripe-js @stripe/react-stripe-js

If you're planning to use server-side functionality as well:

npm install stripe

 

Step 2: Set up your Stripe account

 

Before integrating Stripe into your React application, make sure you have:

  • Created a Stripe account at https://stripe.com
  • Located your API keys in the Stripe Dashboard under Developers > API keys
  • Noted both your Publishable Key (for frontend) and Secret Key (for backend)

 

Step 3: Initialize Stripe in your React application

 

Create a wrapper component or modify your main App component to initialize Stripe:

// src/App.js or src/index.js
import React from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from '@stripe/react-stripe-js';
import PaymentForm from './components/PaymentForm';

// Load Stripe with your publishable key
const stripePromise = loadStripe('pk_test_your_publishable_key');

function App() {
  return (
    
); } export default App;

 

Step 4: Create a payment form component

 

Create a new component for your payment form:

// src/components/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 [processing, setProcessing] = useState(false);
  const [succeeded, setSucceeded] = useState(false);
  const stripe = useStripe();
  const elements = useElements();

  const handleChange = (event) => {
    if (event.error) {
      setError(event.error.message);
    } else {
      setError(null);
    }
  };

  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;
    }

    setProcessing(true);

    try {
      // Call your backend to create the payment intent
      const response = await fetch('/create-payment-intent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          amount: 1000, // amount in cents
          currency: 'usd',
        }),
      });
      
      const data = await response.json();
      
      // Confirm the payment with Stripe.js
      const { paymentIntent, error } = await stripe.confirmCardPayment(
        data.clientSecret,
        {
          payment\_method: {
            card: elements.getElement(CardElement),
            billing\_details: {
              name: 'Customer Name', // You should collect this from the user
            },
          },
        }
      );

      if (error) {
        setError(`Payment failed: ${error.message}`);
        setProcessing(false);
      } else if (paymentIntent.status === 'succeeded') {
        setError(null);
        setSucceeded(true);
        setProcessing(false);
        // Handle successful payment here
        console.log('Payment succeeded!');
      }
    } catch (err) {
      setError(`Payment failed: ${err.message}`);
      setProcessing(false);
    }
  };

  return (
    
{error &&
{error}
} {succeeded &&
Payment succeeded!
}
); }; export default PaymentForm;

 

Step 5: Set up the backend for creating Payment Intents

 

Create a server file (e.g., server.js) to handle payment intents:

// server.js
const express = require('express');
const app = express();
const stripe = require('stripe')('sk_test_your_secret_key');

app.use(express.json());

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 in this guide by including this parameter
      metadata: { integration_check: 'accept_a\_payment' },
    });

    res.json({ clientSecret: paymentIntent.client\_secret });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

 

Step 6: Using Stripe Elements for specific payment methods

 

For more specific payment methods, you can use specialized Stripe Elements:

// Example of using specific Stripe Elements
import { CardNumberElement, CardExpiryElement, CardCvcElement } from '@stripe/react-stripe-js';

// In your component's render method:

 

Step 7: Adding styles for your payment form

 

Create a CSS file for styling your payment form:

/_ src/components/PaymentForm.css _/
.form-row {
  margin-bottom: 16px;
}

label {
  display: block;
  margin-bottom: 8px;
  font-weight: 500;
}

.StripeElement {
  height: 40px;
  padding: 10px 12px;
  width: 100%;
  color: #32325d;
  background-color: white;
  border: 1px solid #ced4da;
  border-radius: 4px;
  box-shadow: 0 1px 3px 0 #e6ebf1;
  -webkit-transition: box-shadow 150ms ease;
  transition: box-shadow 150ms ease;
}

.StripeElement--focus {
  box-shadow: 0 1px 3px 0 #cfd7df;
}

.StripeElement--invalid {
  border-color: #fa755a;
}

.card-error {
  color: #fa755a;
  margin-top: 8px;
  font-size: 14px;
}

.payment-success {
  color: #28a745;
  margin-top: 8px;
  font-size: 14px;
}

button {
  background: #5469d4;
  color: #ffffff;
  border-radius: 4px;
  border: 0;
  padding: 12px 16px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  display: block;
  width: 100%;
  transition: all 0.2s ease;
}

button:hover {
  background-color: #4a5fc1;
}

button:disabled {
  opacity: 0.5;
  cursor: default;
}

Import this CSS file in your PaymentForm component:

import './PaymentForm.css';

 

Step 8: Setting up environment variables

 

For better security, use environment variables for your Stripe keys:

  1. Create a .env file in your project root:
// .env.local (for React)
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key

// .env (for Node.js backend)
STRIPE_SECRET_KEY=sk_test_your_secret_key
  1. Update your code to use these variables:
// Frontend
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);

// Backend
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

 

Step 9: Implementing error handling and loading states

 

Enhance your payment form with better error handling and loading states:

// Enhanced version of the handleSubmit function
const handleSubmit = async (event) => {
  event.preventDefault();
  
  if (!stripe || !elements) {
    return;
  }

  setProcessing(true);
  setError(null);

  try {
    // Create payment intent
    const response = await fetch('/create-payment-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        amount: 1000,
        currency: 'usd',
      }),
    });
    
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    
    const data = await response.json();
    
    // Confirm payment
    const { paymentIntent, error: confirmError } = await stripe.confirmCardPayment(
      data.clientSecret,
      {
        payment\_method: {
          card: elements.getElement(CardElement),
          billing\_details: {
            name: 'Customer Name',
          },
        },
      }
    );

    if (confirmError) {
      throw new Error(confirmError.message);
    }
    
    if (paymentIntent.status === 'succeeded') {
      setSucceeded(true);
      // You can also redirect or show a success message
    }
  } catch (err) {
    setError(`Payment failed: ${err.message}`);
  } finally {
    setProcessing(false);
  }
};

 

Step 10: Adding webhook support for payment confirmations

 

For reliable payment processing, set up a webhook endpoint:

// In your server.js
const endpointSecret = 'whsec_your_webhook_signing_secret';

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment\_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log('PaymentIntent was successful!');
      // Update your database, fulfill the order, etc.
      break;
    case 'payment_intent.payment_failed':
      const failedPayment = event.data.object;
      console.log('Payment failed:', failedPayment.last_payment_error?.message);
      // Notify the customer that payment failed
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a 200 response to acknowledge receipt of the event
  res.json({received: true});
});

 

Step 11: Testing your Stripe integration

 

For testing, Stripe provides test card numbers:

  • Card number: 4242 4242 4242 4242
  • Expiration date: Any future date
  • CVC: Any 3 digits
  • ZIP: Any 5 digits

For testing different scenarios:

  • Card number 4000 0000 0000 0002: Will decline the charge
  • Card number 4000 0000 0000 9995: Will require authentication

 

Step 12: Going to production

 

When you're ready to go live:

  • Switch to your live Stripe API keys
  • Ensure you have SSL enabled for your website
  • Thoroughly test the payment flow in your staging environment
  • Set up proper error handling and logging
  • Implement analytics to track conversion rates

Update your environment variables for production:

// .env.production
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_live_your_live_publishable\_key

// Server environment
STRIPE_SECRET_KEY=sk_live_your_live_secret\_key

 

Conclusion

 

You've now set up a complete Stripe integration with your React application, including:

  • Stripe Elements for secure payment collection
  • Payment Intent API for processing payments
  • Webhooks for reliable payment confirmations
  • Error handling and testing capabilities

Remember that Stripe offers many more features like subscription billing, marketplace payments, and recurring payments that you can implement using similar patterns.

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