How I integrated Stripe Payments in React App

Judy@webdecoded
6 min readOct 2, 2023

--

To integrate Stripe into an application, a server-side component is required. The server-side component is responsible for securely handling sensitive information, such as payment details, and communicating with Stripe’s APIs. Since React is a client-side library, it needs a server(unless you’re using its framework like NextJS) to be able to process payments. The flow would be following: Build a form that takes in user information such as payment details and etc, the client-side code(React) sends a request to the server. The server-side code then interacts with Stripe’s APIs to create a payment intent, which includes the necessary information for processing the payment. Because I didn’t need backend functionalities for the rest of my application, and didn’t want to go through the hassle of creating a backend, such as MongoDB, setting up the Express framework, creating APIs, and deploying it, I chose to use a platform that would enable me to deploy serverless functionsAircode. Each function would handle different step of Stripe payment:

  1. Generating payment intent
  2. Monitoring payment status

And the React steps would include :

  1. Capturing user data once user starts filling out the form and sending the request to API(Step 1 in backend) when user submits the form
  2. Confirming the Client success(basically a token returned when payment intent is generated) using stripe package .

Here’s a quick diagram of what’s happening:

Now let’s dive into code ✍️

To be able to successfully run the code you will need Stripe developer account and its public and private keys, which can be obtained by going into the dashboard, creating a new project and navigating to API keys tab:

Stripe Dashboard

First, I added a stripe module in my react project:

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

And created a container component in which I will initialize stripe instance using the publishable key:

import React from 'react'
import { loadStripe } from '@stripe/stripe-js';
import {
Elements,
} from '@stripe/react-stripe-js';
import { CheckoutForm } from '../components/CheckoutForm'

type Props = {}

const options = {
mode: 'payment',
amount: 1099,
currency: 'usd',
// Fully customizable with appearance API.
appearance: {
/*...*/
},
};

const Checkout = (props: Props) => {
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PK);
// import meta.env.VITE_STRIPE_PK is the publishable key you can either directly paste your stripe key here but not recommending if you are planning to upload the code on github as it should remain only available to you or save the key in .env file

return (
<div className='flex container mt-8'>
<Elements stripe={stripePromise} options={options}>
<CheckoutForm />
</Elements>
</div>
)
}

export default Checkout

And my publishable key is saved in .env file like this:

// .env
VITE_STRIPE_PK="your key"

Please note this syntax varies depending on how you created your react app, I personally used Vite, that’s why I prefix my variables with “VITE_” otherwise compiler won’t be able to read it, but if for example you used create-react-app you would need to switch it with “REACT_APP_…” more about it here.

And the checkout form that is used stripe functions inside Elements provider looks like this:

import React, { useState } from 'react';
import {
PaymentElement,
Elements,
useStripe,
useElements,
} from '@stripe/react-stripe-js';

export const CheckoutForm = () => {
const stripe = useStripe();
const elements = useElements();

const [errorMessage, setErrorMessage] = useState('');
const [emailInput, setEmailInput] = useState('');

const backendUrl = import.meta.env.VITE_STRIPE_PK_AIRCODE_URL;

const handleSubmit = async (event) => {
event.preventDefault();

if (elements == null || stripe == null) {
return;
}

// Trigger form validation and wallet collection
const { error: submitError } = await elements.submit();
if (submitError?.message) {
// Show error to your customer
setErrorMessage(submitError.message);
return;
}

const price = 12;

// Create the PaymentIntent and obtain clientSecret from your server endpoint
const res = await fetch(backendUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
currency: 'usd',
email: emailInput,
amount: price * 100,
paymentMethodType: "card"
}),
});

const { client_secret: clientSecret } = await res.json();

const { error } = await stripe.confirmPayment({
//`Elements` instance that was used to create the Payment Element
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/success`,
},
});

if (error) {
// This point will only be reached if there is an immediate error when
// confirming the payment. Show error to your customer (for example, payment
// details incomplete)
setErrorMessage(error.message);
} else {
// Your customer will be redirected to your `return_url`. For some payment
// methods like iDEAL, your customer will be redirected to an intermediate
// site first to authorize the payment, then redirected to the `return_url`.
}
};

return (
<form onSubmit={handleSubmit} className='px-4'>
<div className='mb-3'>
<label htmlFor="email-input">Email</label>
<div>
<input value={emailInput} onChange={(e => setEmailInput(e.target.value))} type="email" id="email-input" placeholder='johndoe@gmail.com' />
</div>
</div>
<PaymentElement />
<button type="submit" disabled={!stripe || !elements}>
Pay
</button>
{/* Show error message to your customers */}
{errorMessage && <div>{errorMessage}</div>}
</form>
);
};

Now only thing we are missing is Aircode URL, which will serve as an access point to our backend, head to Aircode to create a new account and + New NodeJS App with these settings:

New NodeJs App

On the left menu of the project select Environments and Add a new key value pair, where we have STRIPE_SECRET_KEY and as a value paste the Secret Key from the Stripe Dashboard:

Each file in our project setup will serve as a function and we can either rename the started file provided by AirCode or create a new file and name it — payment.ts and paste the following code in there:

// @see https://docs.aircode.io/guide/functions/
import aircode from "aircode";
import Stripe from "stripe";

type Params = {
email: string;
amount: number;
currency: string;
paymentMethodType: string;
};

export default async function (params: Params, context: any) {
console.log("Received params:", params);
const { email, amount, currency, paymentMethodType } = params;

if (process.env.STRIPE_SECRET_KEY) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2023-08-16",
typescript: true,
});

try {
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
});

const payment_id = paymentIntent.id;

context.status(200);
return {
client_secret: paymentIntent.client_secret,
};
} catch (e: any) {
context.status(500);
console.log(e);
return {
message: e.message,
};
}
}

return {
message: "Please add stripe api key",
};
}

Now you might see a red line under the code `import Stripe from “stripe”;` that’s because we also have to install stripe node js module and we’ll do that on the left side by going to Dependencies and selecting stripe from options:

AirCode Project Dependencies

Once the function is ready at the top of the toolbar you will press Deploy that will generate a url that you can copy and paste it into your React App’s .env file:

// .env
VITE_STRIPE_PK="your key"
VITE_STRIPE_PK_AIRCODE_URL="your aircode url"

This value is used by our CheckoutForm to make a request to API, get the client success and than on the client side we call stripe.confirmPayment to confirm the payment and finally we pass it redirect url where in case of success we want stripe module to redirect our users to.

This is the full github url: https://github.com/judygab/digital-product-tutorial/

And for deeper dive you can also watch the video tutorial of the project I integrated payments in:

--

--