Integrating Connect IPS Payment Gateway with Node.js: A Complete Guide

Introduction

Connect IPS (Instant Payment System) is Nepal’s premier digital payment platform that enables seamless online transactions. This comprehensive guide will walk you through integrating Connect IPS with your Node.js application, covering everything from setup to production deployment.

What is Connect IPS?

Connect IPS is a real-time payment system that allows users to make instant payments using their bank accounts. It’s widely adopted in Nepal for e-commerce, bill payments, and digital transactions. The system uses digital signatures for security and provides a robust API for merchant integration.

Prerequisites

Before starting the integration, ensure you have:

  • Node.js (v14 or higher)
  • A Connect IPS merchant account
  • Digital certificate (PFX file) from Connect IPS
  • Basic knowledge of TypeScript/JavaScript
  • Understanding of cryptographic signatures

Setting Up the Environment

First, install the required dependencies:

npm install crypto fs path pem nanoid axios node-rsa
npm install --save-dev @types/node @types/pem

Set up your environment variables inĀ .env:

CONNECTIPS_MERCHANT_ID=your_merchant_id
CONNECTIPS_APP_ID=your_app_id
CONNECTIPS_APP_NAME=your_app_name
CONNECTIPS_PASSWORD=your_app_password
CONNECTIPS_CREDITOR_PASSWORD=your_certificate_password
CONNECTIPS_GATEWAY_URL=https://uat.connectips.com/connectipswebgw/loginpage // please confirm with connect ips support team
CONNECTIPS_CHECKTXN_URL=https://uat.connectips.com/connectipswebws/api/creditor/validatetxn //please confirm with connect ips support team
CONNECTIPS_SUCCESS_URL=https://yoursite.com/payment/success
CONNECTIPS_FAILURE_URL=https://yoursite.com/payment/failure
CONNECTIPS_PFX_PATH=./CREDITOR.pfx

Core Implementation

1. Digital Signature Generation

The heart of Connect IPS integration is generating secure digital signatures:

import * as crypto from 'crypto';
import * as pem from 'pem';
import { readFileSync } from 'fs';

class ConnectIpsService {
  private readonly pkOptions = {
    p12Password: process.env.CONNECTIPS_CREDITOR_PASSWORD, // this will provide you in your email , please contact connectips for this
  };

  async generatePrivateKey(pfx: Buffer): Promise<string> {
    return new Promise((resolve, reject) => {
      pem.readPkcs12(pfx, this.pkOptions, (err, cert) => {
        if (cert && cert.key) {
          resolve(cert.key);
        } else {
          reject(err || new Error('Failed to extract private key from PFX'));
        }
      });
    });
  }

  async generateHash(data: string): Promise<string> {
    try {
      const pfx = this.getPFX();
      const RSAKey = await this.generatePrivateKey(pfx);

      // Convert to PKCS8 format
      const NodeRSAConstructor = require('node-rsa');
      const key = new NodeRSAConstructor(RSAKey);
      const privateKey = key.exportKey('pkcs8');

      // Create signature
      const signer = crypto.createSign('sha256WithRSAEncryption');
      signer.update(data);
      const hash = signer.sign(privateKey, 'base64');

      return hash;
    } catch (error) {
      throw new Error('Failed to generate digital signature');
    }
  }

