*Integrating Razorpay with Next.js

January 3, 2024

I was recently building a project that required payment processing, but when I tried to integrate Razorpay, I realized most available tutorials were outdated and still focused on the legacy Pages router. I spent some time adapting the implementation to work correctly with Server Components and the modern App Router architecture, and here is the configuration that finally worked for me.

0. Prerequisites

Install the Razorpay NPM package:

npm install razorpay

1. Create API Route

Create an API route in app/api/order/create/route.ts:

import { NextResponse } from "next/server"
import Razorpay from "razorpay"
import { v4 as uuid } from "uuid"

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

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const totalAmount = Number(searchParams.get("amount")) // in paisa

  const amount = totalAmount * 100
  const options = {
    amount: amount.toString(),
    currency: "INR",
    receipt: uuid(),
  }

  const order = await instance.orders.create(options)
  return NextResponse.json({ message: "success", order })
}

Create one more route in app/api/order/verify/route.ts:

import { NextResponse } from "next/server"
import Razorpay from "razorpay"
import crypto from "crypto"
import Order from "@/models/OrderModel"
import { v4 as uuid } from "uuid"
import { connectDB } from "@/lib/mongodb"

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

export async function POST(req, res) {
  const { razorpayOrderId, razorpaySignature, razorpayPaymentId, email } =
    await req.json()
  const body = razorpayOrderId + "|" + razorpayPaymentId

  const expectedSignature = crypto
    .createHmac("sha256", process.env.RAZORPAY_KEY_SECRET)
    .update(body.toString())
    .digest("hex")

  const isAuthentic = expectedSignature === razorpaySignature

  if (!isAuthentic) {
    return NextResponse.json(
      { message: "invalid payment signature", error: true },
      { status: 400 }
    )
  }

  // connect db and update data
  await connectDB()
  await Order.findOneAndUpdate({ email: email }, { hasPaid: true })

  return NextResponse.json(
    { message: "payment success", error: false },
    { status: 200 }
  )
}

2. Add Razorpay Script to Root Layout

Add the Razorpay script to the root layout in app/layout.tsx:

import Script from "next/script"
import "./globals.css"

export default function RootLayout({ children }) {
  return (
    <>
      <html lang="en">
        <body>{children}</body>
      </html>
      <Script src="https://checkout.razorpay.com/v1/checkout.js" />
    </>
  )
}

3. Create Payment Button Component

Create a payment button component in app/components/PaymentButton.tsx:

"use client"
import React, { Suspense, useState } from "react"
import { useRouter } from "next/navigation"
import Loading from "@/app/loading"
import { useSession } from "next-auth/react"
import { Button, buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils"

const PaymentButton = ({ amount }) => {
  const { userData } = useSession()
  const router = useRouter()
  const [isLoading, setIsLoading] = useState(false)

  const makePayment = async () => {
    setIsLoading(true)

    // make an endpoint to get this key
    const key = "rzp_test_M******Pw5***n"
    const data = await fetch("/api/order/create?amount=" + amount)
    const { order } = await data?.json()
    const options = {
      key: key,
      name: userData.user?.email,
      currency: order.currency,
      amount: order.amount,
      order_id: order.id,
      modal: {
        ondismiss: function () {
          setIsLoading(false)
        },
      },
      handler: async function (response) {
        const data = await fetch("/api/order/verify", {
          method: "POST",
          body: JSON.stringify({
            razorpayPaymentId: response.razorpay_payment_id,
            razorpayOrderId: response.razorpay_order_id,
            razorpaySignature: response.razorpay_signature,
            email: userData.user?.email,
          }),
        })

        const res = await data.json()
        if (res?.error === false) {
          // redirect to success page
          router.push("/success")
        }
      },
      prefill: {
        email: userData.user?.email,
      },
    }

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

    paymentObject.on("payment.failed", function (response) {
      alert("Payment failed. Please try again.")
      setIsLoading(false)
    })
  }

  return (
    <>
      <Suspense fallback={<Loading />}>
        <div className="">
          <Button
            className={cn(buttonVariants({ size: "lg" }))}
            disabled={isLoading}
            onClick={makePayment()}
          >
            Pay Now
          </Button>
        </div>
      </Suspense>
    </>
  )
}

export default PaymentButton

That's itt!

razorpay nextjs
Integrating Razorpay with Next.js | Salman Ansari