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

qpay-mn-sdk

v0.1.0

Published

TypeScript SDK for QPay V2 (Mongolia) with Ebarimt 3.0 support. Functional API, token auto-refresh, callback verification, Express/Next middleware.

Readme

qpay-mn-sdk

npm version License: MIT TypeScript

TypeScript SDK for QPay V2 (Mongolia) with full Ebarimt 3.0 support.

  • ✅ All V2 endpoints — invoice, payment, ebarimt v3
  • ✅ Functional API, tree-shakeable
  • ✅ Token auto-refresh
  • ✅ Callback verification (always round-trips QPay)
  • ✅ Express & Next.js (App Router + Pages Router) middleware
  • ✅ Bundled reference data — banks, districts, GS1 classifications, VAT codes, error messages
  • ✅ Strict TypeScript types for every request & response
  • ✅ Zero runtime dependencies

Installation

npm i qpay-mn-sdk
# or
pnpm add qpay-mn-sdk
# or
yarn add qpay-mn-sdk

Requires Node 18+ (uses global fetch).

Quick start

import {
  createClient,
  createSimpleInvoice,
  checkPayment,
  createEbarimt,
} from "qpay-mn-sdk";

const client = createClient({
  username: process.env.QPAY_USERNAME!,
  password: process.env.QPAY_PASSWORD!,
  env: "sandbox", // or "production"
});

// 1) Create an invoice — QR + bank deeplinks
const invoice = await createSimpleInvoice(client, {
  invoice_code: "TEST_INVOICE",
  sender_invoice_no: "ORDER-1234",
  invoice_receiver_code: "terminal",
  invoice_description: "Order #1234",
  amount: 25000,
  callback_url: "https://example.com/qpay/webhook",
});

// 2) From your callback handler, verify the payment with QPay
const result = await checkPayment(client, {
  object_type: "INVOICE",
  object_id: invoice.invoice_id,
});
if (result.count > 0 && result.rows[0].payment_status === "PAID") {
  // 3) Issue Ebarimt 3.0 receipt
  await createEbarimt(client, {
    payment_id: result.rows[0].payment_id,
    ebarimt_receiver_type: "CITIZEN",
    ebarimt_receiver: "88614450",
    district_code: "3505",
  });
}

API overview

All functions take the client as the first argument:

| Group | Function | Endpoint | | --- | --- | --- | | Auth | authenticate | POST /v2/auth/token | | | refresh | POST /v2/auth/refresh | | Invoice | createInvoice | POST /v2/invoice (full) | | | createSimpleInvoice | POST /v2/invoice (simple) | | | createEbarimtInvoice | POST /v2/invoice (with ebarimt fields) | | | cancelInvoice | DELETE /v2/invoice/:id | | Payment | getPayment | GET /v2/payment/:id | | | checkPayment | POST /v2/payment/check | | | cancelPayment | DELETE /v2/payment/cancel/:id | | | refundPayment | DELETE /v2/payment/refund/:id | | | listPayments | POST /v2/payment/list | | Ebarimt 3.0 | createEbarimt | POST /v2/ebarimt_v3/create | | | cancelEbarimt | DELETE /v2/ebarimt_v3/:payment_id | | Callback | verifyCallback | (server-side verification) | | | extractPaymentId | (parse query) |

Three invoice variants — when to use which

QPay's single /v2/invoice endpoint accepts three different request shapes. The SDK exposes them as three named functions so the types stay accurate.

| Function | Use when | | --- | --- | | createSimpleInvoice | One-off checkout; you just need a QR + amount | | createInvoice | Multi-account routing, partial payments, subscriptions | | createEbarimtInvoice | A tax receipt (Ebarimt) is required at payment time |

Ebarimt 3.0 (V3)

This SDK calls the new V3 endpoint /v2/ebarimt_v3/create, which returns the extra fields required by the Mongolian tax authority:

const receipt = await createEbarimt(client, { ... });
receipt.merchant_tin;          // ← V3 only
receipt.ebarimt_receipt_id;    // ← V3 only

Two issuance modes:

// To a citizen (uses phone number registered in Ebarimt app)
await createEbarimt(client, {
  payment_id,
  ebarimt_receiver_type: "CITIZEN",
  ebarimt_receiver: "88614450",
  district_code: "3505",
});

// To a company (uses TIN)
await createEbarimt(client, {
  payment_id,
  ebarimt_receiver_type: "COMPANY",
  ebarimt_receiver: "5395305", // company TIN
  district_code: "3505",
});

Callback verification

Important: QPay does not sign callbacks. You MUST round-trip the payment ID back to QPay before trusting it.

import { verifyCallback } from "qpay-mn-sdk";

