← Back to list

Integrating eSewa and Khalti Payment Gateways in Node.js

Digital payments are rapidly growing in Nepal, with platforms like eSewa and Khalti making online transactions fast and convenient. For…

Bibek Kumar Bakabal · 2026-04-21 15:06 · 98 claps · 3.6 min read
#nodejs #khalti #esewa #payment-gateway #payment-integration
Open on Medium ↗
Wiki topics: FIN · Fintech & Banking 🌐 · Web Development

Integrating eSewa and Khalti Payment Gateways in Node.js

Digital payments are rapidly growing in Nepal, with platforms like eSewa and Khalti making online transactions fast and convenient. For modern web applications, integrating these payment gateways is essential to provide a smooth and secure user experience.

In this blog, we will briefly explore how to integrate eSewa and Khalti into a Node.js website, covering the basic steps and workflow required to start accepting online payments.

Prerequisites for Khalti Payment Integration

Before integrating Khalti into your Node.js website, make sure you have the following:

1. Khalti Merchant Account

You need to register as a merchant on Khalti and get approval to use their payment gateway.

2. API Keys (Public & Secret Key)

After approval, Khalti provides:

  • Secret Key → used in backend (Node.js)

3. Node.js Backend Setup

A working Node.js server (Express recommended) to handle payment verification.

Khalti Credentials For Test Test Khalti ID for 9800000000 9800000001 9800000002 9800000003 9800000004 9800000005

Test MPIN 1111

Test OTP 987654

For Sandbox Access

Signup from here as a merchant.

Please use 987654 as login OTP for sandbox env.

For Production Access

Please visit here

import axios from "axios";
import { config } from "../config/index";

interface IPaymentData {
    amount: number;
}

class Payment {

    public static async inititeKhaltiPayment(data: IPaymentData) {
        const amount = 100 * 100;
        const purchase_order_id =
            EsewaCredentialsHelper.generateUniqueId();
        const payload = {
            "amount": amount,
            "return_url": config.PAYMENT_SUCCESS_URL,
            "website_url": config.FRONTEND_URL,
            "purchase_order_id": purchase_order_id,
            "purchase_order_name": "test",
            "customer_info": {
                "name": "Ram Bahadur",
                "email": "test@khalti.com",
                "phone": "9800000001"
            }
        };

        try {
            const response = await axios.post(
                config.KHALTI_PAYMENT_INITIATE_URL as string,
                payload,
                {
                    headers: {
                        "Authorization": `Key ${config.KHALTI_SECRET_KEY as string}`,
                        "Content-Type": "application/json",
                    },
                }
            );

            console.log("response", response.data);

            return {
                success: true,
                pidx: response.data.pidx,
                payment_url: response.data.payment_url,
                expires_at: response.data.expires_at,
                expires_in: response.data.expires_in,
                purchase_order_id,
            };
        } catch (error: any) {
            console.error("Khalti Error:", error?.response?.data || error.message);
            throw new Error(
                error?.response?.data?.message || error.message || "Payment initiation failed"
            );
        }
    }

    public static async verifyKhaltiPayment(pidx: string) {
        try {
            const payload = {
                "pidx": "YZwkAxPGmeiW233quZfFBB"
            }
            const response = await axios.post(
                config.KHALTI_PAYMENT_STATUS_CHECK_URL as string,
                payload,
                {
                    headers: {
                        "Authorization": `Key ${config.KHALTI_SECRET_KEY as string}`,
                        "Content-Type": "application/json",
                    },
                }
            );
            console.log("response", response.data);

            return {
                success: true,
                data: response.data,
            };
        } catch (error: any) {
            console.error("Khalti Error:", error?.response?.data || error.message);
            throw new Error(
                error?.response?.data?.message || "Payment verification failed"
            );
        }
    }
}
#Khalti Credentials
KHALTI_SECRET_KEY=secrect key
KHALTI_PAYMENT_INITIATE_URL=https://dev.khalti.com/api/v2/epayment/initiate/
KHALTI_PAYMENT_STATUS_CHECK_URL=https://dev.khalti.com/api/v2/epayment/lookup/

# Frontend URLs
FRONTEND_URL=http://localhost:5173
PAYMENT_SUCCESS_URL=http://localhost:5173/payment-success
PAYMENT_FAILURE_URL=http://localhost:5173/payment-failure

In the above Code Blocks you can see that how the khalti payment work

  1. first we need the amount which is mendatory Khalti always accept the amount in paisa so that we always have to multiply by 100 to convert amount in paisa eg. *(amount 100). 2.we need the purchase_order_id which we can generate random by using different combination of different eg: random_number + purchase_product_id which should be unique 3.we need costumer details. 4.return_url which decide where to route after payment success 5.website_url means the fronted url. Now After this POST request , to KHALTI_PAYMENT_INITIATE_URL it return pidx: response.data.pidx, payment_url: response.data.payment_url, expires_at: response.data.expires_at, expires_in: response.data.expires_in**
  {
        "pidx": "bZQLD9wRVWo4CdESSfuSsB",
        "payment_url": "https://test-pay.khalti.com/?pidx=bZQLD9wRVWo4CdESSfuSsB",
        "expires_at": "2023-05-25T16:26:16.471649+05:45",
        "expires_in": 1800
    }

