DEV Community

Cover image for Integrate Razorpay Payment Gateway in your React app
Soumya Dey
Soumya Dey

Posted on • Edited on

Integrate Razorpay Payment Gateway in your React app

What is Razorpay?

Razorpay is a payments solution in India that allows businesses to access all payment modes including credit card, debit card, netbanking, UPI and other popular wallets.

Don't confuse India-only business with single currency payments.
Razorpay accepts payments in almost all currencies.
Enter fullscreen mode Exit fullscreen mode

Before integrating the payment gateway in our app let us understand the payment flow of razorpay.

Payment Flow

Alt Text

  1. Customers place an order on your website or app
  2. You create an order from your server using the Razorpay instance.
  3. You then pass the Order ID to the checkout and collect payment details.
  4. Authentication of a payment then happens in your back-end by verifying the signature returned by Razorpay.
  5. Capture the payment.

This is a high-level representation of the Razorpay payment flow.
You can TRY OUT the payment flow live here.


Let's Integrate Razorpay in Our App

Create a Razorpay account

Navigate to Razorpay Dashboard and create a account for yourself. No need to activate it as we are going to work in Test mode.

Go to Settings tab and generate your API keys and save them somewhere. We'll need this later.

Create our server

Create a folder in your pc and initialize npm on that folder.

npm init
Enter fullscreen mode Exit fullscreen mode

We'll need to install some dependencies for our server.
Install these dependencies.

  • express
  • razorpay
  • dotenv
  • crypto (for verifying the signature in the last step)
  • mongoose (Optional : If you waant to save the details in a database)
npm i express razorpay dotenv crypto mongoose
Enter fullscreen mode Exit fullscreen mode

Now create a file server.js in the root directory and initialize your express server.

const express = require("express");

const app = express();
const port = process.env.PORT || 5000;

// middlewares
app.use(express.json({ extended: false }));

app.listen(port, () => console.log(`server started on port ${port}`));
Enter fullscreen mode Exit fullscreen mode

So now our server is running on localhost:5000 and we need to add some routes for handling the payment steps in our backend.

Save the API keys

We'll use .env file to save our keys. Create a file .env in your root directory.
Add the following code inside .env file and replace the values with your keys.

RAZORPAY_SECRET=<your razorpay secret>
RAZORPAY_KEY_ID=<your razorpay ket id>
Enter fullscreen mode Exit fullscreen mode

Don't add any quotes.

Add route for creating orders

Create a new folder called routes in your root directory and add a new file payment.js inside the routes folder.

  • We need to create a POST route to create a order.
  • For that we'll have to create a instance of Razorpay.
  • Then we'll call instance.orders.create()
require("dotenv").config();
const express = require("express");
const Razorpay = require("razorpay");

const router = express.Router();

router.post("/orders", async (req, res) => {
    try {
        const instance = new Razorpay({
            key_id: process.env.RAZORPAY_KEY_ID,
            key_secret: process.env.RAZORPAY_SECRET,
        });

        const options = {
            amount: 50000, // amount in smallest currency unit
            currency: "INR",
            receipt: "receipt_order_74394",
        };

        const order = await instance.orders.create(options);

        if (!order) return res.status(500).send("Some error occured");

        res.json(order);
    } catch (error) {
        res.status(500).send(error);
    }
});
Enter fullscreen mode Exit fullscreen mode

Note: the amount needs to be in the smallest currency unit i.e for 500 Rupess you'll have to spicify 50000 in the amount variable

Now import this payment.js route file in your server.js file to enable the route. Now your server.js file will look like this...

const express = require("express");

const app = express();
const port = process.env.PORT || 5000;

// middlewares
app.use(express.json({ extended: false }));

// route included
app.use("/payment", require("./routes/payment"));

app.listen(port, () => console.log(`server started on port ${port}`));
Enter fullscreen mode Exit fullscreen mode

Now we'll need to have a front-end form where we'll make the request for creating the order.

Create a React app

We'll create the react app inside our root directory.

npx create-react-app client
Enter fullscreen mode Exit fullscreen mode

I've given my app name as client. You can give any name you want.
After all this let's review our folder structure, So that you don't get lost.

Alt Text

Now lets clean up all the boilerplate react code.
After clean up your react app folder should look like this. You can delete all useless files from the folder.

Alt Text

Also install axios inside your react app folder for making requests to the back-end.

cd client
npm i axios
Enter fullscreen mode Exit fullscreen mode

Add a button to start the payment flow

Go to App.js file and replace all the code with the following.

import React from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";