const result = await verifyCallback(client, req.url, {
  invoiceId: storedOrder.invoice_id, // required
  expectedAmount: storedOrder.amount, // recommended
});
if (result.isPaid) {
  // safe to mark order paid
}

The function throws QPayCallbackError if:

  • qpay_payment_id isn't in the query string
  • The payment isn't found for the supplied invoice
  • The paid amount is less than expectedAmount

Required response

Per QPay spec, your webhook MUST respond with HTTP 200 and the literal body SUCCESS. The middleware does this for you; if you're rolling your own:

import { CALLBACK_SUCCESS_BODY } from "qpay-mn-sdk";
res.status(200).send(CALLBACK_SUCCESS_BODY);

Express middleware

import express from "express";
import { qpayCallback } from "qpay-mn-sdk/middleware/express";

app.get("/qpay/webhook", qpayCallback({
  client,
  resolve: async (req) => {
    const paymentId = req.query.qpay_payment_id as string;
    const order = await db.findOrderByPaymentRef(paymentId);
    return order ? { invoiceId: order.invoice_id, expectedAmount: order.amount } : null;
  },
  onPaid: async (result) => {
    await db.markOrderPaid(result.paymentId);
  },
}));

Next.js middleware

App Router:

// app/api/qpay/webhook/route.ts
import { createQPayCallbackHandler } from "qpay-mn-sdk/middleware/next";

export const GET = createQPayCallbackHandler({
  client,
  resolve: async ({ paymentId }) => {
    const order = await db.findOrderByPaymentRef(paymentId);
    return order && { invoiceId: order.invoice_id, expectedAmount: order.amount };
  },
  onPaid: async (result) => { /* ... */ },
});

Pages Router: use createQPayCallbackApiRoute instead.

Reference data

Bundled lookup tables for everything QPay's docs list as a static table:

import {
  getBank, getCurrency, getDistrict,
  getClassification, searchClassifications,
  getZeroVatCode, getVatExemptCode,
  describeError,
} from "qpay-mn-sdk/data";

getBank("050000");           // { code: "050000", nameEn: "Khan bank", nameMn: "Хаан банк" }
getDistrict("3505");         // { code: "3505", branchName: "...", subBranchName: "..." }
getClassification("0111100"); // { code: "0111100", name: "Улаан буудайн үр" }
searchClassifications("буудай", 10);
describeError("INVOICE_PAID"); // { messageMn: "...", messageEn: "Invoice is paid!" }

The GS1 classification table is ~500 KB. Import it on the server only — it's a separate entry so it won't end up in your client bundle unless you ask for it.

Token management

The client authenticates lazily on the first request and refreshes the token before it expires (60s leeway by default). You can also:

import { authenticate, isAuthenticated, getCurrentToken, restoreToken } from "qpay-mn-sdk";

await authenticate(client);  // validate creds on app start

if (!isAuthenticated(client)) await authenticate(client);

// Persist + restore across processes:
const token = getCurrentToken(client);
await redis.set("qpay:token", JSON.stringify(token));

const cached = JSON.parse(await redis.get("qpay:token") ?? "null");
if (cached) restoreToken(client, cached);

Error handling

import { QPayError, QPayAuthError, QPayValidationError, QPayHttpError } from "qpay-mn-sdk";

try {
  await createInvoice(client, ...);
} catch (e) {
  if (e instanceof QPayValidationError) {
    console.error("bad request:", e.code, e.message);
  } else if (e instanceof QPayAuthError) {
    console.error("auth failed:", e.message);
  } else if (e instanceof QPayError) {
    console.error("qpay error:", e.status, e.code, e.body);
  }
}

Common business error codes are typed as a literal union (QPayErrorKey) — autocomplete works in switch/if statements.

TypeScript

Every request/response is strictly typed. Literal unions for BankCode, CurrencyCode, PaymentStatus, TransactionType, TaxType, etc.

import type { CreateEbarimtInvoiceRequest, EbarimtResponse } from "qpay-mn-sdk";

Comparison

| | qpay-mn-sdk | @mnpay/qpay | @togtokh.dev/qpay | | --- | --- | --- | --- | | Ebarimt 3.0 (V3) | ✅ | ❌ V2 only | ❌ V2 only | | Full TypeScript | ✅ | partial | partial | | Bundled lookup tables | ✅ | ❌ | ❌ | | Callback verification helper | ✅ | ❌ | ❌ | | Express/Next middleware | ✅ | ❌ | ❌ | | Token auto-refresh | ✅ | manual | manual | | Zero deps | ✅ | axios | axios |

License

MIT

Disclaimer

This is an unofficial SDK. QPay™ is a trademark of QPay LLC, Mongolia. Refer to the official documentation for the authoritative API specification.