Working in the software development industry as a top web development company for 10 years now, we have faced a number of challenges while integrating various payment gateways in ReactJS applications. We have always found it difficult to get a complete guide on a smooth integration, things to consider, and prerequisites. Considering that our developer community might be facing the same challenge, we wanted to share an end-to-end guide for integrating various payment gateways.
This blog series is our attempt to make things available to developers all at one place. This series of blogs will include comprehensive guides on integrating payment gateways starting with Stripe.
Stripe itself has various methods and models which can be integrated in the applications wiz,
-
Direct checkout
-
Subscription
We will have a separate blog of implementing each method so that our fellow ReactJS developer community can get everything at one place for this matter. In this blog, we will begin with how to implement direct checkout method of Stripe integration to ReactJS applications.
Prerequisites
Before getting started with stripe, you would need to register as a merchant and create a main account, using this account you would get two keys: the publishable key starts with pk and secret key that starts with sk. Once the stripe account is created, you will get access to stripe’s dashboard.
Next step is decide what you want the payment gateway to do, i.e. go for one time payment that is called checkout, or recurring payments called subscription or creating a marketplace called connect.
Once decided, you need to finish off the verification process, including providing business information and linking a bank account for payouts.
For testing purposes you can use Test Mode provided by stripe so that no actual transactions and amount would be deducted.
Implementation guide
Here will show how to create a checkout using stripe. We would need a server and a client (can be any frontend language). You can use any language/framework for server and client, code here would be in nodejs and react js.
**
Step 1: Initialise Stripe in server
Create a node server, and use this npm
npm install --save stripe
Server.js
const express = require("express");
const app = express();
// This is your test secret API key.
const stripe = require("stripe")('YOUR_SECRET_API_KEY');
app.use(express.static("public"));
app.use(express.json());
Step 2 : Create a PaymentIntent
Add an endpoint on your server that creates a PaymentIntent. A PaymentIntent tracks the customer’s payment lifecycle, keeping track of any failed payment attempts and ensuring the customer is only charged once.
app.post("/create-payment-intent", async (req, res) => {
const { totalOrderAmount } = req.body;
// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: totalOrderAmount,
currency: "gbp",
// In the latest version of the API, specifying the 'automatic_payment_methods' parameter is
optional because Stripe enables its functionality by default.
automatic_payment_methods: {
enabled: true,
},
});
res.send({
clientSecret: paymentIntent.client_secret,
});
});
app.listen(4242, () => console.log("Node server listening on port 4242!"));
Step 3 : Adding stripe to client
Install npm using
npm install --save @stripe/react-stripe-js @stripe/stripe-js
Step 4 : Load stripe in client
Call loadStripe() with your Stripe publishable API key to configure the Stripe library.
import React, { useState, useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
import "./App.css";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe("YOUR_PUBLISH_KEY_STARTING_WITHPK");
export default function App() {
const [clientSecret, setClientSecret] = useState("");
Step 5 : Fetch a PaymentIntent
Call the api that we created in step 2, that would give us the payment intent.
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify ({ totalOrderAmount : 100 }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
Step 6: Initialize Stripe Elements
Initialise the stripe elements for react js.
`
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
`
Step 7 : The checkout form component
After the payment intent is created, the confirm payment intent is called on click of Pay button.
Here is the code which would do the same. This either returns a redirection URL or would give some error. The below also handles the errors given by stripe.
`
import React, { useEffect, useState } from "react";
import {
PaymentElement,
useStripe,
useElements
} from "@stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, 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'.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
const paymentElementOptions = {
layout: "tabs"
}
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" options={paymentElementOptions} />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
`
Step 8 : Handle post-payment events:
During this whole process various events like payment_intent_created, payment_intent_processing get triggered to keep a track of all such events and do the manipulations if required you can listen to webhooks which are available in stripe dashboard. This would be helpful even to know and track back each and every transaction.
Things to take care while integrating
Even though the documentation for stripe is straightforward there are quite a few things that need to be taken care of while integrating stripe.
-
Publishable keys should always be used at client’s end and secret keys should always be used at server end.
-
The publishable and secret keys should be complimenting each other. Key mismatch happens very easily.
-
Store your API keys securely using environment variables. Never expose secret keys in client-side code.
-
Stripe provides test cards and scenarios to simulate transactions for each use case, so test all the scenarios before going live.
-
Enable multiple payment methods (cards, bank transfers, digital wallets) to offer flexibility to your customers.
-
Be aware of Stripe’s API rate limits and implement retry logic where necessary to handle throttling gracefully.
Possible errors and troubleshooting them
- Mismatched api keys :** Many times it happens while going live the secret key for stripe is made live, but the publishable key is for test account itself. In such case stripe would always show error on initialisation.
Double check the keys, for secret and publishable keys if they are from test mode they are like pk_test_XXXXXXX and sk_test_XXXXX for live mode they are pk_live_XXXX sk_live_XXXXX.
- **Payment Intents mismatch **: Payment Intent created for test mode will always give error in live mode.
While switching from test mode to live mode, make sure to always create a new payment intent while testing in live mode.
- **Webhook Endpoint Errors :** Many times the webhook is not configured properly which would result in 500 or 404.
Debug the webhook endpoints and events required to be listened, also stripe provided a process where webhooks can be configured locally as well.
- **Timeout and Network Errors :** Stripe provides proper errors like timeout or network errors like 'Connection timeout' or 'Network Errors' to be handled.
Implement retry logic with exponential backoff to handle transient network issues.
Conclusion
After a careful analysis, we decided to start this series to assist our fellow developer community in the best way we can. Stay tuned for more in the series where we will cover the Stripe integration in ReactJS as a next blog. Ask your website development company today to follow this blog to get more information on the integrations with ReactJS.