DEV Community

Cover image for Setting up Stripe Checkout with MERN STACK
Arshan Nawaz
Arshan Nawaz

Posted on

Setting up Stripe Checkout with MERN STACK

In this guide, we'll walk through creating a Stripe checkout session for an online service, using Node.js and MongoDB. We'll cover key steps like calculating prices, creating a Stripe checkout session, and handling webhooks to process orders.

1. Setting Up Stripe Checkout

npm install stripe

Enter fullscreen mode Exit fullscreen mode

Next, initialize Stripe with your secret key:

import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
Enter fullscreen mode Exit fullscreen mode

2. Price Calculation Function:

// Sample price calculation based on cart items
async function calculateTotalPrice(cartItems) {
  let totalPrice = 0;
  for (const item of cartItems) {
    totalPrice += item.price * item.quantity;
  }
  return totalPrice;
}
Enter fullscreen mode Exit fullscreen mode

3. Create Checkout Session Function:

export const createCheckoutSession = async (req, res) => {
  try {
    const { cartItems } = req.body; // Expecting cartItems array in request body

    // Sample cart data
    const sampleCartItems = [
      {
        itemId: "1",
        itemName: "Leather Bag",
        quantity: 1,
        price: 50.00, // in USD
        currency: "USD",
        description: "A stylish leather bag",
        image: "https://example.com/images/leather-bag.jpg"
      },
      {
        itemId: "2",
        itemName: "Cotton Shirt",
        quantity: 2,
        price: 25.00, // in USD
        currency: "USD",
        description: "A soft cotton shirt",
        image: "https://example.com/images/cotton-shirt.jpg"
      }
    ];

    // Calculate total price
    const totalPrice = await calculateTotalPrice(sampleCartItems);

    // Create a Stripe customer for this checkout session
    const customer = await stripe.customers.create({
      metadata: {
        cartItems: JSON.stringify(sampleCartItems), // Storing cart in metadata
      }
    });

    // Prepare line items for Stripe
    const line_items = sampleCartItems.map(item => ({
      price_data: {
        currency: item.currency,
        product_data: {
          name: item.itemName,
          description: item.description,
          images: [item.image],
        },
        unit_amount: Math.round(item.price * 100),  // Convert price to cents
      },
      quantity: item.quantity,
    }));

    // Create Stripe checkout session
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      line_items,
      mode: "payment",
      customer: customer.id,
      success_url: `${req.headers.origin}/checkout-success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${req.headers.origin}/checkout-cancelled`,
    });

    res.send({ url: session.url });
  } catch (err) {
    console.error("Checkout session creation error:", err);
    res.status(500).send({ error: "Failed to create checkout session." });
  }
};
Enter fullscreen mode Exit fullscreen mode

4. Handle Stripe Webhook:

const createOrder = async (customer, data) => {
  const cartItems = JSON.parse(customer.metadata.cartItems);

  const newOrder = new Order({
    customerId: customer.id,
    cartItems: cartItems,  // Store cart items in the order
    payment_status: data.payment_status,
    payment_intent: data.payment_intent,
    totalAmount: data.amount_total / 100,  // Convert from cents to dollars
    status: "PROCESSING",
    createdAt: new Date(),
  });

  try {
    await newOrder.save();

    // Send a notification after order creation
    const notification = new Notification({
      userId: customer.id,
      orderId: newOrder._id,
      message: `Your order with ID ${newOrder._id} was successfully created.`,
    });
    await notification.save();
  } catch (err) {
    console.error("Failed to save order:", err);
  }
};

export const handleStripeWebhook = async (req, res) => {
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
  let event;

  try {
    const signature = req.headers["stripe-signature"];
    event = stripe.webhooks.constructEvent(req.body, signature, webhookSecret);
  } catch (err) {
    console.log(`⚠️  Webhook signature verification failed: ${err.message}`);
    return res.sendStatus(400);
  }

  const data = event.data.object;
  const eventType = event.type;

  if (eventType === "checkout.session.completed") {
    stripe.customers.retrieve(data.customer)
      .then(async (customer) => {
        try {
          await createOrder(customer, data);
        } catch (err) {
          console.error("Error creating order:", err);
        }
      })
      .catch(err => console.error("Error retrieving customer:", err));
  }

  res.status(200).end();
};
Enter fullscreen mode Exit fullscreen mode

5. Handling routes

// Adjust controllers and path 
import express from "express";
import { createCheckoutSession, handleStripeWebhook } from "../controllers/payment/payment.js";
import { isUser } from "../middleware/auth.js";
import bodyParser from "body-parser";

