Skip to main content

PhonePe Payment Gateway Integration

· 8 min read
Kamlesh
Quality Assurance @TestKarts

This tutorial will guide you through the process of integrating the PhonePe Payment Gateway in your Node.js backend and React frontend. We'll use the provided backend code for payment processing and show how to build the frontend with React, React Router DOM, React Bootstrap, and Axios.

Prerequisites:

  • Node.js installed
  • React app set up with React Router DOM
  • Express server setup (for backend recommended)
  • Basic knowledge of JavaScript, React, and REST APIs

Note- No Need of PhonePe Merchant account

Backend Integration with PhonePe Payment Gateway

Step 1: Install Required Packages

In your backend directory, you will need to install the following packages:

npm install axios crypto

These dependencies will help with making HTTP requests and generating cryptographic hashes.

Step 2: Backend Code Implementation

The backend will handle the generation of the order, checksum, and interaction with the PhonePe API.

2.1: Order ID Generation

First, you need a function to generate unique order IDs. This ensures each payment transaction has a unique identifier. You can use any method or npm library to generate it.

const generateOrderId = () => {
const timestamp = Date.now(); // Get the current timestamp
const randomNumber = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); // Random 4-digit number
return `ord-${timestamp}${randomNumber}`; // Order ID format
};

module.exports = generateOrderId;
2.2: Checksum Generation

For the payment request, you need a checksum generated from the payload and your secret key (provided by PhonePe).

const { createHash } = require('crypto');

const generateChecksum = async (payload, endpoint, SALT_KEY) => {
const stringToHash = payload + endpoint + SALT_KEY;
const sha256Value = createHash('sha256').update(stringToHash).digest('hex');
return `${sha256Value}###1`; // Key index 1
};

module.exports = { generateChecksum };
2.3: Payment Order Creation

Step 1: Add Environment Variables

In order to safely store sensitive data like MERCHANT_ID, PAYMENT_SALT_KEY, and API URLs, we should use a .env file.

In your root project directory, create a .env file and add the following lines:

MERCHANT_ID=PGTESTPAYUAT86
MERCHANT_BASE_URL=https://api-preprod.phonepe.com/apis/pg-sandbox/pg/v1/pay
MERCHANT_STATUS_URL=https://api-preprod.phonepe.com/apis/pg-sandbox/pg/v1/status
PAYMENT_SALT_KEY=96434309-7796-489d-8924-ab56988a6076
FRONTEND_URL=http://localhost:3000 //replace this as per your setup
API=http://localhost:5000/api //replace this as per your setup

This file contains:

  • MERCHANT_ID: The merchant ID provided by PhonePe to identify your account.
  • MERCHANT_BASE_URL: The base URL for making payment requests.
  • MERCHANT_STATUS_URL: The URL to check payment status.
  • PAYMENT_SALT_KEY: The secret key provided by PhonePe, used to generate a checksum for security.
  • FRONTEND_URL: The URL of your frontend, which users will be redirected to after payment.
  • API: The URL of your backend API endpoint for handling callbacks.

Now, implement the route to create a payment order. Here, the server will generate a payment request and interact with PhonePe's API.

src/controller/payment.js
// Import necessary modules
const axios = require('axios'); // Axios for making HTTP requests
const { Buffer } = require('node:buffer'); // Buffer for encoding/decoding data
const { generateChecksum } = require('../utils/paymentHelper'); // Function to generate checksum for validation
const generateOrderId = require('../helpers/orderIdGenerate'); // Function to generate a unique order ID
const Order = require("../models/order"); // Order model to save order details in the database

// Access environment variables for PhonePe Payment Gateway configuration
const PAYMENT_SALT_KEY = process.env.PAYMENT_SALT_KEY; // Salt key used for checksum generation
const MERCHANT_ID = process.env.MERCHANT_ID; // Merchant ID provided by PhonePe
const MERCHANT_BASE_URL = process.env.MERCHANT_BASE_URL; // Base URL for payment initiation
const MERCHANT_STATUS_URL = process.env.MERCHANT_STATUS_URL; // URL to check payment status

