Skip to Content

NestJS

QPay V2 payment module for NestJS. Built on top of qpay-js , this package provides a global NestJS module with dependency injection, an injectable QPayService, a webhook controller, and support for both synchronous and asynchronous configuration.


Installation

npm install @qpay-sdk/nestjs qpay-js

Both @nestjs/common, @nestjs/core, qpay-js, and reflect-metadata are peer dependencies.


Configuration

Static Configuration (forRoot)

import { Module } from '@nestjs/common'; import { QPayModule } from '@qpay-sdk/nestjs'; @Module({ imports: [ QPayModule.forRoot({ baseUrl: 'https://merchant.qpay.mn', username: 'your_username', password: 'your_password', invoiceCode: 'YOUR_CODE', callbackUrl: 'https://yoursite.com/qpay/webhook', }), ], }) export class AppModule {}

Async Configuration (forRootAsync)

Use forRootAsync to load configuration from NestJS ConfigService or other async sources:

import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { QPayModule } from '@qpay-sdk/nestjs'; @Module({ imports: [ ConfigModule.forRoot(), QPayModule.forRootAsync({ imports: [ConfigModule], useFactory: (config: ConfigService) => ({ baseUrl: config.get('QPAY_BASE_URL', 'https://merchant.qpay.mn'), username: config.get('QPAY_USERNAME'), password: config.get('QPAY_PASSWORD'), invoiceCode: config.get('QPAY_INVOICE_CODE'), callbackUrl: config.get('QPAY_CALLBACK_URL'), }), inject: [ConfigService], }), ], }) export class AppModule {}

Environment Variables

When using ConfigModule, set these in your .env:

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

Configuration Options

PropertyTypeDescription
baseUrlstringQPay API base URL
usernamestringMerchant username
passwordstringMerchant password
invoiceCodestringDefault invoice code
callbackUrlstringPayment callback URL

The QPayModuleOptions interface extends QPayConfig from qpay-js.


Quick Start

import { Injectable } from '@nestjs/common'; import { QPayService } from '@qpay-sdk/nestjs'; @Injectable() export class PaymentService { constructor(private readonly qpay: QPayService) {} async createPayment(orderId: string, amount: number) { return this.qpay.createSimpleInvoice({ invoiceCode: 'YOUR_CODE', senderInvoiceNo: `ORDER-${orderId}`, amount, callbackUrl: 'https://yoursite.com/qpay/webhook', }); } }

Create Invoice

Simple Invoice

