Payment Gateway Integration (Razorpay)
Episode 16 - Payment Gateway Integration (Razorpay)
Hey everyone! Welcome back to the Node.js tutorial series. Today we're going to learn something SUPER exciting - integrating a Payment Gateway into our application!
We'll be using Razorpay, one of the most popular payment gateways in India. Let's make some money! (Just kidding... let's learn how to accept it!)
What we will cover:
- Payment Gateway Workflow
- Steps to Integrate Razorpay
- Creating Razorpay Account
- Backend API for Orders
- Frontend Modal Integration
- Webhooks for Payment Verification
- Updating User Premium Status
- Interview Questions
Understanding the Payment Workflow
Before we start coding, let's understand how payment gateways work!
Important Concept: ================== There is NO DIRECT connection between Frontend and Razorpay! Frontend ←→ Backend ←→ Razorpay All communication with Razorpay happens through YOUR backend!
Complete Payment Flow
Payment Flow Diagram:
=====================
FRONTEND BACKEND RAZORPAY
======== ======= ========
1. [Pay Now]
Button Click
│
▼
2. ─────────────────→ Create Order API
│
▼
3. ─────────────────────────→ Generate Order
│
▼
4. ←───────────────────────── Order ID
│
▼
5. ←───────────────── Return Order ID
│
▼
6. Display Payment
Modal (Card/UPI)
│
▼
7. User Completes
Payment
│
▼
8. ─────────────────→
Payment Processed
│
▼
9. ←───────────────────────── Webhook Call
(signature, paymentId, (Auto-notify)
orderId, status)
│
▼
10. Update DB
(payment status,
user premium)
│
▼
11. ←───────────────── Verify Payment API
│
▼
12. Update UI
(Show Premium)
Key Points About the Flow
Two API Calls: ============== 1. CREATE ORDER API - Frontend → Backend → Razorpay - Returns Order ID to frontend - Used to display payment modal 2. VERIFY PAYMENT API - Frontend → Backend - Checks if payment was successful - Updates UI accordingly Webhook: ======== - Razorpay → Backend (automatically!) - Notifies your server about payment status - Contains signature for verification - Updates database with payment status
Steps to Integrate Razorpay
Step 1: Create Razorpay Account
Creating Razorpay Account:
==========================
1. Go to razorpay.com
2. Click "Sign Up"
3. Provide details:
- Business name
- Email
- Phone number
- Aadhar card / PAN
- Website URL
- Bank account details
4. Wait for approval (3-5 days)
Note: You can use TEST MODE while waiting!
Test mode lets you simulate payments.
Step 2: Include Required Links in Your Project
Mandatory Pages for Razorpay Approval: ====================================== Razorpay REQUIRES these pages on your website: 1. Privacy Policy 2. Terms of Use 3. Refund and Cancellation Policy 4. Contact Information 5. About Us Add these as links in your footer! ┌─────────────────────────────────────────────────┐ │ FOOTER │ │ Privacy Policy | Terms | Refund | Contact | About│ └─────────────────────────────────────────────────┘ ⚠️ Without these, Razorpay may REJECT or DELAY approval!
Step 3: Create Premium Membership Page
Frontend Component: =================== Create a "Premium" component with different plans: ┌─────────────────────────────────────────────────┐ │ Choose Your Plan │ ├─────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ SILVER │ │ GOLD │ │ │ │ │ │ │ │ │ │ ₹499/mo │ │ ₹999/mo │ │ │ │ │ │ │ │ │ │ [Buy Now]│ │ [Buy Now]│ │ │ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────┘ Link this page to Navbar for easy access!
Step 4: Create Razorpay Instance (Backend)
Step 1: Install Razorpay ======================== npm i razorpay
Step 2: Create Instance (utils/razorpay.js)
===========================================
const Razorpay = require("razorpay");
const razorpayInstance = new Razorpay({
key_id: process.env.RAZORPAY_KEY_ID,
key_secret: process.env.RAZORPAY_KEY_SECRET
});
module.exports = razorpayInstance;
Getting API Keys from Dashboard
How to Get key_id & key_secret: =============================== 1. Login to Razorpay Dashboard 2. Go to Settings → API Keys 3. Choose "Test Mode" (for development) 4. Click "Generate Key" 5. Copy Key ID and Key Secret ⚠️ IMPORTANT: Store these in .env file! // .env RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxxxxx RAZORPAY_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxx
Step 5: Create Order API (Backend)
// routes/payment.js
const express = require("express");
const { userAuth } = require("../middleware/auth");
const razorpayInstance = require("../utils/razorpay");
const Payment = require("../models/Payment");
const paymentRouter = express.Router();
// Create Order API
paymentRouter.post("/payment/create", userAuth, async (req, res) => {
try {
const { membershipType } = req.body;
// Set amount based on membership type
let amount;
if (membershipType === "silver") {
amount = 49900; // ₹499 in paise
} else if (membershipType === "gold") {
amount = 99900; // ₹999 in paise
}
// Create order with Razorpay
const order = await razorpayInstance.orders.create({
amount: amount, // Amount in PAISE (not rupees!)
currency: "INR",
receipt: "receipt_" + Date.now(),
notes: {
firstName: req.user.firstName,
lastName: req.user.lastName,
emailId: req.user.email,
membershipType: membershipType
}
});
// Save order to database
const payment = new Payment({
userId: req.user._id,
orderId: order.id,
status: order.status,
amount: order.amount,
currency: order.currency,
notes: order.notes
});
const savedPayment = await payment.save();
// Return order details to frontend
res.json({
...savedPayment.toJSON(),
keyId: process.env.RAZORPAY_KEY_ID
});
} catch (err) {
res.status(500).json({ msg: err.message });
}
});
module.exports = paymentRouter;
Important Notes: ================ 1. Amount is in PAISE, not Rupees! ₹499 = 49900 paise 2. Notes contain metadata sent with order - Used to identify user after payment - Returned in webhook 3. Receipt should be unique - Use timestamp or order number 4. Save order in DB BEFORE returning to frontend - Needed to verify payment later
Step 6: Connect API in Frontend
// Premium.jsx
const handleBuyClick = async (membershipType) => {
try {
// Call backend to create order
const response = await axios.post(
BASE_URL + "/payment/create",
{ membershipType },
{ withCredentials: true }
);
const order = response.data;
// Now display Razorpay modal
displayRazorpayModal(order);
} catch (err) {
console.error("Error creating order:", err);
}
};
Step 7: Display Payment Modal
Step 1: Add Razorpay Script to index.html ========================================= <!-- Add this in your index.html --> <script src="https://checkout.razorpay.com/v1/checkout.js"></script> This script provides the Razorpay modal!
Step 2: Create Modal Options & Display
======================================
const displayRazorpayModal = (order) => {
// Extract data from API response
const { amount, keyId, currency, notes, orderId } = order;
// Configure modal options
const options = {
key: keyId,
amount: amount,
currency: currency,
name: "Dev Tinder", // Your app name
description: "Premium Membership",
order_id: orderId,
// Pre-fill user details
prefill: {
name: notes.firstName + " " + notes.lastName,
email: notes.emailId
},
// Customize theme
theme: {
color: "#F37254" // Your brand color
},
// Called after payment modal closes
handler: function (response) {
// Verify payment status
verifyPayment();
}
};
// Create and open modal
const rzp = new window.Razorpay(options);
rzp.open();
};
Modal Window: ============= ┌─────────────────────────────────────────┐ │ Dev Tinder │ │ Premium Membership │ │ │ │ ₹499.00 │ │ │ │ ┌─────────────────────────────────┐ │ │ │ Card Number │ │ │ └─────────────────────────────────┘ │ │ │ │ ○ Credit/Debit Card │ │ ○ UPI │ │ ○ Net Banking │ │ ○ Wallet │ │ │ │ [Pay ₹499.00] │ └─────────────────────────────────────────┘
Step 8: Create Webhook in Razorpay Dashboard
What is a Webhook?
==================
A webhook is a URL that Razorpay calls AUTOMATICALLY
when a payment event occurs (success/failure).
It's like saying: "Hey Razorpay, whenever someone pays,
call THIS URL on my server!"
Setting Up Webhook: =================== 1. Go to Razorpay Dashboard 2. Navigate to: Account & Settings → Webhooks 3. Click "Add New Webhook" 4. Fill in: - Webhook URL: https://yourdomain.com/payment/webhook - Secret: your_webhook_secret (any random string) - Events: Select "payment.captured" and "payment.failed" 5. Click "Create Webhook" Save the secret in .env: RAZORPAY_WEBHOOK_SECRET=your_webhook_secret
Step 9: Create Webhook API (Backend)
const { validateWebhookSignature } = require("razorpay/dist/utils/razorpay-utils");
// Webhook API - Called by Razorpay automatically
paymentRouter.post("/payment/webhook", async (req, res) => {
try {
// Get signature from headers (sent by Razorpay)
const webhookSignature = req.headers["x-razorpay-signature"];
// Validate the webhook is from Razorpay
const isValidWebhook = validateWebhookSignature(
JSON.stringify(req.body),
webhookSignature,
process.env.RAZORPAY_WEBHOOK_SECRET
);
if (!isValidWebhook) {
return res.status(400).json({ msg: "Webhook signature invalid" });
}
// Extract payment details from webhook body
const paymentDetails = req.body.payload.payment.entity;
// Find the payment in our database
const payment = await Payment.findOne({
orderId: paymentDetails.order_id
});
if (!payment) {
return res.status(404).json({ msg: "Payment not found" });
}
// Update payment status
payment.status = paymentDetails.status;
await payment.save();
// If payment successful, update user as premium
if (paymentDetails.status === "captured") {
const user = await User.findById(payment.userId);
user.isPremium = true;
user.membershipType = payment.notes.membershipType;
user.premiumExpiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days
await user.save();
}
// Return success to Razorpay
return res.status(200).json({ msg: "Webhook received successfully" });
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
Webhook Security: ================= Why validate webhook signature? - Anyone could call your webhook URL - Signature proves request is from Razorpay - Prevents fake payment confirmations! x-razorpay-signature header: - Automatically added by Razorpay - Hash of body + your secret - validateWebhookSignature() verifies it
Step 10: Verify Payment API
// Verify if user is premium
paymentRouter.get("/premium/verify", userAuth, async (req, res) => {
try {
const user = req.user.toJSON();
if (user.isPremium) {
return res.json({
isPremium: true,
membershipType: user.membershipType
});
}
return res.json({ isPremium: false });
} catch (err) {
return res.status(500).json({ msg: err.message });
}
});
Step 11: Update Frontend UI
// Premium.jsx
const [isPremium, setIsPremium] = useState(false);
// Check premium status on mount
useEffect(() => {
verifyPremium();
}, []);
const verifyPremium = async () => {
try {
const res = await axios.get(
BASE_URL + "/premium/verify",
{ withCredentials: true }
);
if (res.data.isPremium) {
setIsPremium(true);
}
} catch (err) {
console.error(err);
}
};
// Add handler to Razorpay options
const options = {
// ... other options
handler: function (response) {
// Called when modal closes
verifyPremium(); // Check if payment succeeded
}
};
// Render based on premium status
return (
<div>
{isPremium ? (
<h1>You are a Premium Member! 🎉</h1>
) : (
// Show membership plans
)}
</div>
);
Step 12: Deploy and Update .env
Final Steps: ============ 1. Push code to your instance (AWS/Heroku/etc.) 2. Update .env file on server: RAZORPAY_KEY_ID=rzp_live_xxxxx (use LIVE keys!) RAZORPAY_KEY_SECRET=xxxxx RAZORPAY_WEBHOOK_SECRET=xxxxx 3. Update webhook URL in Razorpay Dashboard - Change from localhost to production URL 4. Switch from Test Mode to Live Mode 5. Test with real payment (small amount!)
Payment Model Schema
// models/Payment.js
const mongoose = require("mongoose");
const paymentSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true
},
orderId: {
type: String,
required: true
},
paymentId: {
type: String
},
status: {
type: String,
enum: ["created", "captured", "failed"],
default: "created"
},
amount: {
type: Number,
required: true
},
currency: {
type: String,
default: "INR"
},
notes: {
type: Object
}
}, { timestamps: true });
module.exports = mongoose.model("Payment", paymentSchema);
Quick Recap
| Step | Description |
|---|---|
| Step 1 | Create Razorpay account |
| Step 2 | Add required policy pages |
| Step 3 | Create Premium membership page |
| Step 4 | Create Razorpay instance (backend) |
| Step 5 | Create Order API |
| Step 6 | Connect API in frontend |
| Step 7 | Display payment modal |
| Step 8 | Create webhook in Razorpay |
| Step 9 | Create webhook API |
| Step 10 | Verify payment API |
| Step 11 | Update frontend UI |
| Step 12 | Deploy and go live |
Interview Questions
Q: How does payment gateway integration work?
"The frontend never directly connects to the payment gateway. When a user clicks 'Pay', the frontend calls our backend API, which creates an order with Razorpay and returns an order ID. The frontend then displays a payment modal. After payment, Razorpay notifies our backend via webhook, and we update the database accordingly."
Q: What is a webhook and why is it needed?
"A webhook is a URL that Razorpay automatically calls when a payment event occurs. It's needed because payments happen on Razorpay's servers, not ours. The webhook notifies our server about payment status (success/failure) so we can update our database and grant premium access to users."
Q: Why do we validate webhook signatures?
"We validate webhook signatures to ensure the request actually came from Razorpay and not a malicious actor. Anyone could call our webhook URL, so we use the signature (a hash of the body + our secret) to verify authenticity and prevent fake payment confirmations."
Q: Why is amount in paise instead of rupees?
"Razorpay uses paise (smallest currency unit) to avoid floating-point precision issues. ₹499 becomes 49900 paise. This is a common practice in payment systems worldwide - Stripe uses cents, for example."
Key Points to Remember
- Frontend never connects directly to Razorpay
- Two API calls: Create order + Verify payment
- Webhook automatically notifies backend
- Amount in paise (₹499 = 49900)
- Validate webhook signature for security
- Store keys in .env file
- Required pages: Privacy, Terms, Refund, Contact, About
- Test mode for development, Live mode for production
- Notes contain metadata sent with order
- handler function called when modal closes
What's Next?
Now you know how to integrate payment gateways! In the upcoming episodes, we will:
- Add subscription-based payments
- Handle payment failures and retries
- Implement invoice generation
Keep coding, keep learning! See you in the next one!
Post a Comment