How to Integrate Razorpay Payments into Your MERN Project

If you are building a web application that requires online payments—such as course selling, appointment booking, SaaS billing, or e-commerce—Razorpay is one of the most reliable payment gateways available in India.
This article explains how to integrate Razorpay in a MERN application by covering:
-
Backend setup (Node.js + Express)
-
Razorpay order creation
-
Razorpay checkout integration in React
-
Payment verification
-
Webhook setup (optional but recommended)
-
Recommended production setup
-
Clean reusable code
You can use this as a reference guide for any future MERN project requiring payments.
1. Understanding Razorpay Payment Flow
Before writing any code, here is the exact working flow:
React Frontend → Express Backend → Razorpay API
1. User clicks “Pay Now”
2. React calls the backend to create a Razorpay order
3. Backend returns order_id
4. Razorpay pop-up opens → user completes payment
5. Razorpay returns payment_id + order_id + signature to frontend
6. Frontend sends these values back to the backend
7. Backend verifies the payment signature using HMAC SHA256
8 If valid → mark payment as SUCCESS
This is the official and recommended approach.
2. Project Structure
bashbackend/ ├── server.js ├── routes/ │ └── paymentRoutes.js ├── controllers/ │ └── paymentController.js ├── utils/ │ └── razorpay.js ├── .env frontend/ ├── src/ │ └── components/PayButton.jsx ├── public/ └── index.html
3. Install Backend Dependencies
Run the following command:
javascriptnpm install express razorpay cors dotenv crypto
4. Configure Environment Variables (.env)
Create a .env file in your backend folder:
javascriptRAZORPAY_KEY_ID=YOUR_TEST_OR_LIVE_KEY_ID RAZORPAY_KEY_SECRET=YOUR_TEST_OR_LIVE_KEY_SECRET CLIENT_URL=http://localhost:3000 SERVER_URL=http://localhost:5000
5. Create Razorpay Instance (Backend)
backend/utils/razorpay.js
javascriptimport Razorpay from 'razorpay'; import dotenv from 'dotenv'; dotenv.config(); export const razorpay = new Razorpay({ key_id: process.env.RAZORPAY_KEY_ID, key_secret: process.env.RAZORPAY_KEY_SECRET, });
6. Backend Controller: Create Razorpay Order
backend/controllers/paymentController.js
javascriptimport crypto from "crypto"; import { razorpay } from "../utils/razorpay.js"; export const createOrder = async (req, res) => { try { const { amount } = req.body; const options = { amount: Number(amount) * 100, currency: "INR", receipt: "receipt_" + Date.now(), }; const order = await razorpay.orders.create(options); res.json({ success: true, order }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } };
7. Backend Controller: Verify Razorpay Payment Signature
javascriptexport const verifyPayment = async (req, res) => { try { const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = req.body; const body = razorpay_order_id + "|" + razorpay_payment_id; const expectedSignature = crypto .createHmac("sha256", process.env.RAZORPAY_KEY_SECRET) .update(body) .digest("hex"); if (expectedSignature === razorpay_signature) { return res.json({ success: true, message: "Payment Verified" }); } else { return res.status(400).json({ success: false, message: "Invalid Signature" }); } } catch (error) { res.status(500).json({ success: false, message: error.message }); } };
8. Create Express Routes
backend/routes/paymentRoutes.js
javaimport express from 'express'; import { createOrder, verifyPayment } from '../controllers/paymentController.js'; const router = express.Router(); router.post("/create-order", createOrder); router.post("/verify-payment", verifyPayment); export default router;
9. Final Express Setup
backend/server.js
javascriptimport express from "express"; import cors from "cors"; import dotenv from "dotenv"; import paymentRoutes from "./routes/paymentRoutes.js"; dotenv.config(); const app = express(); app.use(express.json()); app.use(cors({ origin: process.env.CLIENT_URL })); app.use("/api/payment", paymentRoutes); app.get("/", (req, res) => { res.send("Razorpay MERN Backend Running"); }); app.listen(5000, () => console.log("Server running on port 5000"));
Backend is now ready.
10. Frontend Setup
Install Axios:
bashnpm install axios
Add Razorpay script loader inside:
frontend/public/index.html
javascript<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
11. Create Payment Button Component
frontend/src/components/PayButton.jsx
javascriptimport axios from "axios"; function PayButton() { const handlePayment = async () => { const { data } = await axios.post( "http://localhost:5000/api/payment/create-order", { amount: 499 } ); const order = data.order; const options = { key: "YOUR_TEST_OR_LIVE_KEY_ID", amount: order.amount, currency: "INR", name: "My Application", description: "Payment for Service", order_id: order.id, handler: async function (response) { const verify = await axios.post( "http://localhost:5000/api/payment/verify-payment", response ); if (verify.data.success) { alert("Payment Successful"); } else { alert("Payment Verification Failed"); } }, theme: { color: "#121212", }, }; const razorpayObj = new window.Razorpay(options); razorpayObj.open(); }; return <button onClick={handlePayment}>Pay Now</button>; } export default PayButton;
12. Testing Razorpay Payments
Use Razorpay’s test mode.
Test card:
bash4111 1111 1111 1111 Expiry: Any future month/year CVV: 123
Payment will always succeed in test mode.
13. Optional: Razorpay Webhook (For 100% Accurate Payments)
If the user closes the tab immediately after paying, verification might fail.
Webhooks ensure payment confirmation is always delivered.
backend/routes/webhookRoutes.js
javascriptimport express from "express"; import crypto from "crypto"; const router = express.Router(); router.post("/razorpay", (req, res) => { const secret = process.env.RAZORPAY_KEY_SECRET; const shasum = crypto.createHmac("sha256", secret); shasum.update(JSON.stringify(req.body)); const digest = shasum.digest("hex"); if (digest === req.headers["x-razorpay-signature"]) { console.log("Webhook Verified"); // database update logic console.log("Event Data:", req.body); } else { return res.status(400).json({ message: "Invalid Signature" }); } res.status(200).json({ status: "ok" }); }); export default router;
Add this in server.js:
javascriptapp.use("/api/webhook", webhookRoutes);
Set webhook URL in Razorpay Dashboard:
bashhttps://your-domain.com/api/webhook/razorpay
Conclusion
Integrating Razorpay into a MERN stack application can significantly enhance your project's payment capabilities, providing a seamless and secure transaction experience for users.
By following this guide, you have learned how to set up the backend and frontend, create and verify Razorpay orders, and implement optional webhooks for reliable payment confirmations.
With this knowledge, you can confidently incorporate Razorpay into any future MERN projects, ensuring a robust and efficient payment system.