Skip to Content
FrameworksExpress

Express

QPay V2 payment middleware for Express. Built on top of qpay-js , this package provides Express middleware that attaches a QPay client to requests, a pre-built router with invoice and webhook endpoints, and callback-based webhook handling.


Installation

npm install @qpay-sdk/express qpay-js

Both express and qpay-js are peer dependencies.


Configuration

Environment Variables

The middleware reads configuration from environment variables by default (via qpay-js’s loadConfigFromEnv()):

QPAY_BASE_URL=https://merchant.qpay.mn QPAY_USERNAME=your_username QPAY_PASSWORD=your_password QPAY_INVOICE_CODE=your_invoice_code QPAY_CALLBACK_URL=https://yoursite.com/qpay/webhook

Explicit Config

You can also pass a config object directly:

import { qpayMiddleware } from '@qpay-sdk/express'; app.use(qpayMiddleware({ baseUrl: 'https://merchant.qpay.mn', username: 'your_username', password: 'your_password', invoiceCode: 'YOUR_CODE', callbackUrl: 'https://yoursite.com/qpay/webhook', }));

Quick Start

Option 1: Middleware + Custom Routes

Attach the QPay client to every request, then use req.qpay in your route handlers:

import express from 'express'; import { qpayMiddleware } from '@qpay-sdk/express'; const app = express(); app.use(express.json()); app.use(qpayMiddleware()); app.post('/pay', async (req, res) => { const invoice = await req.qpay.createSimpleInvoice({ invoiceCode: process.env.QPAY_INVOICE_CODE, senderInvoiceNo: 'ORDER-001', amount: 10000, callbackUrl: process.env.QPAY_CALLBACK_URL, }); res.json(invoice); }); app.listen(3000);

Option 2: Pre-built Router

Mount the pre-built router for a complete payment API:

