Integrating PhonePe Payment Gateway in Node.js


In this blog, we will walk through the integration of PhonePe Payment Gateway in a Node.js application. This guide includes step-by-step instructions to initiate payments and handle payment status callbacks using PhonePe’s APIs.
Prerequisites
Before starting, ensure you have the following:
Node.js installed on your system.
A PhonePe merchant account with API credentials (
merchantId
andmerchantSecret
).A MongoDB database to store payment-related information.
Step 1: Setup Your Node.js Project
Initialize a new Node.js project and install the required dependencies:
npm init -y
npm install express mongoose body-parser axios crypto uuid
Step 2: Create Database Models
Define one Mongoose model: payments (MasterClassPayment
).
const mongoose = require('mongoose');
const masterClassPaymentSchema = new mongoose.Schema({
masterClassId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'MasterClass',
},
name: {
type: String,
},
email: {
type: String,
},
phone: {
type: String,
},
amount: {
type: Number,
},
transactionId: {
type: String,
unique: true
},
merchantTransactionId: {
type: String,
unique: true
},
status: {
type: String,
enum: ['PENDING', 'SUCCESS', 'FAILED'],
default: 'PENDING'
},
paymentResponse: {
type: Object
},
createdAt: {
type: Date,
default: Date.now
}
});
const MasterClassPayment = mongoose.model('MasterClassPayment', masterClassPaymentSchema);
module.exports = MasterClassPayment;
Step 3: Payment Initiation API
This API initiates a payment by sending the request to the PhonePe Payment Gateway.
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');
const phonePeConfig = {
merchantId: '',
merchantSecret: '',
paymentUrl: 'https://api.phonepe.com/v3/transaction/initiate',
};
Step 4. API
router.post('/master-classes/:slug/initiate-payment', async (req, res) => {
try {
const { slug } = req.params;
const { name, email, phone, amount } = req.body;
if (!amount || !slug) {
return res.status(400).json({
success: false,
message: 'Amount and slug are required'
});
}
const amountInPaise = Math.round(parseFloat(amount) * 100);
console.log('Amount in paise:', amountInPaise);
if (amountInPaise < 100) {
return res.status(400).json({
success: false,
message: 'Amount must be at least 1 rupee'
});
}
const masterClass = await MasterClass.findOne({ slug });
if (!masterClass) {
return res.status(404).json({
success: false,
message: 'MasterClass not found'
});
}
const merchantTransactionId = generateTransactionId();
const transactionId = `TXN_${uuidv4()}`;
console.log('Generated merchantTransactionId:', merchantTransactionId);
const payment = new MasterClassPayment({
masterClassId: masterClass._id,
name,
email,
phone,
amount: amountInPaise / 100,
transactionId,
merchantTransactionId,
status: 'PENDING'
});
await payment.save();
const payload = {
merchantId: phonePeConfig.merchantId,
merchantTransactionId,
merchantUserId: phone ? `MUID_${phone}` : `MUID_${Date.now()}`,
name: name || '',
amount: amountInPaise,
redirectUrl: 'http://localhost:300/api/v1/master-class/mc/payment-status',
redirectMode: 'POST',
mobileNumber: phone || '',
paymentInstrument: {
type: 'PAY_PAGE'
}
};
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString('base64');
const keyIndex = 1;
const string = payloadBase64 + '/pg/v1/pay' + phonePeConfig.merchantSecret;
const sha256 = crypto.createHash('sha256').update(string).digest('hex');
const checksum = `${sha256}###${keyIndex}`;
const response = await axios.post(
'https://api.phonepe.com/apis/hermes/pg/v1/pay',
{ request: payloadBase64 },
{
headers: {
'Content-Type': 'application/json',
'X-VERIFY': checksum
}
}
);
return res.status(200).json({
success: true,
message: 'Payment initiated',
data: response.data,
transactionId,
merchantTransactionId
});
} catch (error) {
console.error('Payment Initiation Error:', error);
return res.status(500).json({
success: false,
message: 'Payment initiation failed',
error: error.message
});
}
});
router.post('/mc/payment-status', async (req, res) => {
try {
const { transactionId } = req.body;
if (!transactionId) {
return res.status(400).json({ success: false, message: 'transactionId is required' });
}
// Query using PhonePe's merchantTransactionId
const paymentRecord = await MasterClassPayment.findOne({
merchantTransactionId: transactionId,
});
if (!paymentRecord) {
return res.status(404).json({
success: false,
message: 'Payment record not found for the provided transactionId',
});
}
// Generate checksum for PhonePe status API
const endpointPath = `/pg/v1/status/${phonePeConfig.merchantId}/${transactionId}`;
const stringToSign = endpointPath + phonePeConfig.merchantSecret;
const sha256 = crypto.createHash('sha256').update(stringToSign).digest('hex');
const checksum = `${sha256}###1`;
// Make API request to PhonePe for status
const phonePeResponse = await axios.get(
`https://api.phonepe.com/apis/hermes${endpointPath}`,
{
headers: {
'Content-Type': 'application/json',
'X-VERIFY': checksum,
'X-MERCHANT-ID': phonePeConfig.merchantId,
},
}
);
const phonePeData = phonePeResponse.data;
// Handle Payment Success
if (phonePeData.code === 'PAYMENT_SUCCESS') {
const updatedPayment = await MasterClassPayment.findOneAndUpdate(
{ merchantTransactionId: transactionId },
{
status: 'SUCCESS',
paymentResponse: phonePeData,
},
{ new: true }
);
const masterClass = await MasterClass.findByIdAndUpdate(
updatedPayment.masterClassId,
{
$push: {
registration: {
name: updatedPayment.name,
email: updatedPayment.email,
phone: updatedPayment.phone,
payment: {
transactionId: updatedPayment.transactionId,
merchantTransactionId: updatedPayment.merchantTransactionId,
status: 'SUCCESS',
amount: updatedPayment.amount,
},
},
},
},
{ new: true }
);
// Render a thank-you HTML page
return res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Successful</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 50px;
}
.container {
max-width: 600px;
margin: auto;
border: 1px solid #ddd;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: green;
}
p {
font-size: 18px;
margin: 10px 0;
}
a {
text-decoration: none;
color: #fff;
background-color: #007bff;
padding: 10px 20px;
border-radius: 5px;
display: inline-block;
margin-top: 20px;
}
a:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<h1>Thank You!</h1>
<p>Your payment of <strong>₹${(updatedPayment.amount).toFixed(2)}</strong> was successful.</p>
<p>Transaction ID: <strong>${updatedPayment.transactionId}</strong></p>
<p>Merchant Transaction ID: <strong>${updatedPayment.merchantTransactionId}</strong></p>
<p>You have been successfully registered for the master class.</p>
<a href="https://prayug.com/">Back to Home</a>
</div>
</body>
</html>
`);
}
// Handle Payment Failure
await MasterClassPayment.findOneAndUpdate(
{ merchantTransactionId: transactionId },
{ status: 'FAILED', paymentResponse: phonePeData }
);
return res.status(200).json({
success: false,
message: 'Payment not successful',
code: phonePeData.code,
});
} catch (error) {
// Handle specific HTTP errors
if (error.response) {
return res.status(error.response.status).json({
success: false,
message: error.response.data.message || 'Error from PhonePe API',
error: error.response.data,
});
}
// Generic error response
return res.status(500).json({
success: false,
message: 'Error while checking payment status',
error: error.message,
});
}
});
Step 5: Testing the APIs
Start your Node.js server:
node server.js
Use a tool like Postman to test the
/master-classes/:slug/initiate-payment
endpoint.Simulate the payment status callback using
/mc/payment-status
.
Conclusion
This guide helps you set up a complete integration with PhonePe Payment Gateway in your Node.js application. By following these steps, you can handle payment initiation and callbacks securely and efficiently.
Subscribe to my newsletter
Read articles from Chandan kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Chandan kumar
Chandan kumar
Hello, World! 👋 I'm Chandan Kumar, a passionate and results-driven MEAN stack web developer with a strong background in designing and implementing web applications that deliver exceptional user experiences. I'm dedicated to staying at the forefront of web development trends and technologies to create cutting-edge solutions for my clients. My Expertise: MEAN Stack Development: I specialize in building robust web applications using MongoDB, Express.js, Angular, and Node.js. This full-stack approach allows me to create seamless, end-to-end solutions that meet and exceed client expectations. Front-end Excellence: I have a keen eye for UI/UX design and a deep understanding of front-end technologies such as HTML5, CSS3, and JavaScript. I leverage these skills to craft engaging, responsive, and user-friendly interfaces. Back-end Proficiency: With extensive experience in server-side scripting, API development, and database management, I ensure that the applications I build are not only visually appealing but also highly performant and secure. Problem Solver: I thrive on challenges and enjoy solving complex problems. Whether it's optimizing code for efficiency or troubleshooting issues, I'm committed to finding innovative solutions. My Mission: My mission is to create digital experiences that make a meaningful impact. I'm passionate about collaborating with teams and clients to turn ideas into reality. I believe that technology can empower businesses and individuals alike, and I'm excited to be a part of that journey. Let's Connect: I'm always open to networking and exploring opportunities for collaboration. Whether you're interested in discussing a potential project, sharing insights, or simply connecting with fellow professionals in the tech industry, feel free to reach out. Let's connect and start a conversation! Contact Information: 📩 Email: chandanku845415@gmail.com 📱 LinkedIn: developerchandan 🌐 Portfolio: developerchandan.github.io ✍️ Medium: developerchandan Thank you for visiting my profile, and I look forward to connecting with you!