Seamless Transactions: A Comprehensive Guide to PhonePe Payment Gateway API Integration with Node.js

Arijit DasArijit Das
4 min read

In the ever-evolving landscape of online transactions, seamless payment experiences are crucial for the success of any web application. Integrating a reliable payment gateway is a fundamental step towards achieving this, and PhonePe, with its robust API, offers an excellent solution. In this guide, we'll walk you through the process of integrating the PhonePe Payment Gateway API with Node.js, enabling you to enhance your web application's payment capabilities.

Firstly, create a PhonePe merchant account and store the MerchantId, key Index and salt key. We will use this during making requests on PhonePe APIs.

Proceeding by the assumption that we have already created an account (PhonePe merchant account), we need to first understand the working of the PhonePe APIs and a brief overview of it.

Let's create our express server to begin the work with.

const express = require('express')
const app = express()
const cors = require('cors')
const dotenv = require('dotenv')
dotenv.config({ path: "config.env" })

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

const PORT = 5000
app.listen(PORT, () =>
  console.log(`Server started in development mode on port ${PORT}`)
)

Here, we have created a server using Express and hosted it on port 5000.

In the config.env (.env file), specify the SALT KEY and the MERCHANT ID. We will use it in our further PhonePe API functions without revealing the actual value every time in the code.

SALT_KEY = "********-****-****-****-************"
MERCHANT_ID = "*******************"

For a successful payment approval, we will use PhonePe's two APIs; one for submitting the payment request and the next API for checking the Payment status with the merchant transaction ID value.

The newPayment function:

  • The fields we will need to request the payment initiation API are already stated in the JSON variable data in the function.

  • Next, we have to encode the API link to base64 and add the key index value with the hashed sha256 value to create the checksum value for the X-Verify field.

  • Make the API request to PhonePe and retrieve the response from it. From the response, we will get the redirection link to the Payment Gateway page from where we can initiate our payment.

  • API to call: https://api.phonepe.com/apis/hermes/pg/v1/pay

  • A sample of the response is provided below. Upon successful initiation, we will redirect to the URL specified in the data.instrumentResponse.redirectInfo.url.

  •   {
        "success": true,
        "code": "PAYMENT_INITIATED",
        "message": "Payment Iniiated",
        "data": {
          "merchantId": "*********",
          "merchantTransactionId": "M**********",
          "instrumentResponse": {
            "type": "PAY_PAGE",
            "redirectInfo": {
              "url": "https://mercury-uat.phonepe.com/transact?token=*********",
              "method": "GET"
            }
          }
        }
      }
    

The checkStatus function:

const crypto =  require('crypto');
const axios = require('axios');
require("dotenv").config();

const newPayment = async (req, res) => {
    try {
        const merchantTransactionId = 'M' + Date.now();
        const {user_id, price, phone, name} = req.body;
        const data = {
            merchantId: process.env.MERCHANT_ID,
            merchantTransactionId: merchantTransactionId,
            merchantUserId: 'MUID' + user_id,
            name: name,
            amount: price * 100,
            redirectUrl: `http://localhost:3001/api/v1/status/${merchantTransactionId}`,
            redirectMode: 'POST',
            mobileNumber: phone,
            paymentInstrument: {
                type: 'PAY_PAGE'
            }
        };
        const payload = JSON.stringify(data);
        const payloadMain = Buffer.from(payload).toString('base64');
        const keyIndex = 2;
        const string = payloadMain + '/pg/v1/pay' + process.env.SALT_KEY;
        const sha256 = crypto.createHash('sha256').update(string).digest('hex');
        const checksum = sha256 + '###' + keyIndex;

        const prod_URL = "https://api.phonepe.com/apis/hermes/pg/v1/pay"
        const options = {
            method: 'POST',
            url: prod_URL,
            headers: {
                accept: 'application/json',
                'Content-Type': 'application/json',
                'X-VERIFY': checksum
            },
            data: {
                request: payloadMain
            }
        };

        axios.request(options).then(function (response) {
            return res.redirect(response.data.data.instrumentResponse.redirectInfo.url)
        })
        .catch(function (error) {
            console.error(error);
        });

    } catch (error) {
        res.status(500).send({
            message: error.message,
            success: false
        })
    }
}

const checkStatus = async(req, res) => {
    const merchantTransactionId = req.params['txnId']
    const merchantId = process.env.MERCHANT_ID
    const keyIndex = 2;
    const string = `/pg/v1/status/${merchantId}/${merchantTransactionId}` + process.env.SALT_KEY;
    const sha256 = crypto.createHash('sha256').update(string).digest('hex');
    const checksum = sha256 + "###" + keyIndex;

    const options = {
    method: 'GET',
    url: `https://api.phonepe.com/apis/hermes/pg/v1/status/${merchantId}/${merchantTransactionId}`,
    headers: {
        accept: 'application/json',
        'Content-Type': 'application/json',
        'X-VERIFY': checksum,
        'X-MERCHANT-ID': `${merchantId}`
    }
    };

    // CHECK PAYMENT STATUS
    axios.request(options).then(async(response) => {
        if (response.data.success === true) {
            console.log(response.data)
            return res.status(200).send({success: true, message:"Payment Success"});
        } else {
            return res.status(400).send({success: false, message:"Payment Failure"});
        }
    })
    .catch((err) => {
        console.error(err);
        res.status(500).send({msg: err.message});
    });
};

module.exports = {
    newPayment,
    checkStatus
}

In the route file, call the two created functions from these two APIs. The first API calls the new payment function which starts the payment initiation. The second API calls the status check function which verifies the checksum value with the appropriate merchant transaction id which is provided as txnId as a parameter.

router.post('/payment', newPayment);
router.post('/status/:txnId', checkStatus);
3
Subscribe to my newsletter

Read articles from Arijit Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arijit Das
Arijit Das

Exploring Backend Web Development and Database Security GitHub : https://github.com/arijit2002 Email : das10arijit@gmail.com Web Dev Stack: Languages: NodeJS (ExpressJS), Python (Flask), Java (SpringBoot) Version Control: Git (GitHub, GitLab) Database: MongoDB, Firestore, Oracle, MySQL Cloud: AWS Hosting: Heroku, Render, Netlify, Firebase