// Function to create an order and initiate payment
exports.createOrderPayment = async (req, res) => {
// Destructure the totalAmount from the request body
const { totalAmount } = req.body;

// Generate a unique order ID using the helper function
const orderId = generateOrderId();

// Retrieve the user ID from the request (assuming the user is authenticated)
const userId = req.user._id;

// Convert the total amount to paise (as PhonePe API expects the amount in paise)
const amount = parseFloat(totalAmount) * 100;

// Prepare the payment payload with the necessary details
const paymentPayload = {
merchantId: MERCHANT_ID, // Merchant ID for identification
merchantTransactionId: orderId, // Unique transaction ID for the order
merchantUserId: userId, // User ID to identify the customer
amount: amount, // Total amount in paise (multiplied by 100)
redirectUrl: `${process.env.FRONTEND_URL}/payment/${orderId}`, // URL to redirect the user after payment
callbackUrl: `${process.env.API}`, // Callback URL for the status of the payment
paymentInstrument: { type: 'PAY_PAGE' } // Payment method type (pay page for PhonePe)
};

// Convert the payment payload into a Base64 encoded string
let payloadBase64 = Buffer.from(JSON.stringify(paymentPayload), "utf8").toString("base64");

// Generate the checksum required by PhonePe for security validation
const checksum = await generateChecksum(payloadBase64, '/pg/v1/pay', PAYMENT_SALT_KEY);

// Prepare the options for making the POST request to PhonePe's API
const options = {
method: 'POST', // POST request to initiate the payment
url: MERCHANT_BASE_URL, // The base URL for PhonePe's payment API
headers: {
accept: 'application/json', // Accept JSON response
'Content-Type': 'application/json', // Specify JSON content type
'X-VERIFY': checksum // Include the checksum in the request header for security
},
data: { request: payloadBase64 } // Send the encoded payment payload in the request body
};

// Make the API request to PhonePe's payment gateway
try {
const response = await axios.request(options); // Execute the request
// Check if the response contains the necessary data for redirect URL
if (response.data && response.data.data && response.data.data.instrumentResponse) {
// Send a successful response to the frontend with the URL for redirecting the user
res.status(200).json({
msg: "OK", // Status message indicating success
url: response.data.data.instrumentResponse.redirectInfo.url // Redirect URL for the user to complete payment
});
}
} catch (error) {
// If an error occurs during the request, return a 500 status with the error details
res.status(500).json({ error });
}
};
2.4: Checking Payment Status

After the payment is made, you need to check the status of the payment.

src/controller/payment.js
exports.checkOrderPaymentStatus = async (req, res) => {
const { merchantTransactionId, cartItems } = req.body;
const checksum = await generateChecksum(`/pg/v1/status/${MERCHANT_ID}/`, merchantTransactionId, PAYMENT_SALT_KEY);

const options = {
method: 'GET',
url: `${MERCHANT_STATUS_URL}/${MERCHANT_ID}/${merchantTransactionId}`,
headers: {
accept: 'application/json',
'Content-Type': 'application/json',
'X-VERIFY': checksum
}
};

try {
const response = await axios.request(options);
const payRes = response.data.data;
const amountRs = payRes.amount / 100; // Convert paise to rupees
if (response.data.success) {
const orderItems = cartItems.map((item) => ({
productId: item._id,
payablePrice: item.price,
purchasedQty: item.quantity
}));

const order = new Order({
customOrderId: merchantTransactionId,
user: req.user._id,
items: orderItems,
totalAmount: amountRs,
paymentStatus: 'completed'
});

await order.save();
res.sendSuccessRes(200, 'Order created successfully', order);
} else {
res.sendErrorRes(400, 'Payment failed. Please try again.');
}
} catch (error) {
res.sendErrorRes(400, 'Error checking payment status.', error);
}
};

Frontend Integration with React

In the frontend, we'll build the React components to handle user interactions with the PhonePe payment gateway.

Step 1: Install Dependencies

In your React app, install the required packages:

npm install axios react-router-dom react-bootstrap

Step 2: Payment Form Component

Create a React component to display the payment form and initiate the payment.

import React, { useState } from 'react';
import { Button, Form } from 'react-bootstrap';
import axios from 'axios';
import { useHistory } from 'react-router-dom';

const PaymentForm = () => {
const [amount, setAmount] = useState('');
const history = useHistory();

const handleSubmit = async (e) => {
e.preventDefault();

try {
const response = await axios.post('/api/payment/create', { totalAmount: amount });
if (response.data.url) {
window.location.href = response.data.url; // Redirect to PhonePe payment page
}
} catch (error) {
console.error('Error initiating payment:', error);
}
};

return (
<div>
<h2>Payment</h2>
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formAmount">
<Form.Label>Amount</Form.Label>
<Form.Control
type="number"
placeholder="Enter amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</Form.Group>
<Button variant="primary" type="submit">
Pay with PhonePe
</Button>
</Form>
</div>
);
};

export default PaymentForm;

Step 3: Handling Payment Status

After the user is redirected back from PhonePe, you need to handle the payment status and update the order accordingly.

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';

const PaymentStatus = () => {
const { orderId } = useParams();
const [status, setStatus] = useState(null);

useEffect(() => {
const fetchStatus = async () => {
try {
const response = await axios.post('/api/payment/status', { merchantTransactionId: orderId });
setStatus(response.data);
} catch (error) {
console.error('Error fetching payment status:', error);
}
};

fetchStatus();
}, [orderId]);

return (
<div>
<h2>Payment Status</h2>
{status ? (
<p>{status.paymentStatus}</p>
) : (
<p>Loading payment status...</p>
)}
</div>
);
};

export default PaymentStatus;

Step 4: React Router Setup

Set up routing in App.js to navigate between the payment form and status page.

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import PaymentForm from './components/PaymentForm';
import PaymentStatus from './components/PaymentStatus';

function App() {
return (
<Router>
<Switch>
<Route path="/payment" component={PaymentForm} />
<Route path="/payment/:orderId" component={PaymentStatus} />
</Switch>
</Router>
);
}

export default App;

Conclusion

By following the steps above, you've successfully integrated the PhonePe payment gateway into your Node.js backend and React frontend. The backend handles payment creation, checksum validation, and payment status checks. The frontend includes a form for initiating payments and a page to display the payment status.

Please Support Us

Please support us by subscribing to our YouTube channel at no cost.YouTubeYoutube Link