Creating Orders and Accepting Payments with Razorpay in Node.js
Learn how to create Razorpay orders, integrate the checkout on the frontend, verify payment signatures, and handle successful and failed payments in your Node.js app.
Creating Orders and Accepting Payments with Razorpay
This guide covers the complete payment flow creating orders, frontend checkout, signature verification, and handling results.
Step 1: Create an Order (Backend)
// src/routes/payment.js const router = require("express").Router(); const razorpay = require("../config/razorpay"); const userAuth = require("../middlewares/auth"); const Payment = require("../models/Payment"); // Create order router.post("/create-order", userAuth, asyncHandler(async (req, res) => { const { amount, plan } = req.body; // amount in rupees const order = await razorpay.orders.create({ amount: amount * 100, // Convert to paise currency: "INR", receipt: `receipt_${Date.now()}`, notes: { userId: req.user._id.toString(), plan: plan } }); // Save order in database await Payment.create({ userId: req.user._id, orderId: order.id, amount: order.amount, currency: order.currency, status: order.status, // "created" plan }); res.json({ orderId: order.id, amount: order.amount, currency: order.currency, keyId: process.env.RAZORPAY_KEY_ID }); }));
Step 2: Frontend Checkout
<!-- Load Razorpay checkout script --> <script src="https://checkout.razorpay.com/v1/checkout.js"></script> <script> async function initiatePayment() { // Create order from backend const response = await fetch("/api/payment/create-order", { method: "POST", headers: { "Content-Type": "application/json", "Cookie": document.cookie }, body: JSON.stringify({ amount: 500, plan: "premium" }) }); const { orderId, amount, currency, keyId } = await response.json(); // Open Razorpay checkout const razorpay = new Razorpay({ key_id: keyId, amount: amount, currency: currency, order_id: orderId, name: "DevTinder Premium", description: "Upgrade to Premium Plan", image: "https://yourdomain.com/logo.png", prefill: { name: "John Doe", email: "[email protected]" }, theme: { color: "#fd267d" }, handler: function(response) { // Payment successful verify on backend verifyPayment(response); }, modal: { ondismiss: function() { console.log("Payment cancelled by user"); } } }); razorpay.open(); } async function verifyPayment(paymentResponse) { const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = paymentResponse; const result = await fetch("/api/payment/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ orderId: razorpay_order_id, paymentId: razorpay_payment_id, signature: razorpay_signature }) }); const data = await result.json(); if (data.success) { alert("Payment successful! You are now a Premium member."); window.location.href = "/success"; } else { alert("Payment verification failed. Please contact support."); } } </script>
Step 3: Verify Payment Signature (Backend)
const crypto = require("crypto"); router.post("/verify", asyncHandler(async (req, res) => { const { orderId, paymentId, signature } = req.body; // Find the order in database const payment = await Payment.findOne({ orderId }); if (!payment) { throw new AppError("Order not found", 404); } // Verify signature const body = orderId + "|" + paymentId; const expectedSignature = crypto .createHmac("sha256", process.env.RAZORPAY_KEY_SECRET) .update(body) .digest("hex"); if (expectedSignature !== signature) { // Invalid signature possible fraud payment.status = "failed"; payment.failureReason = "Signature verification failed"; await payment.save(); throw new AppError("Payment verification failed", 400); } // Payment verified successfully payment.paymentId = paymentId; payment.signature = signature; payment.status = "paid"; await payment.save(); // Update user's plan await User.findByIdAndUpdate(payment.userId, { plan: payment.plan, planActivatedAt: new Date() }); res.json({ success: true, message: "Payment verified successfully" }); }));
Step 4: Handle Webhooks (Server-to-Server)
router.post("/webhook", express.raw({ type: "application/json" }), asyncHandler(async (req, res) => { const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET; const signature = req.headers["x-razorpay-signature"]; // Verify webhook signature const expectedSignature = crypto .createHmac("sha256", webhookSecret) .update(req.body) .digest("hex"); if (expectedSignature !== signature) { return res.status(400).json({ message: "Invalid webhook signature" }); } const event = JSON.parse(req.body.toString()); switch (event.event) { case "payment.captured": await handlePaymentCaptured(event.payload.payment.entity); break; case "payment.failed": await handlePaymentFailed(event.payload.payment.entity); break; case "refund.processed": await handleRefundProcessed(event.payload.refund.entity); break; } res.status(200).json({ status: "ok" }); }) ); const handlePaymentCaptured = async (payment) => { const dbPayment = await Payment.findOne({ orderId: payment.order_id }); if (dbPayment && dbPayment.status !== "paid") { dbPayment.status = "paid"; dbPayment.paymentId = payment.id; dbPayment.method = payment.method; await dbPayment.save(); await User.findByIdAndUpdate(dbPayment.userId, { plan: dbPayment.plan, planActivatedAt: new Date() }); } };
Payment Model
const paymentSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, orderId: { type: String, required: true, unique: true }, paymentId: String, signature: String, amount: { type: Number, required: true }, // in paise currency: { type: String, default: "INR" }, status: { type: String, enum: ["created", "paid", "failed", "refunded"], default: "created" }, method: String, // card, upi, netbanking, wallet plan: String, failureReason: String }, { timestamps: true });
The Takeaway
The Razorpay payment flow involves: creating an order on your backend (razorpay.orders.create), opening checkout on the frontend (Razorpay checkout.js), verifying the payment signature on your backend (HMAC-SHA256), handling webhooks for reliable status updates, and storing all payment details in a Payment model. Always verify the signature to prevent fraud, and use webhooks as the source of truth for payment status.
Use razorpay.orders.create({ amount: amount * 100, currency: 'INR', receipt: 'unique_id', notes: { userId, plan } }). The amount is in paise (₹1 = 100 paise). Save the order in your database with status 'created'. Return the order ID and key ID to the frontend.
Create HMAC-SHA256 of 'orderId|paymentId' using your key_secret. Compare with the signature from the frontend. If they match, the payment is genuine. If not, it's potential fraud mark the payment as failed and don't activate the service.
Webhooks are server-to-server notifications that work even if the customer closes the browser. They handle cases where the frontend verification fails (network issues, page closed). Webhooks are the reliable source of truth for payment status. Always verify webhook signatures too.
Load checkout.razorpay.com/v1/checkout.js, create a new Razorpay instance with key_id, amount, currency, order_id, and a handler function. Call razorpay.open() to show the checkout. The handler receives razorpay_order_id, razorpay_payment_id, and razorpay_signature send these to your backend for verification.
Store userId, orderId (unique), paymentId, signature, amount (in paise), currency, status (created/paid/failed/refunded), method (card/upi/netbanking/wallet), plan, and failureReason. Update status as the payment progresses through the flow.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

