Steps you need to follow for Payment Integration in Node.Js(Razorpay)

Steps you need to follow for Payment Integration in Node.Js(Razorpay)

→ If You are working for the test purpose then you can use the test mode of the razorpay. In the test mode you need to do the kyc verification and all.
You can follow this link to go to the razorpay dashboard (https://dashboard.razorpay.com/app/dashboard)

After this , You need to generate the api key and secret for the test account.

One Important thing , the documentation of razorpay is little difficult to understand , So follow the below steps to integrate payment gateway using razorpay in your node application.

Here is the link of the documentation of the razorpay → ( https://razorpay.com/docs/payments/server-integration/nodejs/integration-steps/#integrate-with-razorpay-payment-gateway )

  1. Install Razorpay → npm i razorpay

  2. Instantiate the instance of the razorpay

const Razorpay=require('razorpay');
var instance = new Razorpay({
    key_id: process.env.RAZORPAY_KEY_ID,
    key_secret: process.env.RAZORPAY_KEY_SECRET,
});

module.exports=instance;
  1. Now you will have to create the api for creating the order.For every payment that is to be done , at first the order is created.
    Sample code for creating the Order is given below.
paymentRouter.post("/payment/create", userAuth, async (req, res) => {
    try {
        const options = {
            amount: 50000, //Amount is in paise here.
            currency: "INR",
            receipt: "receipt#1",
            partial_payment: false,
            notes: {  // Here we can pass some meta data like first name,last name,plan purchase info.
                firstName: "value3",
                lastName: "value2",
                membership: "silver",
            },
        };
    // This order create return a promise therefore we need to use await to resolve that promise.
        const order = await razorpayInstance.orders.create(options);
        console.log(order);
}
 catch(error)
 {
     console.error(error);
 }

Sample Response for this api from the razorpay is given below:→

{
    "order": {
        "amount": 50000,
        "amount_due": 50000,
        "amount_paid": 0,
        "attempts": 0,
        "created_at": 1737005150,
        "currency": "INR",
        "entity": "order",
        "id": "order_PjzyyaDjnktLcn",
        "notes": {
            "firstName": "value3",
            "lastName": "value2",
            "membership": "silver"
        },
        "offer_id": null,
        "receipt": "receipt#1",
        "status": "created"
    }
}
  1. You should save the payment in your database too.You can create a payment Schema and save the payment .Sample code snippet is given below for payment Schema
const mongoose = require("mongoose");
const paymentSchema = new mongoose.Schema(
    {
        userId: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User",
            required: true,
        },
        amount: {
            type: Number,
            required: true,
        },
        currency: {
            type: String,
            required: true,
            default: "INR",
        },
        receipt: {
            type: String,
            required: true,
        },
        paymentId: {
            type: String,
        },
        orderId: {
            type: String,
            required: true,
        },
        status: {
            type: String,
            required: true,
        },
        notes: {
            firstName: {
                type: String,
            },
            lastName: {
                type: String,
            },
            membership: {
                type: String,
            },
        },
    },
    { timestamps: true }
);

module.exports = mongoose.model("Payment", paymentSchema);
  1. After saving the payment just return the payment’s information to the frontend.Whole code for creating a order, saving the order to the databse and returning it to the frontend is given below.
const express = require("express");
const { userAuth } = require("../middlewares/auth");
const paymentRouter = express.Router();
const razorpayInstance = require("../utils/razorpay");
const Payment = require("../models/payment");

paymentRouter.post("/payment/create", userAuth, async (req, res) => {
    try {
        const options = {
            amount: 50000, //Amount is in paise here.
            currency: "INR",
            receipt: "receipt#1",
            partial_payment: false,
            notes: {
                firstName: "value3",
                lastName: "value2",
                membership: "silver",
            },
        };
        const order = await razorpayInstance.orders.create(options);
        const payment = new Payment({
            userId: req.user._id,
            amount: order.amount,
            currency: order.currency,
            receipt: order.receipt,
            orderId: order.id,
            status: order.status,
            notes: order.notes,
        });
        const savedPayment = await payment.save();
        res.json({ ...savedPayment.toJSON() });

        //Save it in the database
        console.log(order);
        res.json({ order });
    } catch (error) {
        console.error(error);
    }
});

module.exports = paymentRouter;
  1. Now in the frontend we have to write some code to open the razorpay dialog box for further payment processing.

→ The sample code for this is provided in the documentation too, But they have provided the generalized code , You can’t directly copy paste it , You have to modify it according to your own requirement and tech stack.

The code which was provided by them is given below

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Razorpay Payment</title>
</head>
<body>
  <h1>Razorpay Payment Gateway Integration</h1>
  <form id="payment-form">
    <label for="amount">Amount:</label>
    <input type="number" id="amount" name="amount" required>
    <button type="button" onclick="payNow()">Pay Now</button>
  </form>

  <script src="https://checkout.razorpay.com/v1/checkout.js"></script>
  <script>
    async function payNow() {
      const amount = document.getElementById('amount').value;

      // Create order by calling the server endpoint
      const response = await fetch('/create-order', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ amount, currency: 'INR', receipt: 'receipt#1', notes: {} })
      });

      const order = await response.json();

      // Open Razorpay Checkout
      const options = {
        key: 'YOUR_KEY_ID', // Replace with your Razorpay key_id
        amount: '50000', // Amount is in currency subunits. Default currency is INR. Hence, 50000 refers to 50000 paise
        currency: 'INR',
        name: 'Acme Corp',
        description: 'Test Transaction',
        order_id: 'order_IluGWxBm9U8zJ8', // This is the order_id created in the backend
        callback_url: 'http://localhost:3000/payment-success', // Your success URL
        prefill: {
          name: 'Gaurav Kumar',
          email: 'gaurav.kumar@example.com',
          contact: '9999999999'
        },
        theme: {
          color: '#F37254'
        },
      };

      const rzp = new Razorpay(options);
      rzp.open();
    }
  </script>
