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!