Integrate phonepe payment gateway in you Nextjs App.

Learn how to seamlessly integrate the PhonePe payment gateway into your Next.js application.

User Avatar

Faisal Husain

Integrate phonepe payment gateway in you Nextjs App. blog image

Technical Architecture

The payment integration follows a structured three-stage approach:

  1. Payment Initiation : Generating a unique transaction payload
  2. Payment Processing : Redirecting to PhonePe's secure payment interface
  3. Payment Verification : Confirming transaction status server-side

To begin, let’s set up a fresh Next.js application. Run the following command in your terminal to create a new Next.js project:

terminal
npx create-next-app@latest
terminal
cd you-app-name

Just normal stuff.

Now create a button which just say Pay ₹1

Step 1 - Payment Initiation

We start by creating a simple payment button that triggers the payment process. In the Home component, the handlePay function is called when the user clicks the button. This function interacts with the server-side initiatePayment action to initiate the payment flow.

src/app/page.tsx
"use client";
import { initiatePayment } from "@/actions/initiatePayment";
import { useRouter } from "next/navigation";
 
export default function Home() {
  const router = useRouter();
 
  const handlePay = async (data: number) => {
    try {
      const result = await initiatePayment(data);
      if (result) {
        router.push(result.redirectUrl); // Redirect to status check page
      }
    } catch (error) {
      console.error("Error processing payment:", error);
    }
  };
 
  return (
    <div className="flex items-center justify-center h-screen">
      <button
        onClick={() => handlePay(1)} // Trigger payment for ₹1
      >
        Pay ₹1
      </button>
    </div>
  );
}

Step 2 - Payment Processing

Now the interesting thing is initiatePayment actions

src/actions/initiatePayment.ts
"use server";
import { v4 as uuidv4 } from "uuid";
import sha256 from "crypto-js/sha256";
import axios from "axios";
 
export async function initiatePayment(data: number) {
  const transactionId = "Tr-" + uuidv4().toString().slice(-6); // Here I am generating random id you can send the id of the product you are selling or anything else.
 
  const payload = {
    merchantId: process.env.NEXT_PUBLIC_MERCHANT_ID,
    merchantTransactionId: transactionId,
    merchantUserId: "MUID-" + uuidv4().toString().slice(-6),
    amount: 100 * data, // Amount is converted to the smallest currency unit (e.g., cents/paise) by multiplying by 100. For example, $1 = 100 cents or ₹1 = 100 paise.
    redirectUrl: `${process.env.NEXT_URL}/status/${transactionId}`,
    redirectMode: "REDIRECT",
    callbackUrl: `${process.env.NEXT_URL}/status/${transactionId}`,
    paymentInstrument: {
      type: "PAY_PAGE",
    },
  };
 
  const dataPayload = JSON.stringify(payload);
  const dataBase64 = Buffer.from(dataPayload).toString("base64");
 
  const fullURL = dataBase64 + "/pg/v1/pay" + process.env.NEXT_PUBLIC_SALT_KEY;
  const dataSha256 = sha256(fullURL).toString();
 
  const checksum = dataSha256 + "###" + process.env.NEXT_PUBLIC_SALT_INDEX;
 
  const UAT_PAY_API_URL = `${process.env.NEXT_PUBLIC_PHONE_PAY_HOST_URL}/pg/v1/pay`;
 
  try {
    const response = await axios.post(
      UAT_PAY_API_URL,
      { request: dataBase64 },
      {
        headers: {
          accept: "application/json",
          "Content-Type": "application/json",
          "X-VERIFY": checksum,
        },
      },
    );
 
    return {
      redirectUrl: response.data.data.instrumentResponse.redirectInfo.url,
      transactionId: transactionId,
    };
  } catch (error) {
    console.error("Error in server action:", error);
    throw error;
  }
}

Now setting up .env

NEXT_URL= http://localhost:3000
NEXT_PUBLIC_PHONE_PAY_HOST_URL=https://api-preprod.phonepe.com/apis/pg-sandbox
NEXT_PUBLIC_MERCHANT_ID=PGTESTPAYUAT86
NEXT_PUBLIC_SALT_KEY= 96434309-7796-489d-8924-ab56988a6076
NEXT_PUBLIC_SALT_INDEX=1

The following environment variables are provided by PhonePe for testing purposes in their sandbox. Once you transition to production, PhonePe will provide you with production keys and URLs.

You can use this card details and OTP to complete the payment

