
Razorpay
- Atul
- Integration
- January 30, 2025
Table of Contents
Razorpay
Well to get started with W3 monetary systems, I guess I should try seeing how Razorpay is working first.
Goal: Integrate Razorpay into a NextJS Project:
Getting started with the Docs:
Here is the Demo Razorpay has provided: https://razorpay.com/demo/
Docs: https://razorpay.com/docs/payments/payment-gateway/web-integration/standard/
Prerequisites:
You can start accepting payments from customers on your website using the Razorpay Web Standard Checkout. Razorpay has developed the Standard Checkout method and manages it. You can configure payment methods, orders, company logo and also select custom colour based on your convenience.
They are supporting many platforms!
NodeJS:
Doc link: https://razorpay.com/docs/payments/server-integration/nodejs/
Dependencies: You must use Node.js v22.2 or higher. Know more about the latest Node.js versions
npm i razorpay
Payment gateway integration:
There they have provided a sample NodeJS app: https://razorpay.com/docs/build/browser/assets/images/razorpay-node-sample-project.zip
Download the zip and extract it, then inside it:
pnpm i
node app.js
This will open.
After just running the project, lets see the actual flow.
Before you proceed:
- Create a Razorpay account.
This KYC has to be done.
Business Address
- Building Number, Name, Street
- Pincode
- City
- State
Bank Details
- Account Number
- IFSC Code
Business Policy
Identity Proof
- Aadhaar / Passport / Voter Id
Basic Details
- What’s your mobile number?
- What’s your name?
- Where do you plan to use Razorpay for collecting payments?
- What’s your business type?
- What’s your personal PAN number?
- What’s the name on your PAN?
- What’s your brand name?
- What’s your email?
This is the data you will be requiring for KYC.
- Log in to the Dashboard and generate the API keys in test mode.
- Know about the Razorpay Payment Flow and follow these integration steps:
1. Build Integration
var instance = new Razorpay({
key_id: 'YOUR_KEY_ID',
key_secret: 'YOUR_KEY_SECRET',
});
Create 2 env variables:
RAZORPAY_KEY_ID=
RAZORPAY_KEY_SECRET=
NextJS Project:
Blog Post
Github: git@github.com:piyushyadav1617/razorpay-next.git
So, I am following the blog:
1. Create an Order
Creating the route: app/api/order/route.ts
import Razorpay from "razorpay";
import { NextRequest, NextResponse } from "next/server";
const razorpay = new Razorpay({
key_id: process.env.key_id!,
key_secret: process.env.key_secret,
});
export async function POST(request: NextRequest) {
const { amount, currency } = (await request.json()) as {
amount: string;
currency: string;
};
var options = {
amount: amount,
currency: currency,
receipt: "rcp1",
};
const order = await razorpay.orders.create(options);
console.log(order);
return NextResponse.json({ orderId: order.id }, { status: 200 });
}
This API will be used to create an order on the server side and it will send the orderId to the client. This order id will be used in the checkout.
2. Verfication
Create a route: app/api/verify/route.ts
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
const generatedSignature = (
razorpayOrderId: string,
razorpayPaymentId: string
) => {
const keySecret = process.env.key_secret;
if (!keySecret) {
throw new Error(
"Razorpay key secret is not defined in environment variables."
);
}
const sig = crypto
.createHmac("sha256", keySecret)
.update(razorpayOrderId + "|" + razorpayPaymentId)
.digest("hex");
return sig;
};
export async function POST(request: NextRequest) {
const { orderCreationId, razorpayPaymentId, razorpaySignature } =
await request.json();
const signature = generatedSignature(orderCreationId, razorpayPaymentId);
if (signature !== razorpaySignature) {
return NextResponse.json(
{ message: "payment verification failed", isOk: false },
{ status: 400 }
);
}
return NextResponse.json(
{ message: "payment verified successfully", isOk: true },
{ status: 200 }
);
}
This checks if the signature sent from Razorpay and the signature we generate using key_secret, order id and payment id are same or not.
3. Client side:
Create order function
const createOrderId = async () => {
try {
const response = await fetch("/api/order", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: parseFloat(amount) * 100,
}),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
return data.orderId;
} catch (error) {
console.error("There was a problem with your fetch operation:", error);
}
};
This utilizes /api/order API endpoint
Payment Checkout function:
const processPayment = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const orderId: string = await createOrderId();
const options = {
key: process.env.key_id,
amount: parseFloat(amount) * 100,
currency: currency,
name: "name",
description: "description",
order_id: orderId,
handler: async function (response: any) {
const data = {
orderCreationId: orderId,
razorpayPaymentId: response.razorpay_payment_id,
razorpayOrderId: response.razorpay_order_id,
razorpaySignature: response.razorpay_signature,
};
const result = await fetch("/api/verify", {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
const res = await result.json();
if (res.isOk) alert("payment succeed");
else {
alert(res.message);
}
},
prefill: {
name: name,
email: email,
},
theme: {
color: "#3399cc",
},
};
const paymentObject = new window.Razorpay(options);
paymentObject.on("payment.failed", function (response: any) {
alert(response.error.description);
});
paymentObject.open();
} catch (error) {
console.log(error);
}
};
This utilizes the /api/verfiy to verify the keys.
Finally, I have created a component for getting data from user and making payment:
"use client";
import { useState } from "react";
import Script from "next/script";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
// Type for environment variables
declare global {
interface Window {
Razorpay: any;
}
}
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
// Make sure to create a .env.local file with these variables
const RAZORPAY_KEY_ID = process.env.RAZORPAY_KEY_ID;
interface PaymentModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function PaymentModal({ open, onOpenChange }: PaymentModalProps) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [loading, setLoading] = useState(false);
const amount = 10000; // Fixed amount
const createOrderId = async () => {
try {
const response = await fetch("/api/order", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: amount * 100, // Convert to paisa
}),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
return data.orderId;
} catch (error) {
console.error("There was a problem with your fetch operation:", error);
throw error;
}
};
const processPayment = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
try {
if (!RAZORPAY_KEY_ID) {
throw new Error("Razorpay key is not configured");
}
const orderId = await createOrderId();
if (!orderId) {
throw new Error("Failed to create order");
}
const options = {
key: RAZORPAY_KEY_ID,
amount: amount * 100,
currency: "INR",
name: "VastuTeq Subscription",
description: "Premium Subscription",
order_id: orderId,
handler: async function (response: any) {
const data = {
orderCreationId: orderId,
razorpayPaymentId: response.razorpay_payment_id,
razorpayOrderId: response.razorpay_order_id,
razorpaySignature: response.razorpay_signature,
};
const result = await fetch("/api/verify", {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
const res = await result.json();
if (res.isOk) {
alert("Payment succeeded");
onOpenChange(false); // Close modal on success
} else {
alert(res.message);
}
},
prefill: {
name: name,
email: email,
},
theme: {
color: "#3399cc",
},
} as const;
const paymentObject = new window.Razorpay(options);
paymentObject.on("payment.failed", function (response: any) {
alert(response.error.description);
});
paymentObject.open();
} catch (error) {
console.error(error);
alert(
"Payment failed: " +
(error instanceof Error ? error.message : "Unknown error")
);
} finally {
setLoading(false);
}
};
return (
<>
<Script
id="razorpay-checkout-js"
src="https://checkout.razorpay.com/v1/checkout.js"
/>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Subscribe to VastuTeq</DialogTitle>
</DialogHeader>
<form onSubmit={processPayment} className="space-y-4">
<div className="space-y-1">
<Label>Full name</Label>
<Input
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label>Email</Label>
<Input
type="email"
placeholder="user@gmail.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label>Amount</Label>
<Input
type="text"
value={`₹${amount.toLocaleString()}`}
disabled
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Processing..." : "Pay Now"}
</Button>
</form>
</DialogContent>
</Dialog>
</>
);
}