Setting up Stripe Checkout with MERN STACK

Arshan Nawaz - Sep 28 - - Dev Community

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.
. . . . . . . . . .
Terabox Video Player