Global Bank
chip
4242 4242 4242 4242
Card Holder
John Doe
Expires
12/25
Authorized Signature
Bank Info
Your Bank Details
CVV
123

This is test credential provided by phonepe you can find it here Phonepe Documentation
(I have created these components just for fun purpose 👨‍💻)

Now Step 1 and Step 2 are done . Let’s Move on to Step 3

Step 3 - Payment Verification

Now create a new route src/app/status/[id]/page.tsx, because if you see in the initiatePayment function I have given redirectUrl

src/app/status/[id]/page.tsx
'use client'
import { useParams } from 'next/navigation'
import { useState, useEffect } from 'react'
import axios, { AxiosError } from 'axios'
import Link from 'next/link'
 
const StatusPage = () => {
  const params = useParams()
  const [status, setStatus] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
 
  useEffect(() => {
    const fetchStatus = async () => {
      try {
        const response = await axios.post('/api/status', { id: params?.id })
        setStatus(response.data)
      } catch (error) {
        const errorMessage =
          error instanceof AxiosError
            ? error.response?.data?.message || error.message
            : 'Something went wrong. Please contact the website owner.'
        setError(errorMessage)
      } finally {
        setLoading(false)
      }
    }
 
    if (params?.id) {
      fetchStatus()
    }
  }, [params?.id])
 
  return (
    <div className="flex items-center justify-center h-screen ">
      <div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
        {loading ? (
          <div className="flex justify-center items-center space-x-2">
            <div className="w-8 h-8 border-4 border-t-4 border-gray-300 border-t-transparent rounded-full animate-spin"></div>
            <p className="text-lg text-gray-700">Loading...</p>
          </div>
        ) : error ? (
          <div className="text-center">
            <p className="text-red-500 text-lg">{error}</p>
            <button
              className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700"
              onClick={() => {
                setLoading(true)
                setError(null)
                setStatus(null)
              }}
            >
              Retry
            </button>
          </div>
        ) : (
          <div className="text-center">
            <h1 className="text-2xl font-bold text-gray-800">
              Status: {status ? status : 'No status available'}
            </h1>
            <Link href="/" passHref>
              <button className="mt-4 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-700">
                Back
              </button>
            </Link>
          </div>
        )}
      </div>
    </div>
  )
}
 
export default StatusPage

Now create an api to verify transaction

src/app/api/status/route.ts
import { NextRequest, NextResponse } from "next/server";
import sha256 from "crypto-js/sha256";
import axios from "axios";
 
 
export async function POST(req: NextRequest) : Promise<NextResponse>{
    try {
        const { id} = await req.json();
        const merchantId = process.env.NEXT_PUBLIC_MERCHANT_ID;
        const transactionId = id;
        const st = `/pg/v1/status/${merchantId}/${transactionId}` + process.env.NEXT_PUBLIC_SALT_KEY;
        const dataSha256 = sha256(st).toString();
        const checksum = dataSha256 + "###" + process.env.NEXT_PUBLIC_SALT_INDEX;
        console.log(checksum);
 
        const options = {
            method: "GET",
            url: `${process.env.NEXT_PUBLIC_PHONE_PAY_HOST_URL}/pg/v1/status/${merchantId}/${transactionId}`,
            headers: {
                accept: "application/json",
                "Content-Type": "application/json",
                "X-VERIFY": checksum,
                "X-MERCHANT-ID": `${merchantId}`,
            },
        };
 
        const response = await axios.request(options);
        console.log("r===", response);
 
        if (response.data.code === "PAYMENT_SUCCESS") {            
            return new NextResponse(response.data.code, { status: 200 });
        } else {
            return new NextResponse("FAIL", { status: 200 });
        }
    } catch (error) {
        console.error("Error in payment status check:", error);
 
        return NextResponse.redirect("https://faisalhusa.in", {
            status: 301,
        });
    }
}

Congratulations! 🎊
You've successfully integrated the PhonePe payment gateway into your Next.js app. Thank you for reading through the guide. If you encounter any issues or have further questions, feel free to reach out to me at faisalhusain1320@gmail.com . The code and official documentation for PhonePe are provided below for your reference.


Resources

  1. PhonePe UAT Testing Documentation
  2. PhonePe Pay API Reference
  3. PhonePe Example Code on GitHub
  4. Live Link

Feel free to explore these resources for more detailed information and updates. Happy coding!

Want to hire me as a freelancer? Let's discuss.
Drop a message and let's discuss
Drop in your email ID and I will get back to you.
HomeAboutProjectsBlogsHire MeCrafts