Nepal Payment Solution (NPS) is one of the leading payment gateways in Nepal, providing secure and reliable payment processing for merchants. In this comprehensive guide, we’ll walk through implementing NPS payment gateway integration using Node.js, covering everything from setup to handling webhooks and payment verification.
Table of Contents
- Introduction to NPS Payment Gateway
- Prerequisites and Setup
- Environment Configuration
- Service Layer Implementation
- Controller Layer Implementation
- Key Functions Explained
- Payment Flow
- Error Handling and Security
- Testing and Best Practices
- Conclusion
Introduction to NPS Payment Gateway
Nepal Payment Solution (NPS) offers a robust payment gateway that supports various payment methods including digital wallets, bank transfers, and online banking. The integration provides a seamless checkout experience for customers while ensuring secure transaction processing.
Key Features:
- Multiple payment instrument support
- Real-time transaction processing
- Webhook notifications
- Secure HMAC-SHA512 signature verification
- Sandbox environment for testing
Prerequisites and Setup
Before starting the integration, ensure you have:
- NPS Merchant Account: Register with Nepal Payment Solution to get your merchant credentials
- Node.js Environment: Version 14 or higher
- Required Dependencies:
npm install axios crypto-js
Environment Configuration
First, set up your environment variables. Here’s the complete configuration needed for NPS integration:
// Environment Variables (.env file)
NPS_USERNAME=your_username
NPS_PASSWORD=your_password
NPS_MERCHANT_ID=your_merchant_id
NPS_MERCHANT_NAME=your_merchant_name
NPS_SECRET_KEY=your_secret_key
// API Endpoints
NPS_PROCESS_ID_URL=https://apisandbox.nepalpayment.com/GetProcessId
NPS_GW_URL=https://gatewaysandbox.nepalpayment.com/Payment/Index
NPS_INSTRUMENT_URL=https://apisandbox.nepalpayment.com/GetPaymentInstrumentDetails
NPS_PAYMENT_STATUS_URL=https://apisandbox.nepalpayment.com/CheckTransactionStatus
NPS_RESPONSE_URL=http://localhost:3000/payment/provider/nps/response
// Client Configuration
CLIENT_URL=http://localhost:3001
Note: Use sandbox URLs for testing. Replace with production URLs when going live.
Service Layer Implementation
Let’s create a comprehensive NPS service that handles all payment operations:
const axios = require('axios');
const CryptoJS = require('crypto-js');
class NpsService {
constructor() {
this.logger = console; // Replace with your preferred logger
}
/**
* Generates HMAC-SHA512 signature for NPS API requests
* @param {Object} payload - The payload object to sign
* @returns {string} - Generated signature
*/
async generateNpsSignature(payload) {
try {
const secretKey = process.env.NPS_SECRET_KEY;
// Generate plain text by sorting keys and joining values
const generatePlainText = (data) => {
return Object.keys(data)
.sort()
.map((key) => data[key])
.join('');
};
// Generate HMAC-SHA512 hash
const generateHMACSHA512 = (plainText, secretKey) => {
const hmac = CryptoJS.HmacSHA512(plainText, secretKey);
return hmac.toString(CryptoJS.enc.Hex).toLowerCase();
};
const plainText = generatePlainText(payload);
const signature = generateHMACSHA512(plainText, secretKey);
return signature;
} catch (error) {
this.logger.error('Error in generateNpsSignature', error);
throw new Error(error.message || 'Failed to generate signature');
}
}
/**
* Generates Basic Authentication headers for NPS API
* @returns {Object} - HTTP headers with authorization
*/
async generateNpsHeaders() {
try {
const npsUsername = process.env.NPS_USERNAME;
const npsPassword = process.env.NPS_PASSWORD;
const base64Cred = Buffer.from(`${npsUsername}:${npsPassword}`).toString('base64');
return {
Authorization: `Basic ${base64Cred}`,
'Content-Type': 'application/json',
};
} catch (error) {
this.logger.error('Error in generateNpsHeaders', error);
throw new Error(error.message || 'Failed to generate headers');
}
}
/**
* Initiates payment with NPS gateway
* @param {string} orderId - Unique order identifier
* @param {string} paymentType - Type of payment (prepaid/cash)
* @returns {Object} - Payment initiation response
*/
async initiatePayment(orderId, paymentType) {
try {
// Validate payment type
const allowedPaymentTypes = ['prepaid', 'cash'];
if (!allowedPaymentTypes.includes(paymentType)) {
throw new Error('Invalid payment type');
}
// Create payment order (implement this based on your business logic)
const paymentOrder = await this.createPaymentOrder(orderId, paymentType, 'nps');
// Prepare payload for Process ID request
const payload = {
MerchantId: process.env.NPS_MERCHANT_ID,
MerchantName: process.env.NPS_MERCHANT_NAME,
Amount: paymentOrder.amount,
MerchantTxnId: paymentOrder.transactionId,
};
// Generate and add signature
const signature = await this.generateNpsSignature(payload);
payload.Signature = signature;
// Request Process ID from NPS
const response = await axios.post(
process.env.NPS_PROCESS_ID_URL,
payload,
{ headers: await this.generateNpsHeaders() }
);
const data = response.data;
// Return payment form data
return {
action: process.env.NPS_GW_URL,
MerchantId: process.env.NPS_MERCHANT_ID,
MerchantName: process.env.NPS_MERCHANT_NAME,
Amount: paymentOrder.amount,
MerchantTxnId: paymentOrder.transactionId,
ProcessId: data.data.ProcessId,
ResponseUrl: process.env.NPS_RESPONSE_URL,
TransactionRemarks: `Order #${paymentOrder.orderId}`,
paymentType: paymentType,
};
} catch (error) {
this.logger.error('Error in initiatePayment', error);
throw new Error(error.message || 'Payment initiation failed');
}
}
/**
* Retrieves available payment instruments from NPS
* @returns {Array} - List of available payment methods
*/
async getNpsInstrument() {
try {
const headers = await this.generateNpsHeaders();
const instrumentPayload = {
MerchantId: process.env.NPS_MERCHANT_ID,
MerchantName: process.env.NPS_MERCHANT_NAME,
};
const instrumentSignature = await this.generateNpsSignature(instrumentPayload);
instrumentPayload.Signature = instrumentSignature;
const response = await axios.post(
process.env.NPS_INSTRUMENT_URL,
instrumentPayload,
{ headers }
);
return response.data.data;
} catch (error) {
this.logger.error('Error in getNpsInstrument', error);
throw new Error(error.message || 'Failed to get payment instruments');
}
}
/**
* Handles payment response from NPS gateway
* @param {Object} query - Query parameters from NPS response
* @returns {Object} - Redirect URL based on payment status
*/
async handleNpsResponse(query) {
try {
const { MerchantTxnId, GatewayTxnId } = query;
if (!MerchantTxnId || !GatewayTxnId) {
return {
url: `${process.env.CLIENT_URL}/payment-status/failure?message=Payment failed`,
};
}
// Verify payment status
const status = await this.getNpsPaymentStatus(query);
const payment = await this.getPaymentOrderByTXNID(MerchantTxnId);
if (status.data.Status === 'Success') {
// Update payment status to completed
await this.updatePaymentOrderStatus(MerchantTxnId, 'completed', status);
return {
url: `${process.env.CLIENT_URL}/payment-status/success?orderId=${payment.orderId}`,
};
} else {
return {
url: `${process.env.CLIENT_URL}/payment-status/failure?message=Payment failed&orderId=${payment.orderId}`,
};
}
} catch (error) {
this.logger.error('Error in handleNpsResponse', error);
return {
url: `${process.env.CLIENT_URL}/payment-status/failure?message=Payment failed`,
};
}
}
/**
* Handles NPS webhook notifications
* @param {Object} query - Webhook payload from NPS
* @returns {string} - Webhook response
*/
async handleNpsWebhook(query) {
try {
const { MerchantTxnId, GatewayTxnId } = query;
if (!MerchantTxnId || !GatewayTxnId) {
return 'Failed';
}
const status = await this.getNpsPaymentStatus(query);
if (status.data.Status === 'Success') {
await this.updatePaymentOrderStatus(MerchantTxnId, 'completed', status);
return 'Success';
} else {
return 'Failed';
}
} catch (error) {
this.logger.error('Error in handleNpsWebhook', error);
return 'Failed';
}
}
/**
* Checks payment status with NPS
* @param {Object} query - Transaction details
* @returns {Object} - Payment status response from NPS
*/
async getNpsPaymentStatus(query) {
try {
const { MerchantTxnId, GatewayTxnId } = query;
if (!MerchantTxnId || !GatewayTxnId) {
throw new Error('Missing transaction identifiers');
}
const payload = {
MerchantId: process.env.NPS_MERCHANT_ID,
MerchantName: process.env.NPS_MERCHANT_NAME,
MerchantTxnId: MerchantTxnId,
};
const signature = await this.generateNpsSignature(payload);
payload.Signature = signature;
const response = await axios.post(
process.env.NPS_PAYMENT_STATUS_URL,
payload,
{ headers: await this.generateNpsHeaders() }
);
return response.data;
} catch (error) {
this.logger.error('Error in getNpsPaymentStatus', error);
throw new Error(error.message || 'Failed to get payment status');
}
}
// Helper methods (implement based on your database/ORM)
async createPaymentOrder(orderId, paymentType, provider) {
// Implementation depends on your data layer
// Should create and return payment order with amount and transactionId
}
async getPaymentOrderByTXNID(transactionId) {
// Implementation depends on your data layer
// Should return payment order by transaction ID
}
async updatePaymentOrderStatus(transactionId, status, response) {
// Implementation depends on your data layer
// Should update payment status and store response
}
}
module.exports = NpsService;
Controller Layer Implementation
Create a controller to handle HTTP requests for NPS payment operations:
const express = require('express');
const NpsService = require('./nps.service');
class NpsController {
constructor() {
this.npsService = new NpsService();
}
/**
* Initiates payment with NPS
* POST /payment/provider/nps/:orderId/:paymentType
*/
async initiatePayment(req, res) {
try {
const { orderId, paymentType } = req.params;
const result = await this.npsService.initiatePayment(orderId, paymentType);
res.json(result);
} catch (error) {
res.status(error.status || 500).json({
message: error.message,
error: 'Payment initiation failed'
});
}
}
/**
* Handles payment response from NPS gateway
* GET /payment/provider/nps/response
*/
async handleResponse(req, res) {
try {
const result = await this.npsService.handleNpsResponse(req.query);
res.redirect(result.url);
} catch (error) {
res.redirect(`${process.env.CLIENT_URL}/payment-status/failure?message=Payment failed`);
}
}
/**
* Handles webhook from NPS
* GET /payment/provider/nps/webhook
*/
async handleWebhook(req, res) {
try {
const result = await this.npsService.handleNpsWebhook(req.query);
res.send(result);
} catch (error) {
res.send('Failed');
}
}
/**
* Gets available payment instruments
* GET /payment/provider/nps/instrument
*/
async getInstruments(req, res) {
try {
const instruments = await this.npsService.getNpsInstrument();
res.json(instruments);
} catch (error) {
res.status(error.status || 500).json({
message: error.message,
error: 'Failed to get payment instruments'
});
}
}
}
// Express router setup
const router = express.Router();
const npsController = new NpsController();
router.post('/:orderId/:paymentType', npsController.initiatePayment.bind(npsController));
router.get('/response', npsController.handleResponse.bind(npsController));
router.get('/webhook', npsController.handleWebhook.bind(npsController));
router.get('/instrument', npsController.getInstruments.bind(npsController));
module.exports = router;
Key Functions Explained
1. Signature Generation (generateNpsSignature)
Purpose: Creates a secure HMAC-SHA512 signature for API authentication.
How it works:
- Sorts payload keys alphabetically
- Concatenates all values in order
- Generates HMAC-SHA512 hash using secret key
- Returns lowercase hexadecimal string
Security Importance: Prevents tampering and ensures data integrity.
2. Header Generation (generateNpsHeaders)
Purpose: Creates Basic Authentication headers for NPS API calls.
Implementation:
- Combines username and password
- Encodes in Base64 format
- Returns authorization headers
- 3. Payment Initiation (
initiatePayment
)
3. Payment Initiation (initiatePayment
)
Purpose: Starts the payment process with NPS gateway.
FLow:
- Validates payment type
- Creates payment order in database
- Requests Process ID from NPS
- Returns form data for payment gateway
4. Payment Status Check (getNpsPaymentStatus)
Purpose: Verifies payment status with NPS servers.
When to use:
- After payment completion
- For webhook verification
- Manual status checks
5. Response Handling (handleNpsResponse)
Purpose: Processes user return from payment gateway.
Responsibilities:
- Verifies payment status
- Updates database
- Redirects user appropriately
6. Webhook Handling (handleNpsWebhook)
Purpose: Processes server-to-server notifications from NPS.
Benefits:
- Ensures payment status updates even if user doesn’t return
- Provides backup verification mechanism
Payment Flow
Here’s the complete payment flow:
1. User initiates payment
↓
2. Generate signature and get Process ID
↓
3. Redirect user to NPS gateway
↓
4. User completes payment on NPS
↓
5. NPS redirects user back (handleNpsResponse)
↓
6. NPS sends webhook notification (handleNpsWebhook)
↓
7. Verify payment status
↓
8. Update order status
↓
9. Show success/failure page
Frontend Integration
Create a payment form that submits to NPS gateway:
<!-- Payment Form -->
<form id="npsPaymentForm" method="POST" action="">
<input type="hidden" name="MerchantId" value="">
<input type="hidden" name="MerchantName" value="">
<input type="hidden" name="Amount" value="">
<input type="hidden" name="MerchantTxnId" value="">
<input type="hidden" name="ProcessId" value="">
<input type="hidden" name="ResponseUrl" value="">
<input type="hidden" name="TransactionRemarks" value="">
<button type="submit">Pay with NPS</button>
</form>
<script>
// JavaScript to populate and submit form
async function initiateNpsPayment(orderId, paymentType) {
try {
const response = await fetch(`/payment/provider/nps/${orderId}/${paymentType}`, {
method: 'POST'
});
const data = await response.json();
// Populate form fields
document.getElementById('npsPaymentForm').action = data.action;
document.querySelector('input[name="MerchantId"]').value = data.MerchantId;
document.querySelector('input[name="MerchantName"]').value = data.MerchantName;
document.querySelector('input[name="Amount"]').value = data.Amount;
document.querySelector('input[name="MerchantTxnId"]').value = data.MerchantTxnId;
document.querySelector('input[name="ProcessId"]').value = data.ProcessId;
document.querySelector('input[name="ResponseUrl"]').value = data.ResponseUrl;
document.querySelector('input[name="TransactionRemarks"]').value = data.TransactionRemarks;
// Submit form
document.getElementById('npsPaymentForm').submit();
} catch (error) {
console.error('Payment initiation failed:', error);
}
}
</script>
Environment-Specific Configuration:
const config = {
development: {
npsBaseUrl: 'https://apisandbox.nepalpayment.com',
amount: 3 // Test amount
},
production: {
npsBaseUrl: 'https://api.nepalpayment.com',
amount: 'calculated' // Real amount
}
};
Conclusion
This comprehensive guide covered the complete implementation of NPS payment gateway integration in Node.js. The implementation includes:
- Secure signature generation using HMAC-SHA512
- Proper authentication with Basic Auth headers
- Complete payment flow from initiation to verification
- Webhook handling for reliable payment updates
- Error handling and security best practices
- Testing strategies for reliable code
Key Takeaways
- Security First: Always validate signatures and use HTTPS
- Error Handling: Implement comprehensive error handling
- Testing: Test thoroughly in sandbox before production
- Monitoring: Log all payment events for debugging
- Documentation: Keep your integration well-documented
Production Checklist
Before going live:
- Replace sandbox URLs with production URLs
- Update credentials for production environment
- Test all payment flows thoroughly
- Implement proper logging and monitoring
- Set up error alerting
- Configure proper HTTPS certificates
- Test webhook endpoints
- Validate all security measures
This implementation provides a solid foundation for integrating NPS payment gateway in your Node.js application. Remember to always refer to the latest NPS documentation for any API changes or updates.
This blog post provides a complete implementation guide based on production-ready code. Always test thoroughly in a sandbox environment before deploying to production.