npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

esewa-npm

v1.0.0

Published

Zero-dependency Node.js SDK for the eSewa ePay v2 payment gateway (Nepal) — signature generation, checkout initialization and signed callback verification.

Downloads

24

Readme

esewa-npm

Zero-dependency Node.js SDK for the eSewa ePay v2 payment gateway (Nepal).

It implements the integration flow documented at developer.esewa.com.np: request signing, checkout initialization, and — critically — verification of the signed callback eSewa sends back to your success URL.

  • Zero runtime dependencies — only node:crypto
  • TypeScript-first — full .d.ts types ship in the package
  • Dual format — ESM + CommonJS builds with a proper exports map
  • Node 18+ — uses native fetch-era APIs; no polyfills
npm install esewa-npm

The payment flow in 30 seconds

┌──────────┐   1. checkout.init() → signed form fields    ┌──────────┐
│ Merchant │ ───────────────────────────────────────────► │  Browser │
│  Server  │   2. auto-submit POST form                   └────┬─────┘
└──────────┘                                                   │ 3. POST /api/epay/main/v2/form
                                                               ▼
                                                        ┌──────────────┐
                                                        │    eSewa     │  4. customer logs in
                                                        │  ePay v2     │     (wallet / bank)
                                                        └──────┬───────┘
                                                               │ 5. redirect
                                                               ▼
┌──────────┐   6. GET {success_url}?data=<base64 JSON>   ┌──────────────┐
│ Merchant │ ◄────────────────────────────────────────── │    eSewa     │
│  Server  │   7. decode + VERIFY signature locally       └──────────────┘
└──────────┘   8. deliver goods only if verified === true

Steps 1–2 and 6–7 are what this SDK does. Never mark an order paid without step 7 — the callback data is client-controlled until its HMAC signature is verified with your secret key.


Quickstart (Express)

import express from 'express';
import { Esewa } from 'esewa-npm';

const esewa = new Esewa({
  productCode: 'EPAYTEST',           // your merchant code (EPAYTEST in UAT)
  secretKey: '8gBm/:&EnhH.1/q',      // your HMAC secret key (UAT key shown)
  env: 'UAT',                        // 'UAT' | 'PROD'
});

const app = express();
app.use(express.urlencoded({ extended: true }));

// 1) "Pay with eSewa" — build the signed request and redirect the customer
app.get('/checkout/:orderId', (req, res) => {
  const { htmlForm } = esewa.checkout.init({
    amount: 100,                     // product price
    taxAmount: 10,                   // defaults to 0
    transactionUuid: req.params.orderId,  // unique per attempt: [A-Za-z0-9-]
    successUrl: 'https://shop.example.com/esewa/success',
    failureUrl: 'https://shop.example.com/esewa/failure',
  });
  res.send(htmlForm);                // auto-submitting form → redirects to eSewa
});

// 2) eSewa redirects back here on success: /esewa/success?data=<base64>
app.get('/esewa/success', (req, res) => {
  const { data, verified } = esewa.checkout.parseCallback(String(req.query.data));

  if (!verified) {
    // Signature mismatch: someone tampered with the payload. Treat as fraud.
    return res.status(400).send('Invalid eSewa signature');
  }
  if (data.status !== 'COMPLETE') {
    return res.status(402).send(`Payment not complete: ${data.status}`);
  }

  // Verified: mark order paid using data.transaction_uuid + data.transaction_code
  res.send(`Order ${data.transaction_uuid} paid. Ref: ${data.transaction_code}`);
});

// eSewa redirects here on failure / cancellation / pending
app.get('/esewa/failure', (req, res) => {
  const { data, verified } = esewa.checkout.parseCallback(String(req.query.data));
  res.send(`Payment failed${verified ? ` (${data.status})` : ''}`);
});

app.listen(3000);

That's the entire integration. For server-rendered apps, write htmlForm to the response; for SPAs, return formFields as JSON and POST them from the browser to actionUrl.

async function payWithEsewa(orderId) {
  const { actionUrl, formFields } = await fetch(`/api/checkout/${orderId}`).then(r => r.json());

  const form = document.createElement('form');
  form.action = actionUrl;
  form.method = 'POST';
  for (const [name, value] of Object.entries(formFields)) {
    const input = document.createElement('input');
    input.type = 'hidden';
    input.name = name;
    input.value = value;
    form.appendChild(input);
  }
  document.body.appendChild(form);
  form.submit();
}

Configuration

| Option | Type | Required | Description | | ------------- | --------------------- | -------- | ---------------------------------------------------------------------------------------------------- | | productCode | string | yes | Merchant code issued by eSewa. EPAYTEST for the sandbox. | | secretKey | string | yes | HMAC secret key issued by eSewa. Keep this server-side only. | | env | 'UAT' \| 'PROD' | no | Defaults to 'UAT'. Selects the endpoint set below. | | successUrl | string | no | Default success redirect URL (can be overridden per request). | | failureUrl | string | no | Default failure redirect URL (can be overridden per request). | | hosts | Partial<EsewaHosts> | no | Advanced: override endpoint URLs. |

Endpoints

| Environment | Payment form endpoint | | ----------- | ---------------------------------------------------- | | UAT | https://rc-epay.esewa.com.np/api/epay/main/v2/form | | PROD | https://epay.esewa.com.np/api/epay/main/v2/form |

Sandbox credentials (published by eSewa for testing)