</body>
</html>

Now we have to do some sort of modifications , Like

a) the script tag <script src="checkout.razorpay.com/v1/checkout.js"></script>
Should be added in the head tag of the index.html if you are using react or some related framwork.

b) const rzp = new Razorpay(options); rzp.open();
This is the main code which opens the dialog box for the payment . Now here Razorpay comes from the script tag , therefore it can’t be required or accessed directly, And hence we have to extract it from the windows object .The modified code is this
const rzp = new window.Razorpay(options);

Now here, options will come from the API response.

The one most important thing is, we must not pass the informatpion about amount from the frontend , The amount should be handled from the backend only, Otherwise someone can temper with that amount which may result into your monitary loss.

I am providing the whole code below of frontend :→

import axios from "axios";
import React from "react";
import { BASE_URL } from "../utils/constants";

const Premium = () => {
    const handleBuyClick = async (type) => {
        const order = await axios.post(
//This is the same API call whose code has been mentioned above.
            BASE_URL + "/payment/create",
            {
                membershipType: type,
            },
            {
                withCredentials: true,
            }
        );
        //After getting the positive response , the razorpay dialog box should be opeaned.
        // This Razorpay will come from the script tag which has been added in the head tag of the index.html.
        const {amount,currency,keyId,notes,orderId}=order.data;
        const options = {
            //It is the same key id of the razorpay account , And It can be public so, we can keep it in the frontend code.
            key: keyId, // Replace with your Razorpay key_id
            amount, // Amount is in currency subunits. Default currency is INR. Hence, 50000 refers to 50000 paise
            currency,
            name: "devTinder",
            description: "Connect to the other developers",
            order_id: orderId, // This is the order_id created in the backend
            prefill: {
                name:notes.firstName+" "+notes.lastName,
                email: notes.emailId,
                contact: "9999999999",
            },
            theme: {
                color: "#F37254",
            },
        };

        const rzp = new window.Razorpay(options);
        rzp.open();
    };

    return (
        <div className="m-10">
            <div className="flex w-full">
                <div className="card bg-base-300 rounded-box grid h-80 flex-grow place-items-center">
                    <h1 className="text-3xl">Silver Membership</h1>
                    <ul>
                        <li>- Chat with other people</li>
                        <li>- 100 connections per day</li>
                        <li>- Blue Tick</li>
                        <li>- 3 months</li>
                    </ul>
                    <button
                        className="btn btn-secondary"
                        onClick={() => handleBuyClick("silver")}
                    >
                        Buy Silver
                    </button>
                </div>
                <div className="divider divider-horizontal"></div>
                <div className="card bg-base-300 rounded-box grid h-80 flex-grow place-items-center">
                    <h1 className="text-3xl                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     ">
                        Gold Membership
                    </h1>
                    <ul>
                        <li>- Chat with other people</li>
                        <li>- 300 connections per day</li>
                        <li>- Blue Tick</li>
                        <li>- 6 months</li>
                    </ul>
                    <button
                        className="btn btn-primary"
                        onClick={() => handleBuyClick("gold")}
                    >
                        Buy Gold
                    </button>
                </div>
            </div>
        </div>
    );
};

