Integrating Stripe Payments into Your SaaS with MERN Stack.

Photo by CardMapr.nl on Unsplash

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.

  1. 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.

  1. 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, and planType 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.