| Item | Value | | ------------- | -------------------------------------------------- | | Product code | EPAYTEST | | Secret key | 8gBm/:&EnhH.1/q | | eSewa ID | 9711111111 … 9711111114 | | Password | Nepal@123 | | MPIN (app) | 1122 | | OTP token | 123456 (fixed in UAT so you don't need real SMS) |


API reference

new Esewa(config) → Esewa

Creates a client bound to one merchant + environment. Throws EsewaValidationError when required config is missing or env is invalid.

esewa.checkout.init(params) → CheckoutInitResult

Builds and signs an ePay v2 payment request. No network call — returns:

| Property | Type | Description | | ------------- | -------------------- | ----------------------------------------------------------------------- | | actionUrl | string | Where the browser POST goes (per env). | | formFields | CheckoutFormFields | Signed fields — POST them verbatim. | | htmlForm | string | Self-submitting HTML form (noscript fallback included). | | env | 'UAT' \| 'PROD' | Environment used. |

CheckoutParams:

| Param | Required | Notes | | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | amount | yes | Product price. | | taxAmount | no | Defaults 0. | | productServiceCharge | no | Defaults 0. | | productDeliveryCharge | no | Defaults 0. | | totalAmount | no | Defaults to the sum of the four components. If provided and inconsistent, EsewaValidationError is thrown (eSewa defines total_amount = amount + tax + service + delivery). | | transactionUuid | yes | Unique per request; alphanumeric + hyphen only (validated locally). | | successUrl | no* | Required via config or param. | | failureUrl | no* | Required via config or param. | | signedFieldNames | no | Advanced override; defaults to total_amount,transaction_uuid,product_code. |

esewa.checkout.parseCallback(base64Data) → ParsedCallback

Decode and verify the data query parameter from your success/failure URL in one call. Returns { data, verified }.

  • Throws EsewaDecodeError when the payload is not base64-encoded JSON.
  • A bad signature does not throw — check verified (that's the point: you want to inspect and log the fraudulent payload).
const { data, verified } = esewa.checkout.parseCallback(String(req.query.data));
// data.status: 'COMPLETE' | 'PENDING' | 'NOT_FOUND' | ... (when present)
// data.transaction_code: eSewa reference, e.g. '000AWEO'
// data.transaction_uuid: echoes what you sent

esewa.checkout.decodeCallback(base64Data) → EsewaCallbackData

Decode only, no verification. Useful for logging raw payloads before deciding what to do.

esewa.checkout.verifyCallback(data, secretKey?) → boolean

Verify an already-decoded payload. Uses the client's secret key unless one is passed.

Low-level signature utilities

import { generateSignature, signFields, verifySignature, buildSignatureMessage } from 'esewa-npm';

// Raw HMAC-SHA256 (base64) over any message:
generateSignature('total_amount=110,transaction_uuid=241028,product_code=EPAYTEST', secret);
// → 'i94zsd3oXF6ZsSr/kGqT4sSzYQzjj1W/waxjWyRwaME='   (official docs vector)

// Sign a record of fields:
signFields({ total_amount: '110', transaction_uuid: '241028', product_code: 'EPAYTEST' }, secret,
  'total_amount,transaction_uuid,product_code');

// Constant-time verification:
verifySignature(fields, signature, secret, signedFieldNames?);

How the signature works

  1. Take the fields named in signed_field_names, in that exact order.
  2. Build the message name1=value1,name2=value2,...
  3. signature = base64( HMAC-SHA256( message, secretKey ) ) (RFC 2104).

Request side (you): signed_field_names = "total_amount,transaction_uuid,product_code" →

total_amount=110,transaction_uuid=241028,product_code=EPAYTEST

Response side (eSewa → you): the payload's own signed_field_names lists more fields, including signed_field_names itself as the last signed entry:

transaction_code=000AWEO,status=COMPLETE,total_amount=1000.0,
transaction_uuid=250610-162413,product_code=EPAYTEST,
signed_field_names=transaction_code,status,total_amount,transaction_uuid,product_code,signed_field_names

verifyCallback() handles both shapes automatically — you pass the raw fields and it reads signed_field_names from the payload. Verification is constant-time (timingSafeEqual), so it is safe against timing attacks.

Note: the standalone signature sample shown in the docs' Signature Generation section (4Ov7pCI1…) does not reproduce from the printed input — it is a stale doc artifact. The form example (i94zsd3o…) and the callback response example do reproduce exactly, and this SDK matches both. Verified programmatically against the published vectors.


Error classes

| Class | Code | Raised when | | ---------------------- | ----------------------- | ------------------------------------------------------------------ | | EsewaError | — | Base class of all SDK errors. | | EsewaValidationError | ESEWA_VALIDATION_FAILED | Bad config/params: missing fields, illegal transactionUuid, inconsistent totalAmount, … | | EsewaDecodeError | ESEWA_DECODE_FAILED | Callback data is not base64-encoded JSON. |

All extend Error, so err instanceof EsewaError catches everything; err.code gives you the stable string.


Security checklist

  • Never expose secretKey to the browser. Signing belongs on your server; the SPA pattern above fetches pre-signed fields from your API instead.
  • Only trust verified === true payloads. The unverified data is just base64 — anyone can craft it.
  • Cross-check the verified payload against your own records: transaction_uuid matches the pending order, total_amount matches the order total, and product_code is yours. Signature proves eSewa said it; your DB proves it's this order.
  • Deliver goods only after verification. If eSewa's response never arrives (session times out after 5 minutes), reconcile using eSewa's transaction status API — roadmap below.
  • Keep transactionUuid unique per attempt; reuse will collide in eSewa's ledger.

Roadmap

  • esewa.status.check(uuid) — transaction status API (/api/epay/transaction/status/)
  • Refund APIs
  • Browser-safe build (client-side signing for non-hosted flows)

PRs welcome — the signature module is already generic enough to power all of them.

License

MIT