DEV Community

Cover image for Integrate Razorpay Payment Gateway into your React app
Sudhanshu Gaikwad
Sudhanshu Gaikwad

Posted on

Integrate Razorpay Payment Gateway into your React app

What is Razorpay?

Razorpay is a popular payment gateway.
It lets your website or app accept payments via:

  • Credit / Debit cards
  • Net Banking
  • UPI
  • Wallets (Paytm, PhonePe, etc.)

It also supports multiple currencies, so you can use it for Indian as well as international customers.
Before writing any code, let’s understand the payment flow.


Payment Flow (Simple Explanation)

High-level flow

  1. User clicks the Pay button
  2. Frontend asks your backend to create a Razorpay Order
  3. Backend creates the order and sends back order_id, amount, and currency
  4. Frontend opens Razorpay Checkout with those details
  5. After successful payment, Razorpay gives you payment_id, order_id, and signature
  6. Frontend sends these three values to your backend
  7. Backend verifies the signature using HMAC-SHA256 Only after verification, mark the order as Paid in your database

Important: Never skip step 7. Always verify the signature on the backend.

This is a high-level overview of how the Razorpay payment flow works. Understanding this flow will make it easier to integrate Razorpay securely into a React application.


1. Razorpay account & keys

  1. Sign up at dashboard.razorpay.com
  2. Stay in Test Mode while developing
  3. Generate API keys under Account & Settings → API Keys
  4. Store them securely:

RAZORPAY_KEY_ID=rzp_test_xxxxxxxx     # This can be used in frontend (public)
RAZORPAY_KEY_SECRET=your_secret_here  # This must stay only on backend

Enter fullscreen mode Exit fullscreen mode

2. Backend (Node + Express)

Create a new folder and install packages:


mkdir razorpay-server
cd razorpay-server
npm init -y
npm install express cors razorpay dotenv crypto

Enter fullscreen mode Exit fullscreen mode

Create a .env file:


RAZORPAY_KEY_ID=rzp_test_xxxxxxxx
RAZORPAY_KEY_SECRET=your_secret_here
PORT=5000

Enter fullscreen mode Exit fullscreen mode

Create server.js:

require("dotenv").config();
const express = require("express");
const cors = require("cors");
const Razorpay = require("razorpay");
const crypto = require("crypto");

const app = express();
app.use(cors());
app.use(express.json());

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