const router = express.Router();

// Stripe Checkout Session route
router.post("/create-checkout-session",  express.json({ type: "application/json" }),isUser, createCheckoutSession);

// Use the raw body parser only for Stripe webhooks
router.post('/webhook', bodyParser.raw({ type: 'application/json' }), handleStripeWebhook);

export default router;
Enter fullscreen mode Exit fullscreen mode
Put this toute above these middleware
app.use("/payment", payment);

//app.use(express.json());
app.use(express.static("public"));
app.use(express.urlencoded({ extended: true }));
app.use(express.json({ limit: "10mb" }));

Enter fullscreen mode Exit fullscreen mode

*6. Frontend *

 const handleStripeCheckout = async () => {
        setIsLoading(true);
        try {
          const response = await api.post(`${config.BASE_URL}/payment/create-checkout-session`, {
            cartItems,
          });

          console.log("Stripe response:", response); // Log the response
          if (response.data.url) {
            window.location.href = response.data.url; // Redirect to Stripe checkout
          } else {
            console.error("No URL returned from Stripe:", response.data);
          }
        } catch (err) {
          console.error("Stripe Checkout Error:", err);
          Swal.fire({
            title: "Error",
            text: "There was an issue processing your payment.",
            icon: "error",
            confirmButtonText: "OK",
          });
        }
        setIsLoading(false);
      };
Enter fullscreen mode Exit fullscreen mode

7. Tailwind Success Page ( optional )

import React from 'react'
import { Link } from 'react-router-dom'

function CheckoutSuccess() {
  return (
    <div className="h-screen flex justify-center items-center bg-gray-100">
      <div className="bg-white p-6 rounded-lg shadow-md text-center max-w-md">
        <svg viewBox="0 0 24 24" className="text-green-600 w-16 h-16 mx-auto my-6">
          <path fill="currentColor"
            d="M12,0A12,12,0,1,0,24,12,12.014,12.014,0,0,0,12,0Zm6.927,8.2-6.845,9.289a1.011,1.011,0,0,1-1.43.188L5.764,13.769a1,1,0,1,1,1.25-1.562l4.076,3.261,6.227-8.451A1,1,0,1,1,18.927,8.2Z">
          </path>
        </svg>
        <h3 className="md:text-2xl text-base text-gray-900 font-semibold">Payment Done!</h3>
        <p className="text-gray-600 my-2">Thank you for completing your secure online payment.</p>
        <p>Have a great day!</p>
        <div className="py-10">
          <Link to="/orders" className="px-12 bg-indigo-600 hover:bg-indigo-500 text-white font-semibold py-3 rounded-lg">
            Go to Orders
          </Link>
        </div>
      </div>
    </div>
  )
}

export default CheckoutSuccess
Enter fullscreen mode Exit fullscreen mode

8. Tailwind Cancel Page ( optional )

import React from 'react'
import { Link } from 'react-router-dom'

function CheckoutCancel() {
  return (
    <div className="h-screen flex justify-center items-center bg-gray-100">
      <div className="bg-white p-6 rounded-lg shadow-md text-center max-w-md">
        <svg viewBox="0 0 24 24" className="text-red-600 w-16 h-16 mx-auto my-6">
          <path fill="currentColor"
            d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm1 14.93V17a1 1 0 1 1-2 0v-1.07a6.84 6.84 0 0 1-4.6-4.6H7a1 1 0 1 1 0-2H6.4A6.84 6.84 0 0 1 11 6.07V5a1 1 0 1 1 2 0v1.07a6.84 6.84 0 0 1 4.6 4.6H17a1 1 0 1 1 0 2h.6A6.84 6.84 0 0 1 13 15.93Z">
          </path>
        </svg>
        <h3 className="md:text-2xl text-base text-gray-900 font-semibold">Payment Cancelled</h3>
        <p className="text-gray-600 my-2">Your payment has been cancelled. You can continue shopping or try again later.</p>
        <div className="py-10">
          <Link to="/" className="px-12 bg-red-600 hover:bg-red-500 text-white font-semibold py-3 rounded-lg">
            Continue Shopping
          </Link>
        </div>
      </div>
    </div>
  )
}

export default CheckoutCancel
Enter fullscreen mode Exit fullscreen mode

9. Conclusion:

  • This backend example demonstrates how to:
  • Create a Stripe checkout session with sample cart items (like a bag and a shirt).
  • Handle price calculations based on cart items.
  • Use Stripe webhooks to handle completed payments and save orders to the database.

Top comments (0)