import express from 'express'; import { createQPayRouter } from '@qpay-sdk/express'; const app = express(); app.use('/qpay', createQPayRouter(undefined, { onPaymentReceived: (invoiceId, result) => { console.log('Payment confirmed:', invoiceId); // Update order status, send confirmation email, etc. }, onPaymentFailed: (invoiceId, reason) => { console.log('Payment failed:', invoiceId, reason); }, })); app.listen(3000);

This registers three endpoints:

  • POST /qpay/invoice — Create a simple invoice
  • POST /qpay/check — Check payment status
  • POST /qpay/webhook — Webhook callback handler

Create Invoice

Using Middleware

app.use(qpayMiddleware()); app.post('/create-invoice', async (req, res) => { try { const invoice = await req.qpay.createSimpleInvoice({ invoiceCode: 'YOUR_CODE', senderInvoiceNo: `ORDER-${req.body.orderId}`, amount: req.body.amount, callbackUrl: 'https://yoursite.com/qpay/webhook', }); // invoice.invoiceId -- QPay invoice ID // invoice.qrImage -- Base64 QR code // invoice.qrText -- QR code text // invoice.qPayShortUrl -- Short payment URL // invoice.urls -- Bank deep links res.json(invoice); } catch (err) { res.status(500).json({ error: err.message }); } });

Full Invoice

const invoice = await req.qpay.createInvoice({ invoiceCode: 'YOUR_CODE', senderInvoiceNo: 'ORDER-001', invoiceReceiverCode: 'receiver_001', invoiceDescription: 'Payment for Order #001', amount: 10000, callbackUrl: 'https://yoursite.com/qpay/webhook', });

Check Payment

const result = await req.qpay.checkPayment({ objectType: 'INVOICE', objectId: invoiceId, }); if (result.rows && result.rows.length > 0) { // Payment confirmed }

Cancel Invoice

await req.qpay.cancelInvoice(invoiceId);

Webhook Handling

Using the Pre-built Router

The createQPayRouter function includes a webhook endpoint at POST /webhook:

app.use('/qpay', createQPayRouter(undefined, { onPaymentReceived: async (invoiceId, result) => { // Called when payment is confirmed await Order.findOneAndUpdate( { qpayInvoiceId: invoiceId }, { status: 'paid' }, ); }, onPaymentFailed: async (invoiceId, reason) => { // Called when payment verification fails console.error(`Payment failed for ${invoiceId}: ${reason}`); }, }));

Standalone Webhook Middleware

Use the qpayWebhook function for a standalone webhook endpoint:

import { qpayMiddleware, qpayWebhook } from '@qpay-sdk/express'; app.use(qpayMiddleware()); app.post('/qpay/webhook', qpayWebhook({ onPaymentReceived: async (invoiceId, result) => { console.log('Paid:', invoiceId); }, onPaymentFailed: async (invoiceId, reason) => { console.log('Failed:', invoiceId, reason); }, }));

Webhook Flow

When QPay sends a POST to your webhook:

  1. The handler extracts invoice_id from the request body
  2. Calls checkPayment to verify the payment via the QPay API
  3. If payment rows are found, calls onPaymentReceived(invoiceId, result)
  4. If no rows are found, calls onPaymentFailed(invoiceId, 'No payment found')
  5. If an error occurs, calls onPaymentFailed(invoiceId, errorMessage)
  6. Returns a JSON response ({ status: 'paid' } or { status: 'unpaid' })

Event Callbacks

The Express package uses callback-based event handling via QPayWebhookOptions:

interface QPayWebhookOptions { onPaymentReceived?: (invoiceId: string, result: any) => void | Promise<void>; onPaymentFailed?: (invoiceId: string, reason: string) => void | Promise<void>; }

Both callbacks support async functions.


Testing

Mocking the Client

import request from 'supertest'; import express from 'express'; import { qpayMiddleware } from '@qpay-sdk/express'; const app = express(); app.use(express.json()); app.use(qpayMiddleware({ baseUrl: 'https://merchant.qpay.mn', username: 'test', password: 'test', })); // Mock qpay-js client methods for testing jest.mock('qpay-js', () => ({ QPayClient: jest.fn().mockImplementation(() => ({ createSimpleInvoice: jest.fn().mockResolvedValue({ invoiceId: 'inv_123', qrImage: 'base64data', }), checkPayment: jest.fn().mockResolvedValue({ rows: [{ paymentId: 'pay_123' }], }), })), loadConfigFromEnv: jest.fn().mockReturnValue({ baseUrl: 'https://merchant.qpay.mn', username: 'test', password: 'test', }), }));

Testing the Webhook

import request from 'supertest'; describe('Webhook', () => { it('should return paid status', async () => { const onPaymentReceived = jest.fn(); const app = express(); app.use('/qpay', createQPayRouter(undefined, { onPaymentReceived })); const res = await request(app) .post('/qpay/webhook') .send({ invoice_id: 'inv_123' }); expect(res.body.status).toBe('paid'); expect(onPaymentReceived).toHaveBeenCalledWith('inv_123', expect.any(Object)); }); });

API Reference

Exports

ExportTypeDescription
qpayMiddleware(config?)RequestHandlerExpress middleware that attaches req.qpay
qpayWebhook(options?)RequestHandlerStandalone webhook handler middleware
createQPayRouter(config?, options?)RouterPre-built Express router with all endpoints

QPayRequest

Extended Express Request with QPay client:

interface QPayRequest extends Request { qpay: QPayClient; }

QPayWebhookOptions

interface QPayWebhookOptions { onPaymentReceived?: (invoiceId: string, result: any) => void | Promise<void>; onPaymentFailed?: (invoiceId: string, reason: string) => void | Promise<void>; }

Pre-built Router Endpoints

MethodPathDescription
POST/invoiceCreate a simple invoice (body forwarded to createSimpleInvoice)
POST/checkCheck payment status (body forwarded to checkPayment)
POST/webhookWebhook callback handler

Re-exported Types from qpay-js

TypeDescription
QPayConfigConfiguration interface
QPayClientQPay HTTP client
CreateInvoiceRequestFull invoice request
CreateSimpleInvoiceRequestSimple invoice request
InvoiceResponseInvoice response
PaymentCheckRequestPayment check request
PaymentCheckResponsePayment check response

Last updated on