export default Premium;
  1. Now as soon as the payment is successfull then the razorpay needs to notify that yes payment is successfull or vice-versa. Therefore it is to be done with the webhook.

→ You can go to the “account and settings” to the razorpay dashboard , then in that in the section of “website and app settings” , go to the “webhooks” and then we need to create the webhook url.

→ While creting the webhook url we need to select the events too like payment.captured(payment success) or payment.failed.

→ Web Hook Url means “what api the razorpay should call on the success or failure of the payment.”

→ Now this is the inmportant part of the payment integration. Once the url is generated
We have to write the post request which will be made by the razorpay automatically.(Therefore make sure that while writing that api we must not write the middleware which is used for checking whether the user is logged in or not.)

→ This is the link for the webhook validation’s documentation → (https://razorpay.com/docs/webhooks/validate-test/) But it is very tough to understand.
Let me simplify the process for you :→

Below is the code of documentation

/* NODE SDK: https://github.com/razorpay/razorpay-node */
const {validateWebhookSignature} = require('razorpay/dist/utils/razorpay-utils')

validateWebhookSignature(JSON.stringify(webhookBody), webhookSignature, webhookSecret)
#webhook_body should be raw webhook request body

But here nothing is mentioned about the webhookBody,webhookSignature and webhook Secret.
→ webhookSecret → It is the secret which we have to create while creating the webhook url for security purpose.

→ webhookBody → It is the body which will be coming from the request, So simply we can replace webhookBody with the req.body.

→ webhookSignature → This was literally tough to find. But when the razorpay makes the webhook call then there is header present with the key as “X-Razorpay-Signature”, Therefore we have to fetch it and then we will be able to use this.
Here is the sample code snippet :→” const webhookSignature = req.get("X-Razorpay-Signature")Th; “

→ We must send back 200 status to the razorpay otherwise it will keep notifying and we will end up in a loop.😂.

→ Below is the exact sample code with the help of which you can implement this.

const express = require("express");
const paymentRouter = express.Router();
const razorpayInstance = require("../utils/razorpay");
const Payment = require("../models/payment");
const { membershipAmount } = require("../utils/constants");
const {validateWebhookSignature} = require("razorpay/dist/utils/razorpay-utils");
const User = require("../models/user");
// This request is webhook and it will be called by the razorpay therfore we must not use the userAuth here.
paymentRouter.post("/payment/webhook", async (req, res) => {
    try {
        const webhookSignature = req.get("X-Razorpay-Signature");
        const isWebhookValid = validateWebhookSignature(
            JSON.stringify(req.body),
            webhookSignature,
            process.env.RAZORPAY_WEBHOOK_SECRET
        );
        if (!isWebhookValid) {
            return res.status(400).send("Webhook is not valid");
        }
        // Update my payment status in the database
        const paymentDetails = req.body.payload.payment.entity;
        const payment = await Payment.findOne({
            orderId: paymentDetails.order_id,
        });
        payment.status = paymentDetails.status;
        await payment.save();
        // Update the user as premium
        const user = await User.findOne({ _id: payment.userId });
        user.isPremium = true;
        user.membershipType = payment.notes.membershipType;
        // return success response to the webhook
        /* if (req.body.event === "payment.captured") {
        }
        if (req.body.event === "payment.failed") {
        } */
        return res.status(200).json({ msg: "WebHook Received" });
    } catch (error) {
        console.error(error);
        return res.status(500).send("Internal Server Error");
    }
});
module.exports = paymentRouter;