// Create Order
app.post("/api/create-order", async (req, res) => {
  try {
    const { amount, currency = "INR", receipt } = req.body;

    if (!amount || amount < 1) {
      return res.status(400).json({ error: "Invalid amount" });
    }

    const order = await razorpay.orders.create({
      amount: Math.round(amount * 100), // Convert rupees to paise
      currency,
      receipt: receipt || `rcpt_${Date.now()}`,
    });

    res.json({
      id: order.id,
      amount: order.amount,
      currency: order.currency,
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Could not create order" });
  }
});

// Verify Payment
app.post("/api/verify-payment", async (req, res) => {
  try {
    const {
      orderCreationId,
      razorpayPaymentId,
      razorpayOrderId,
      razorpaySignature,
    } = req.body;

    // Create our own signature
    const shasum = crypto.createHmac(
      "sha256",
      process.env.RAZORPAY_KEY_SECRET
    );
    shasum.update(`${orderCreationId}|${razorpayPaymentId}`);
    const digest = shasum.digest("hex");

    // Compare signatures
    if (digest !== razorpaySignature) {
      return res.status(400).json({ msg: "Transaction not legit!" });
    }

    // Payment is verified
    // You can now save order details in your database
    res.json({
      success: true,
      msg: "Payment verified successfully",
      orderId: razorpayOrderId,
      paymentId: razorpayPaymentId,
    });
  } catch (error) {
    res.status(500).send(error);
  }
});

app.listen(process.env.PORT || 5000, () => {
  console.log("Server is running on port 5000");
});
Enter fullscreen mode Exit fullscreen mode

Run the server:

node server.js
Enter fullscreen mode Exit fullscreen mode

3. Frontend (Vite + React)

Create a new React app:


npm create vite@latest razorpay-client -- --template react
cd razorpay-client
npm install

Enter fullscreen mode Exit fullscreen mode

Create a .env file in the React project:


VITE_RAZORPAY_KEY_ID=rzp_test_xxxxxxxx

Enter fullscreen mode Exit fullscreen mode

Payment Button Component

Create a file PayButton.jsx:

import { useState } from "react";

export default function PayButton({ amount = 499, productName = "Premium Plan" }) {
  const [loading, setLoading] = useState(false);

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

  const handlePay = async () => {
    setLoading(true);

    const scriptLoaded = await loadScript(
      "https://checkout.razorpay.com/v1/checkout.js"
    );

    if (!scriptLoaded) {
      alert("Failed to load Razorpay. Please check your internet.");
      setLoading(false);
      return;
    }

    try {
      // 1. Create order from backend
      const orderRes = await fetch("http://localhost:5000/api/create-order", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ amount, currency: "INR" }),
      });

      const order = await orderRes.json();

      if (!order.id) throw new Error("Order creation failed");

      // 2. Open Razorpay Checkout
      const options = {
        key: import.meta.env.VITE_RAZORPAY_KEY_ID,
        amount: order.amount,
        currency: order.currency,
        name: "Your Brand Name",
        description: productName,
        order_id: order.id,
        handler: async (response) => {
          // 3. Send payment details to backend for verification
          const verifyRes = await fetch(
            "http://localhost:5000/api/verify-payment",
            {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                orderCreationId: order.id,
                razorpayPaymentId: response.razorpay_payment_id,
                razorpayOrderId: response.razorpay_order_id,
                razorpaySignature: response.razorpay_signature,
              }),
            }
          );

          const result = await verifyRes.json();

          if (result.success) {
            alert("Payment successful!");
            // You can redirect or update UI here
          } else {
            alert(result.msg || "Payment verification failed");
          }
        },
        prefill: {
          name: "",
          email: "",
          contact: "",
        },
        theme: {
          color: "#0f172a",
        },
      };

      const rzp = new window.Razorpay(options);

      rzp.on("payment.failed", (res) => {
        console.error(res.error);
        alert(res.error.description || "Payment failed");
      });

      rzp.open();
    } catch (err) {
      console.error(err);
      alert("Something went wrong. Please try again.");
    } finally {
      setLoading(false);
    }
  };

  return (
    <button onClick={handlePay} disabled={loading}>
      {loading ? "Processing..." : `Pay ₹${amount}`}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use the component anywhere:


<PayButton amount={999} productName="Pro Subscription" />

Enter fullscreen mode Exit fullscreen mode

4. How Verification Works

After payment succeeds, Razorpay sends you:

  • orderCreationId
  • razorpayPaymentId
  • razorpayOrderId
  • razorpaySignature

On the backend, we create a signature ourselves using:


HMAC-SHA256(orderCreationId + "|" + razorpayPaymentId, KEY_SECRET)

Enter fullscreen mode Exit fullscreen mode

If our calculated signature matches the one sent by Razorpay, the payment is genuine.

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:

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

We'll need to use the SHA256algorithm, use the razorpayPaymentIdand the orderCreationIdto construct a HMAC hex digest. Then compare the digest with the razorpaySignature. If both are equal, then our payment is verified.

Create a route for verification
Add this POST route to your backend (you can put it in the same server.js or in a routes/payment.js file):

app.post("/api/verify-payment", async (req, res) => {
  try {
    // getting the details back from our front-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",
      process.env.RAZORPAY_KEY_SECRET   // never hardcode the secret
    );

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

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

    // comparing 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({
      success: true,
      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.
You can proceed to make a payment, and if successful, you can view the payment inside your Razorpay Dashboard in the Transactions tab.


And if you click the Pay ₹2,998 button, a pop-up should appear.


Testing checklist

  • Use Razorpay test cards (e.g. 4111 1111 1111 1111)
  • Test both successful and failed payments
  • Confirm the signature verification endpoint rejects tampered data
  • Check the network tab: Key Secret must never appear in any request from the browser

That’s it! All the steps are done. Now try making a payment.
Once it succeeds, you’ll see the transaction in your Razorpay Dashboard → Transactions. In your Admin panel, the order will also appear with status Paid, like this:

Top comments (0)