Integrating Stripe Payments into Your SaaS with MERN Stack.
Hey, in this post I am going to explain how you can integrate Stripe payments into your SaaS and update your database for payments using webhooks. I will use MongoDB as an example, but this method can be applied to any other database of your choice. I use React, Express, and Node.js for the tech stack.
Prerequisites
Have a basic understanding of the MERN stack.
Create separate folders for frontend and backend code.
Have a pricing page of your choice, or set up the code as needed. If not you can download the page from Link
Install Stripe on both the Frontend and Backend.
Let's have a look at how the layout looks.
I have created a dummy pricing page with a toggle button that offers two options: monthly or annually. There are also two cards: free and premium. For this tutorial, we will only be working with the premium card.
Once you click to Try Free for 14 days it will navigate you to stripe checkout options where you can simply provide your details and it will update your database accordingly.
Configure Stripe and create products.
Go to Stripe and create an account.
Search for "Products" and create two products: Monthly and Annually.
Copy their price IDs from the product profiles and store them in your
.env
file or another secure location.
Now, go to the Developer section in Stripe and copy the Publishable Key and Secret Key. Store them in a safe place, preferably in your .env
file. You'll need these to interact with your Stripe account programmatically.
Frontend: Implement Stripe Frontend Integration and Subscription Management
Install Stripe using the following command:
npm install --save @stripe/react-stripe-js @stripe/stripe-js
Write the code for a Premium Card component and attach a function, e.g.,
handleOnPricing
to Premium card div.Choose the subscription type based on a toggle button and store it using
useState
hooks.Create
stripeProfile
and load the Publishable Key from the.env
file to connect with the correct Stripe user for payment processing.Create an object that stores
userId
,email
, andplanType
to pass to the backend.Create three functions:
To store the current Stripe profile in the database with the customer ID.
To retrieve the current user session for utilizing the email address and pricing information during checkout.
A prebuilt Stripe redirect-to-checkout function.
Code eg. below
import React, { useEffect, useState } from 'react';
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY);
const Pricing = () => {
const { adminUser } = useSelector((state) => state.admin);
const [loading, setLoading] = useState(false);
const [toggleOn, setToggleOn] = useState(true);
const handelOnToggle = (e) => {
setToggleOn(!toggleOn);
};
const handelOnPricing = async () => {
setLoading(true);
if (adminUser && adminUser?._id) {
const subscriptionType = toggleOn ? 'yearly' : 'monthly';
const obj = {
userId: adminUser?._id,
email: adminUser?.email,
plan: subscriptionType,
};
// 1. Create a Stripe customer
const { customerId } = await createStripeCustomer(obj);
// 2. Create a Stripe Checkout Session
const { sessionId } = await stripePayment({ ...obj, customerId });
// 3. Redirect the user to the Stripe Checkout page
const stripe = await stripePromise;
await stripe.redirectToCheckout({ sessionId });
} else {
navigate('/signup');
}
};
I created a separate component to handle API calls to make clean code.
import axios from 'axios'
const rootUrl = process.env.REACT_APP_API_ENDPOINT;
const stripeEP = rootUrl + '/stripe'
const apiProcessor = async ({ method, url, data}) => {
try {
const response = await axios({
method,
url,
data,
})
return response.data
} catch (error) {
let message = error.message
console.log(message)
if (error.response) {
return {
status: 'error',
message
}
}
}
}
// creting stripe customer payment
export const createStripeCustomer = (data) => {
const option = {
method: 'post',
url: stripeEp + '/create-customer',
isPrivate: true,
data
}
return apiProcessor(option)
}
// stripe payment
export const stripePayment = (data) => {
const option = {
method: 'post',
url: stripeEp + '/create-checkout-session',
isPrivate: true,
data
}
return apiProcessor(option)
}
Backend: Handle API Requests and Payment Processing for Stripe Integration
Install Stripe using the following command:
npm install --save @stripe/react-stripe-js @stripe/stripe-js
Create
stripeProfile
and load the Secret Key from the.env
file to connect with the correct Stripe user for payment processing.
Create two endpoints
Create a customer in the database based on the Stripe response to keep a record for future reference.
Create an endpoint for checkout sessions to process payments programmatically based on a monthly or yearly subscription.
Code eg. below.
import express from 'express'
const router = express.Router();
import Stripe from 'stripe';
import dotenv from 'dotenv'
import { updateOneAdminUser } from '../../MongoModles/User/AdminUserModal.js';
dotenv.config();
const stripe = new Stripe(process.env.STRIPE_SECRET);
//Create stripe customer
router.post('/create-customer', async (req, res, next) => {
try {
const { email, userId } = req.body;
const customer = await stripe.customers.create({
email: email,
});
// Update the user's document with the Stripe customer ID
await updateOneAdminUser({ _id: userId }, { 'subscription.stripeCustomerId': customer.id });
res.status(200).json({ customerId: customer.id });
} catch (error) {
next(error);
}
});
//Stripe checkout session
router.post('/create-checkout-session', async (req, res, next) => {
try {
const { plan, customerId, } = req.body;
const priceId = plan === 'monthly' ? process.env.STRIPE_MONTHLY_PLAN_ID : process.env.STRIPE_YEARLY_PLAN_ID;
const session = await stripe.checkout.sessions.create({
customer: customerId,
payment_method_types: ['card'],
mode: 'subscription',
line_items: [
{
price: priceId,
quantity: 1,
},
],
success_url: `${process.env.SERVER_URL}/home`,
cancel_url: `${process.env.SERVER_URL}/pricing`,
});
res.status(200).json({ sessionId: session.id });
} catch (error) {
next(error);
}
});
export default router;
Since this blog is already quite detailed, I will cover how to use webhooks to update the database based on payment events such as success, cancellation, or updates in a separate section.
Subscribe to my newsletter
Read articles from Shiv Bartaula directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Shiv Bartaula
Shiv Bartaula
I am a Full Stack Developer with over two years of experience building scalable web applications that deliver business value. I have a solid background in both frontend and backend development, working with modern technologies like Node.js, React, Next.js, TypeScript, and AWS to create responsive, high-performance solutions.