function App() {

    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>Buy React now!</p>
                <button className="App-link" onClick={displayRazorpay}>
                    Pay ₹500
                </button>
            </header>
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

You can see I've only added a button and assigned a function displayRazorpay to the onClick event. So now lets create that function.
We'll need to do 3 things when the button is clicked.

  1. Load the Razorpay checkout script that does all the work behind the scene for us.
  2. Make a POST request to our backend route /payment/orders to create a new order, which will return details of the order including the idand amount.
  3. Then use that id and amount and finally show the Razorpay popup.

For loading the script I've added a function loadScript that takes a script src string as input and returns a Promise that loads the script.

function loadScript(src) {
        return new Promise((resolve) => {
            const script = document.createElement("script");
            script.src = src;
            script.onload = () => {
                resolve(true);
            };
            script.onerror = () => {
                resolve(false);
            };
            document.body.appendChild(script);
        });
}
Enter fullscreen mode Exit fullscreen mode

Then I'll add the function displayRazorpay that will first load the script, then make a post request to our back-end route and finally show the popup.

async function displayRazorpay() {
        const res = await loadScript(
            "https://checkout.razorpay.com/v1/checkout.js"
        );

        if (!res) {
            alert("Razorpay SDK failed to load. Are you online?");
            return;
        }

        // creating a new order
        const result = await axios.post("http://localhost:5000/payment/orders");

        if (!result) {
            alert("Server error. Are you online?");
            return;
        }

        // Getting the order details back
        const { amount, id: order_id, currency } = result.data;

        const options = {
            key: "rzp_test_r6FiJfddJh76SI", // Enter the Key ID generated from the Dashboard
            amount: amount.toString(),
            currency: currency,
            name: "Soumya Corp.",
            description: "Test Transaction",
            image: { logo },
            order_id: order_id,
            handler: async function (response) {
                const data = {
                    orderCreationId: order_id,
                    razorpayPaymentId: response.razorpay_payment_id,
                    razorpayOrderId: response.razorpay_order_id,
                    razorpaySignature: response.razorpay_signature,
                };

                const result = await axios.post("http://localhost:5000/payment/success", data);

                alert(result.data.msg);
            },
            prefill: {
                name: "Soumya Dey",
                email: "SoumyaDey@example.com",
                contact: "9999999999",
            },
            notes: {
                address: "Soumya Dey Corporate Office",
            },
            theme: {
                color: "#61dafb",
            },
        };

        const paymentObject = new window.Razorpay(options);
        paymentObject.open();
}
Enter fullscreen mode Exit fullscreen mode

For every successful payment, the checkout returns:

  • razorpay_payment_id
  • razorpay_order_id
  • razorpay_signature

We can access these values inside the handler property. And as you can see I've made a request to a new back-end route and sent those details with the order id that we recieved while creating the order before. This is for verifying the whether the payment is legit or not.

Don't confuse the "razorpay_order_id" with the "order_id" we got
while creating a new order. These two are entirely different.
Enter fullscreen mode Exit fullscreen mode

Now your App.js file should look like the following.

import React from "react";
import logo from "./logo.svg";
import "./App.css";
import axios from "axios";

function App() {
    function loadScript(src) {
        return new Promise((resolve) => {
            const script = document.createElement("script");
            script.src = src;
            script.onload = () => {
                resolve(true);
            };
            script.onerror = () => {
                resolve(false);
            };
            document.body.appendChild(script);
        });
    }

    async function displayRazorpay() {
        const res = await loadScript(
            "https://checkout.razorpay.com/v1/checkout.js"
        );

        if (!res) {
            alert("Razorpay SDK failed to load. Are you online?");
            return;
        }

        const result = await axios.post("http://localhost:5000/payment/orders");

        if (!result) {
            alert("Server error. Are you online?");
            return;
        }

        const { amount, id: order_id, currency } = result.data;

        const options = {
            key: "rzp_test_r6FiJfddJh76SI", // Enter the Key ID generated from the Dashboard
            amount: amount.toString(),
            currency: currency,
            name: "Soumya Corp.",
            description: "Test Transaction",
            image: { logo },
            order_id: order_id,
            handler: async function (response) {
                const data = {
                    orderCreationId: order_id,
                    razorpayPaymentId: response.razorpay_payment_id,
                    razorpayOrderId: response.razorpay_order_id,
                    razorpaySignature: response.razorpay_signature,
                };

                const result = await axios.post("http://localhost:5000/payment/success", data);

                alert(result.data.msg);
            },
            prefill: {
                name: "Soumya Dey",
                email: "SoumyaDey@example.com",
                contact: "9999999999",
            },
            notes: {
                address: "Soumya Dey Corporate Office",
            },
            theme: {
                color: "#61dafb",
            },
        };

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

    return (
        <div className="App">
            <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>Buy React now!</p>
                <button className="App-link" onClick={displayRazorpay}>
                    Pay ₹500
                </button>
            </header>
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

If you start your react app the app should look like this in the browser

Alt Text

And if you click the Pay ₹500 button a popup should appear.

Alt Text

But don't proceed with the payment yet. We need to add another route in our back-end to verify the payment.

Verify the payment

For this step we just need to create a signature by ourselves in the back-end and check if our signature is same as the signature sent by razorpay.
Remember, after successful payment our react app is sending back four values to the backend route /payment/success.

  • orderCreationId (order id, we got back while creating the order)
  • razorpayPaymentId
  • razorpayOrderId
  • razorpaySignature

We'll need to use the SHA256 algorithm, use the razorpayPaymentId and the orderCreationId to construct a HMAC hex digest. Then compare the digest with the razorpaySignature. If both are equal, then out payment is verified.

Create a route for verification

Navigate to payment.js file inside your routes folder and the following POST route.

router.post("/success", async (req, res) => {
    try {
        // getting the details back from our font-end
        const {
            orderCreationId,
            razorpayPaymentId,
            razorpayOrderId,
            razorpaySignature,
        } = req.body;

        // Creating our own digest
        // The format should be like this:
        // digest = hmac_sha256(orderCreationId + "|" + razorpayPaymentId, secret);
        const shasum = crypto.createHmac("sha256", "w2lBtgmeuDUfnJVp43UpcaiT");

        shasum.update(`${orderCreationId}|${razorpayPaymentId}`);

        const digest = shasum.digest("hex");

        // comaparing our digest with the actual signature
        if (digest !== razorpaySignature)
            return res.status(400).json({ msg: "Transaction not legit!" });

        // THE PAYMENT IS LEGIT & VERIFIED
        // YOU CAN SAVE THE DETAILS IN YOUR DATABASE IF YOU WANT

        res.json({
            msg: "success",
            orderId: razorpayOrderId,
            paymentId: razorpayPaymentId,
        });
    } catch (error) {
        res.status(500).send(error);
    }
});
Enter fullscreen mode Exit fullscreen mode

Now all the steps are completed.
Now you can proceed to make a payment and if successfull you can view the payment inside your Razorpay Dashboard in the Transactions tab.
Below are some demo card details you can use to test the payment flow in Test mode.

Alt Text

Use any valid expiration date in the future and any random CVV to create a successful payment.
And when ready you can switch to Live mode and activate your account before production. You'll be given a new set of API keys for the Live mode.

You can find all the source code in my Github repo.

When you are there don't forget to Follow me on Github

Thanks a lot for reading.

I'll also make a blog post about Integrating Stripe with React. Stay tuned for that!


Find me around web 🕸:

Top comments (29)

Collapse
 
webdevavi profile image
Avinash Sinha

This is exactly what i needed, thank you so much

Collapse
 
mohsinalisoomro profile image
MOHSIN ALI SOOMRO

Thanks bro

Collapse
 
karthickailanware profile image
karthic-kailanware

Thanks for the detailed post

Collapse
 
soumyadey profile image
Soumya Dey

Happy to help you 😄👍

Collapse
 
dibyajyotimishra profile image
DIBYAJYOTI MISHRA

Hi. I am getting undefined values of razorpayOrderId and razorpaySignature from the response while working on test mode. So the payment verification fails in the backend. I have been stuck at this for over a day now. Any help is highly appreciated.

Collapse
 
zuveriya4 profile image
Zuveriya4

have you solved it?? i have been stuck on the same

Collapse
 
loop36 profile image
Anand V Balagopalan

// middlewares
app.use(express.json({ extended: false }));

This line helped me when I had similar issue

Collapse
 
arif_siddiqui profile image
Arifuddin Siddiqui

I'm getting this error for my frontend code and the router for the backend is not even hitting can you help me out with the issue
checkout-frame.modern.js:1

   GET https://browser.sentry-cdn.com/7.64.0/bundle.min.js net::ERR_BLOCKED_BY_CLIENT  `import React,{useEffect} from 'react'
Enter fullscreen mode Exit fullscreen mode

import axios from 'axios'

const Premium = () => {
function loadScript(src) {
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = src;
script.onload = () => {
resolve(true);
};
script.onerror = () => {
resolve(false);
};
document.body.appendChild(script);
});
}

async function displayRazorpay() {
    console.log("hello from display razorpay");
    const res = await loadScript(
        "https://checkout.razorpay.com/v1/checkout.js"
    );
    console.log("res = ",res)
    if (res==false) {
        alert("Razorpay SDK failed to load. Are you online?");
        return;
    }

    const result = await axios.post("http://localhost:3000/orders");
    console.log("result = ",result)
    if (!result) {
        alert("Server error. Are you online?");
        return;
    }
    console.log("hello from loadScript")
    const { amount, id: order_id, currency } = result.data;

    const options = {
        key: "rzp_test_mlDqpbOrW5vbFn",
        amount: amount.toString(),
        currency: currency,
        name: "Soumya Corp.",
        description: "Test Transaction",
        image: { logo },
        order_id: order_id,
        handler: async function (response) {
            const data = {
                orderCreationId: order_id,
                razorpayPaymentId: response.razorpay_payment_id,
                razorpayOrderId: response.razorpay_order_id,
                razorpaySignature: response.razorpay_signature,
            };
            console.log("data = ",data)
            const result = await axios.post("http://localhost:5000/payment/success", data);

            alert(result.data.msg);
        },
        prefill: {
            name: "Soumya Dey",
            email: "SoumyaDey@example.com",
            contact: "9999999999",
        },
        notes: {
            address: "Soumya Dey Corporate Office",
        },
        theme: {
            color: "#61dafb",
        },
    };

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

return (
    <div style={{ backgroundColor: '#F3F4F6' }} className="h-screen">
        <main  className="max-w-6xl mx-auto pt-10  px-8 overflow-y-hidden ">
            <div className="max-w-md mx-auto mb-14 text-center overflow-hidden">
                <h1 className="text-4xl font-semibold mb-6 lg:text-5xl overflow-hidden">
                    <span className="text-indigo-600">Flexible</span> Plans
                </h1>
                <p className="text-xl text-gray-500 font-medium overflow-hidden">
                    Choose a plan that works best for you and your team.
                </p>
            </div>
            <div className="flex flex-col justify-between items-center lg:flex-row lg:items-start">
                <div className="w-full flex-1 mt-8 p-8 order-2 bg-white shadow-xl rounded-3xl sm:w-96 lg:w-full lg:order-1 lg:rounded-r-none">
                    <div className="mb-7 pb-7 flex items-center border-b border-gray-300">
                        <img
                            src="https://res.cloudinary.com/williamsondesign/abstract-1.jpg"
                            alt=""
                            className="rounded-3xl w-20 h-20"
                        />
                        <div className="ml-5 overflow-hidden ">
                            <span className="block text-2xl font-semibold overflow-hidden">Basic</span>
                            <span>
                                <span className="font-medium text-gray-500 text-xl align-top ">
                                    $
                                </span>
                                <span className="text-3xl font-bold ">10 </span>
                            </span>
                            <span className="text-gray-500 font-medium " >/ user</span>
                        </div>
                    </div>
                    <ul className="mb-7 font-medium text-gray-500 ">
                        <li className="flex text-lg mb-2 ">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                Get started with <span className="text-black">messaging</span>
                            </span>
                        </li>
                        <li className="flex text-lg mb-2">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                Flexible <span className="text-black">team meetings</span>
                            </span>
                        </li>
                        <li className="flex text-lg">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                <span className="text-black">5 TB</span> cloud storage
                            </span>
                        </li>
                    </ul>
                    <button  onClick={displayRazorpay}
                        className=" App-link flex justify-center items-center bg-indigo-600 rounded-xl py-5 px-20 text-center text-white text-xl"
                    >
                        Choose Plan
                        <img
                            src="https://res.cloudinary.com/williamsondesign/arrow-right.svg"
                            className="ml-2"
                        />
                    </button>
                </div>
                <div className="w-full flex-1 p-8 order-1 shadow-xl rounded-3xl bg-gray-900 text-gray-400 sm:w-96 lg:w-full lg:order-2 lg:mt-0">
                    <div className="mb-8 pb-8 flex items-center border-b border-gray-600">
                        <img
                            src="https://res.cloudinary.com/williamsondesign/abstract-2.jpg"
                            alt=""
                            className="rounded-3xl w-20 h-20"
                        />
                        <div className="ml-5 overflow-hidden">
                            <span className="block text-3xl font-semibold text-white overflow-hidden">
                                Startup
                            </span>
                            <span>
                                <span className="font-medium text-xl align-top">$ </span>
                                <span className="text-3xl font-bold text-white">24 </span>
                            </span>
                            <span className="font-medium">/ user</span>
                        </div>
                    </div>
                    <ul className="mb-10 font-medium text-xl">
                        <li className="flex mb-6">
                            <img src="https://res.cloudinary.com/williamsondesign/check-white.svg" />
                            <span className="ml-3 overflow-hidden">
                                All features in <span className="text-white">Basic</span>
                            </span>
                        </li>
                        <li className="flex mb-6">
                            <img src="https://res.cloudinary.com/williamsondesign/check-white.svg" />
                            <span className="ml-3 overflow-hidden">
                                Flexible <span className="text-white ">call scheduling</span>
                            </span>
                        </li>
                        <li className="flex ">
                            <img src="https://res.cloudinary.com/williamsondesign/check-white.svg" />
                            <span className="ml-3 overflow-hidden">
                                <span className="text-white ">15 TB</span> cloud storage
                            </span>
                        </li>
                    </ul>
                    <a
                        href="#/"
                        className="flex justify-center items-center bg-indigo-600 rounded-xl py-6 px-4 text-center text-white text-2xl"
                    >
                        Choose Plan
                        <img
                            src="https://res.cloudinary.com/williamsondesign/arrow-right.svg"
                            className="ml-2"
                        />
                    </a>
                </div>
                <div className="w-full flex-1 mt-8 p-8 order-3 bg-white shadow-xl rounded-3xl sm:w-96 lg:w-full lg:order-3 lg:rounded-l-none">
                    <div className="mb-7 pb-7 flex items-center border-b border-gray-300">
                        <img
                            src="https://res.cloudinary.com/williamsondesign/abstract-3.jpg"
                            alt=""
                            className="rounded-3xl w-20 h-20"
                        />
                        <div className="ml-5 overflow-hidden">
                            <span className="block text-2xl font-semibold overflow-hidden">Enterprise</span>
                            <span>
                                <span className="font-medium text-gray-500 text-xl align-top">
                                    $
                                </span>
                                <span className="text-3xl font-bold">35 </span>
                            </span>
                            <span className="text-gray-500 font-medium">/ user</span>
                        </div>
                    </div>
                    <ul className="mb-7 font-medium text-gray-500">
                        <li className="flex text-lg mb-2">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                All features in <span className="text-black">Startup</span>
                            </span>
                        </li>
                        <li className="flex text-lg mb-2">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                Growth <span className="text-black">oriented</span>
                            </span>
                        </li>
                        <li className="flex text-lg">
                            <img src="https://res.cloudinary.com/williamsondesign/check-grey.svg" />
                            <span className="ml-3">
                                <span className="text-black">Unlimited</span> cloud storage
                            </span>
                        </li>
                    </ul>
                    <a
                        href="#/"
                        className="flex justify-center items-center bg-indigo-600 rounded-xl py-5 px-4 text-center text-white text-xl"
                    >
                        Choose Plan
                        <img
                            src="https://res.cloudinary.com/williamsondesign/arrow-right.svg"
                            className="ml-2"
                        />
                    </a>
                </div>
            </div>
        </main>
    </div>

)
Enter fullscreen mode Exit fullscreen mode

}

export default Premium`

Collapse
 
neeshsamsi profile image
Neesh Samsi

Very detailed post, just what I was looking for. I have never worked with verifying signatures so maybe I missed it but where did you get the string "w2lBtgmeuDUfnJVp43UpcaiT"? Is that something standard or something you decided on, or is it something dynamic?

Collapse
 
karanmartian profile image
Karan Kamdar

Any idea how to use the razorpay.createPayment() function from the checkout.razorpay.com/v1/razorpay.js library with React? Been sweating it out from the last two nights to get this working as per below doc:

razorpay.com/docs/payment-gateway/...

The RZP checkout should pop up and stay but it comes momentarily and then redirects to the callback URL. I have tried with the callback URL method as that's the only one I can use with React.

I am pulling my hair out for this one, any help would be greatly appreciated!

Collapse
 
piyushgargdev profile image
Piyush Garg

Hey, Great Post

I just made a package on npm to intergrate razorpay in your react app.
I would highly appreciate if your give me reviews on it.

npmjs.com/package/react-razorpay

Collapse
 
karanmartian profile image
Karan Kamdar

Great article! One question though: Would it be better to have the loadScript run at page load time as opposed to running JIT when the func is activated?

Collapse
 
soumyadey profile image
Soumya Dey

Yeah. That would make the Razorpay popup appear a bit quicker. Thanks for the suggestion.

Collapse
 
aashishkumar123 profile image
Aashish Kumar

You don't need to add currency and amount, it will comes with order_id while request for payment.