Complete Guide to NPS (Nepal Payment Solution) Payment Gateway Integration in Node.js

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

  1. Introduction to NPS Payment Gateway
  2. Prerequisites and Setup
  3. Environment Configuration
  4. Service Layer Implementation
  5. Controller Layer Implementation
  6. Key Functions Explained
  7. Payment Flow
  8. Error Handling and Security
  9. Testing and Best Practices
  10. 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:

  1. NPS Merchant Account: Register with Nepal Payment Solution to get your merchant credentials
  2. Node.js Environment: Version 14 or higher
  3. 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:

  1. Validates payment type
  2. Creates payment order in database
  3. Requests Process ID from NPS
  4. 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

  1. Security First: Always validate signatures and use HTTPS
  2. Error Handling: Implement comprehensive error handling
  3. Testing: Test thoroughly in sandbox before production
  4. Monitoring: Log all payment events for debugging
  5. 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top