  getPFX(): Buffer {
    const filePath = process.env.CONNECTIPS_PFX_PATH || './CREDITOR.pfx';
    try {
      return readFileSync(filePath);
    } catch (err) {
      throw new Error(`PFX file not found at: ${filePath}`);
    }
  }

async generateSignature(body: any) {
    try {
      // Create the message string exactly as specified in documentation
      const message = this.objectToKeyValueString(body);
      // Use the new generateHash method
      const signature = await this.generateHash(message);
      return signature;
    } catch (error) {
      console.error('Signature generation error:', error);
      throw new HttpException(
        'Failed to generate signature',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }

objectToKeyValueString = (obj: any) => {
    return Object.entries(obj)
      .map(([key, value]) => `${key}=${value}`)
      .join(',');
  };
}

2. Creating Payment Orders

When a user initiates a payment, create a payment order:

async createPaymentOrder(orderId: string, amount: number) {
  try {
    // Convert amount to paisa (NPR smallest unit)
    const amountInPaisa = amount * 100;
    
    const transactionId = this.generateTransactionId(); // use nanoi or random generate transactionId but it should be be under the 20 characters and any payload should not exceed the 20 characters except token
    
    const paymentData = {
      MERCHANTID: process.env.CONNECTIPS_MERCHANT_ID,
      APPID: process.env.CONNECTIPS_APP_ID,
      APPNAME: process.env.CONNECTIPS_APP_NAME,
      TXNID: transactionId, // should not exceed the 20 characters
      TXNDATE: this.getCurrentDate(),
      TXNCRNCY: 'NPR',
      TXNAMT: amountInPaisa.toString(),
      REFERENCEID: transactionId,
      REMARKS: 'Order Payment', // should not exceed the 20 characters
      PARTICULARS: 'E-commerce Purchase', // should not exceed the 20 characters
      TOKEN: 'TOKEN', // Placeholder for signature
    };

    // Generate digital signature
    const signature = await this.generateSignature(paymentData);
    paymentData.TOKEN = signature;

    return {
      method: 'post',
      gatewayUrl: process.env.CONNECTIPS_GATEWAY_URL,
      keysValues: {
        ...paymentData,
        successUrl: process.env.CONNECTIPS_SUCCESS_URL,
        failureUrl: process.env.CONNECTIPS_FAILURE_URL,
      },
    };
  } catch (error) {
    throw new Error('Failed to create payment order');
  }
}

generateTransactionId(): string {
  return `TXN_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

getCurrentDate(): string {
  const d = new Date();
  return (
    ('0' + d.getDate()).slice(-2) +
    '-' +
    ('0' + (d.getMonth() + 1)).slice(-2) +
    '-' +
    d.getFullYear()
  );
}

3. Handling Payment Responses

After payment completion, Connect IPS redirects users back to your application:

async handlePaymentCallback(txnId: string) {
  try {
    // Verify payment status with Connect IPS
// for generating the signature the key should be in the uppercase
    const checkData = {
      MERCHANTID: process.env.CONNECTIPS_MERCHANT_ID,
      APPID: process.env.CONNECTIPS_APP_ID,
      REFERENCEID: txnId,
      TXNAMT: await this.getTransactionAmount(txnId),
    };

    const signature = await this.generateSignature(checkData);
    // for checking the status the payload should be in the camelcase
    const payload = {
      merchantId: process.env.CONNECTIPS_MERCHANT_ID,
      appId: process.env.CONNECTIPS_APP_ID,
      referenceId: txnId,
      txnAmt: checkData.TXNAMT,
      token: signature,
    };

    // Create basic auth header
    const base64Cred = Buffer.from(
      `${process.env.CONNECTIPS_APP_ID}:${process.env.CONNECTIPS_PASSWORD}`
    ).toString('base64');

    const response = await axios.post(
      process.env.CONNECTIPS_CHECKTXN_URL,
      payload,
      {
        headers: {
          Authorization: `Basic ${base64Cred}`,
          'Content-Type': 'application/json',
        },
      }
    );

    return this.processPaymentStatus(response.data, txnId);
  } catch (error) {
    throw new Error('Payment verification failed');
  }
}

processPaymentStatus(response: any, txnId: string) {
  switch (response.status) {
    case 'SUCCESS':
      return this.updatePaymentStatus(txnId, 'completed', response);
    case 'FAILED':
      return this.updatePaymentStatus(txnId, 'failed', response);
    case 'ERROR':
      return this.updatePaymentStatus(txnId, 'cancelled', response);
    default:
      return this.updatePaymentStatus(txnId, 'unknown', response);
  }
}

4. Express.js Route Implementation

Create routes to handle payment flow:

import express from 'express';
const router = express.Router();

// Initiate payment
router.post('/initiate', async (req, res) => {
  try {
    const { orderId, amount } = req.body;
    const paymentData = await connectIpsService.createPaymentOrder(orderId, amount);
    
    // Return payment form or redirect data
    res.json(paymentData);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Handle payment success
router.get('/success', async (req, res) => {
  try {
    const { TXNID } = req.query;
    const result = await connectIpsService.handlePaymentCallback(TXNID as string);
    res.redirect(`/order-confirmation?status=success&txn=${TXNID}`);
  } catch (error) {
    res.redirect(`/order-confirmation?status=error&message=${error.message}`);
  }
});

// Handle payment failure
router.get('/failure', async (req, res) => {
  const { TXNID } = req.query;
  res.redirect(`/order-confirmation?status=failed&txn=${TXNID}`);
});

export default router;

Frontend Integration

Create a payment form that submits to Connect IPS:

<!DOCTYPE html>
<html>
<head>
    <title>Connect IPS Payment</title>
</head>
<body>
    <form id="paymentForm" action="{{gatewayUrl}}" method="post">
        <input type="hidden" name="MERCHANTID" value="{{MERCHANTID}}">
        <input type="hidden" name="APPID" value="{{APPID}}">
        <input type="hidden" name="APPNAME" value="{{APPNAME}}">
        <input type="hidden" name="TXNID" value="{{TXNID}}">
        <input type="hidden" name="TXNDATE" value="{{TXNDATE}}">
        <input type="hidden" name="TXNCRNCY" value="{{TXNCRNCY}}">
        <input type="hidden" name="TXNAMT" value="{{TXNAMT}}">
        <input type="hidden" name="REFERENCEID" value="{{REFERENCEID}}">
        <input type="hidden" name="REMARKS" value="{{REMARKS}}">
        <input type="hidden" name="PARTICULARS" value="{{PARTICULARS}}">
        <input type="hidden" name="TOKEN" value="{{TOKEN}}">
        <input type="hidden" name="successUrl" value="{{successUrl}}">
        <input type="hidden" name="failureUrl" value="{{failureUrl}}">
        
        <button type="submit">Proceed to Payment</button>
    </form>   
 </body>
</html>

Security Best Practices

  1. Certificate Management: Store PFX files securely and never commit them to version control
  2. Environment Variables: Use secure environment variable management
  3. Input Validation: Always validate and sanitize input data
  4. HTTPS Only: Ensure all communication uses HTTPS
  5. Signature Verification: Always verify payment responses with Connect IPS

Testing and Production Deployment

Testing

  • Use Connect IPS sandbox environment for testing
  • Implement comprehensive unit tests for signature generation
  • Test all payment scenarios: success, failure, and cancellation

Production Deployment

  • Use production certificates and URLs
  • Implement proper logging and monitoring
  • Set up webhook endpoints for payment notifications
  • Implement retry mechanisms for network failures

Conclusion

Integrating Connect IPS with Node.js requires careful attention to security, especially digital signature generation. This implementation provides a robust foundation for processing payments in Nepalese e-commerce applications. Remember to thoroughly test your integration and follow Connect IPS guidelines for production deployment.

The key success factors are proper certificate management, accurate signature generation, and comprehensive error handling. With this implementation, you can confidently process payments through Connect IPS while maintaining security and reliability.

1 thought on “Integrating Connect IPS Payment Gateway with Node.js: A Complete Guide”

Leave a Comment

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

Scroll to Top