import { QPayService } from '@qpay-sdk/nestjs'; @Injectable() export class PaymentService { constructor(private readonly qpay: QPayService) {} async createInvoice(orderId: string, amount: number) { const invoice = await this.qpay.createSimpleInvoice({ invoiceCode: 'YOUR_CODE', senderInvoiceNo: `ORDER-${orderId}`, 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 return invoice; } }

Full Invoice

const invoice = await this.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 this.qpay.checkPayment({ objectType: 'INVOICE', objectId: invoiceId, }); if (result.rows && result.rows.length > 0) { // Payment confirmed }

Cancel Invoice

await this.qpay.cancelInvoice(invoiceId);

Get Payment Detail

const payment = await this.qpay.getPayment(paymentId);

List Payments

const payments = await this.qpay.listPayments({ objectType: 'INVOICE', objectId: invoiceId, });

Direct Client Access

For advanced use cases, get the underlying QPayClient:

const client = this.qpay.getClient();

Webhook Handling

Built-in Webhook Controller

The package includes a QPayWebhookController that handles POST /qpay/webhook:

import { Module } from '@nestjs/common'; import { QPayModule, QPayWebhookController } from '@qpay-sdk/nestjs'; @Module({ imports: [QPayModule.forRoot({ /* ... */ })], controllers: [QPayWebhookController], }) export class AppModule {}

The controller:

  1. Accepts POST /qpay/webhook with { invoice_id: string } body
  2. Calls checkPayment to verify the payment
  3. Returns { status: 'paid', invoiceId } or { status: 'unpaid', invoiceId }

Custom Webhook Controller

For more control, create your own webhook controller:

import { Controller, Post, Body, HttpCode } from '@nestjs/common'; import { QPayService } from '@qpay-sdk/nestjs'; import { EventEmitter2 } from '@nestjs/event-emitter'; @Controller('qpay') export class CustomWebhookController { constructor( private readonly qpay: QPayService, private readonly eventEmitter: EventEmitter2, ) {} @Post('webhook') @HttpCode(200) async handleWebhook(@Body() body: { invoice_id?: string }) { const invoiceId = body.invoice_id; if (!invoiceId) { return { error: 'Missing invoice_id' }; } const result = await this.qpay.checkPayment({ objectType: 'INVOICE', objectId: invoiceId, }); if (result.rows?.length > 0) { this.eventEmitter.emit('payment.received', { invoiceId, result }); return { status: 'paid', invoiceId }; } return { status: 'unpaid', invoiceId }; } }

Dependency Injection

Using QPayService

QPayService is automatically available in any provider because QPayModule is decorated with @Global():

import { Injectable } from '@nestjs/common'; import { QPayService } from '@qpay-sdk/nestjs'; @Injectable() export class OrderService { constructor(private readonly qpay: QPayService) {} }

Using @InjectQPay Decorator

Inject the raw QPayClient directly using the custom decorator:

import { Injectable } from '@nestjs/common'; import { InjectQPay } from '@qpay-sdk/nestjs'; import { QPayClient } from 'qpay-js'; @Injectable() export class PaymentService { constructor(@InjectQPay() private readonly client: QPayClient) {} async pay() { return this.client.createSimpleInvoice({ /* ... */ }); } }

Injection Tokens

TokenValueProvides
QPAY_OPTIONS'QPAY_OPTIONS'QPayModuleOptions configuration
QPAY_CLIENT'QPAY_CLIENT'QPayClient instance

Testing

Mocking QPayService

import { Test, TestingModule } from '@nestjs/testing'; import { QPayService } from '@qpay-sdk/nestjs'; describe('PaymentService', () => { let service: PaymentService; let qpayService: QPayService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ PaymentService, { provide: QPayService, useValue: { createSimpleInvoice: jest.fn().mockResolvedValue({ invoiceId: 'inv_123', qrImage: 'base64data', }), checkPayment: jest.fn().mockResolvedValue({ rows: [{ paymentId: 'pay_123' }], }), cancelInvoice: jest.fn(), getPayment: jest.fn(), listPayments: jest.fn(), getClient: jest.fn(), }, }, ], }).compile(); service = module.get(PaymentService); qpayService = module.get(QPayService); }); it('should create invoice', async () => { const result = await service.createPayment('001', 10000); expect(result.invoiceId).toBe('inv_123'); expect(qpayService.createSimpleInvoice).toHaveBeenCalled(); }); });

Testing the Webhook Controller

import { Test, TestingModule } from '@nestjs/testing'; import { QPayWebhookController, QPayService } from '@qpay-sdk/nestjs'; describe('QPayWebhookController', () => { let controller: QPayWebhookController; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [QPayWebhookController], providers: [ { provide: QPayService, useValue: { checkPayment: jest.fn().mockResolvedValue({ rows: [{ paymentId: 'pay_123' }], }), }, }, ], }).compile(); controller = module.get(QPayWebhookController); }); it('should return paid status', async () => { const result = await controller.handleWebhook({ invoice_id: 'inv_123' }); expect(result).toEqual({ status: 'paid', invoiceId: 'inv_123' }); }); });

API Reference

Module

MethodDescription
QPayModule.forRoot(options)Register with static configuration
QPayModule.forRootAsync(options)Register with async configuration

QPayService Methods

MethodReturn TypeDescription
createInvoice(request)Promise<InvoiceResponse>Create a full invoice
createSimpleInvoice(request)Promise<InvoiceResponse>Create a simple invoice
cancelInvoice(invoiceId)Promise<void>Cancel an invoice
getPayment(paymentId)Promise<PaymentDetail>Get payment details
checkPayment(request)Promise<PaymentCheckResponse>Check payment status
listPayments(request)Promise<PaymentListResponse>List payments
getClient()QPayClientAccess the underlying client

Exported Symbols

ExportDescription
QPayModuleNestJS dynamic module
QPayServiceInjectable payment service
QPayWebhookControllerBuilt-in webhook controller
InjectQPayDecorator for injecting raw QPayClient
QPAY_OPTIONSInjection token for config options
QPAY_CLIENTInjection token for client instance
QPayModuleOptionsConfiguration interface (type)
QPayModuleAsyncOptionsAsync configuration interface (type)

Last updated on