Now, You Can Simply Route to payment_url it open the payment ui where you can easily process the payment After Payment you route to return_url simpy in in url contains the information of payment like pidx In the return Url section you have to request another api to check the if the payment is veify or not you have to pass the only pidx to verify payment if payment is verify it return to success_Url

Esewa Payment Integration

1. Use Test Credentials

eSewa provides demo credentials:

  • Merchant Code (scd): EPAYTEST
  • Test User ID: 9806800001
  • Password: 123456
  • MPIN: 1122
  • Token: 123456

2. Use UAT (Test) URLs

import axios from "axios";
import { config } from "../config/index";
import EsewaCredentialsHelper from "../helper/EsewaCredentialsHelper";

interface IPaymentData {
    amount: number;
}

export interface IEsewaInitializeResponse {
    formHtml: string;
    transaction_uuid: string;
}

class Payment {
    public static async inititeEsewaPayment(data: IPaymentData): Promise<IEsewaInitializeResponse> {
        const amount = 100;
        const transaction_uuid = EsewaCredentialsHelper.generateUniqueId();

        const message = `total_amount=${amount},transaction_uuid=${transaction_uuid},product_code=${config.ESEWA_MERCHENT_ID}`;

        const signature = EsewaCredentialsHelper.generateHash(
            config.ESEWA_SECRET_KEY as string,
            message
        );

        const payload = {
            amount,
            tax_amount: 0,
            total_amount: amount,
            transaction_uuid,
            product_code: config.ESEWA_MERCHENT_ID,
            product_service_charge: 0,
            product_delivery_charge: 0,
            success_url: config.PAYMENT_SUCCESS_URL,
            failure_url: config.PAYMENT_FAILURE_URL,
            signed_field_names: "total_amount,transaction_uuid,product_code",
            signature,
        };

        const formHtml = `
        <html>
        <body onload="document.forms[0].submit()">
            <form action="${config.ESEWA_PAYMENT_INITIATE_URL}" method="POST">
                ${Object.entries(payload)
                .map(([key, value]) =>
                    `<input type="hidden" name="${key}" value="${value}" />`
                )
                .join("")}
            </form>
        </body>
        </html>
        `;
        console.log("formHtml", transaction_uuid);
        return { transaction_uuid, formHtml };
    }

    public static async verifyEsewaPayment(transaction_uuid: string) {
        try {
            const payload = {
                transaction_uuid: 'id-1776334867786-5nnc7kf1b',
                total_amount: 100,
                product_code: config.ESEWA_MERCHENT_ID
            }
            const response = await axios.get(
                config.ESEWA_PAYMENT_STATUS_CHECK_URL as string,
                {
                    params: payload,
                }
            );
            console.log("response", response.data);
            return {
                success: true,
                data: response.data,
            };
        } catch (error: any) {
            console.error("eSewa Error:", error?.response?.data || error.message);
            throw new Error(
                error?.response?.data?.message || "Payment verification failed"
            );
        }
    }

}

export default Payment;

import crypto from "crypto";

class EsewaCredentialsHelper {

    public static generateHash(secret: string, data: string) {
        const hmac = crypto.createHmac('sha256', secret);
        return hmac.update(data).digest('base64');
    }

    public static generateUniqueId() {
    return `id-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}

export default EsewaCredentialsHelper
ESEWA_SECRET_KEY=8gBm/:&EnhH.1/q
ESEWA_MERCHENT_ID=EPAYTEST
ESEWA_PAYMENT_INITIATE_URL=https://rc-epay.esewa.com.np/api/epay/main/v2/form
ESEWA_PAYMENT_STATUS_CHECK_URL=https://rc.esewa.com.np/api/epay/transaction/status/
ESEWA_FAILURE_URL=https://developer.esewa.com.np/failure

For to integrate We Cannot Perform the operation only using the Backend We need Fronted + backend Steps

  1. We need a hash,transcation_id,return_url,success_url etc after collect all the payload need for the request we simply create a form and return the form ,that build html file directly route to payment section ,we can easily process the payment like khalti . 2.to Verify the payment we need the total_amont ,transaction_uuid, product_code as payload after that we hit the GET Request on ESEWA_PAYMENT_STATUS_CHECK_URL simpy this return response like below after success payment
{
  "product_code": "EPAYTEST",
  "transaction_uuid": "123",
  "total_amount": 100.0
  "status": "COMPLETE",
  "ref_id": "0001TS9"
} 

메타데이터
post_id
50589600a679
slug
integrating-esewa-and-khalti-payment-gateways-node-js-50589600a679
url
https://medium.com/@bibekmagar746/integrating-esewa-and-khalti-payment-gateways-node-js-50589600a679
canonical_url
https://medium.com/@bibekmagar746/integrating-esewa-and-khalti-payment-gateways-node-js-50589600a679
author_url
https://medium.com/@bibekmagar746
status
ok
fetched_at
2026-07-11 02:50:30