QPay Dart SDK
A Dart/Flutter client library for the QPay V2 API with automatic token management, strong typing, and comprehensive error handling.
Features
- Automatic access token acquisition and refresh
- Strongly typed request/response models for all endpoints
- Invoice creation (simple, full, and ebarimt/tax)
- Payment checking, listing, cancellation, and refund
- Ebarimt (electronic tax receipt) creation and cancellation
- Named error code constants for easy matching
- Works with Dart CLI apps and Flutter
Installation
Dart
dart pub add qpay
Flutter
flutter pub add qpay
Or add it manually to your pubspec.yaml:
dependencies:
qpay: ^1.0.0
Then run:
dart pub get
Quick Start
import 'package:qpay/qpay.dart';
void main() async {
final client = QPayClient(QPayConfig(
baseUrl: 'https://merchant.qpay.mn',
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD',
invoiceCode: 'YOUR_INVOICE_CODE',
callbackUrl: 'https://yourapp.com/callback',
));
try {
// Create a simple invoice
final invoice = await client.createSimpleInvoice(CreateSimpleInvoiceRequest(
invoiceCode: 'YOUR_INVOICE_CODE',
senderInvoiceNo: 'INV-001',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Order #001',
amount: 10000,
callbackUrl: 'https://yourapp.com/callback',
));
print('Invoice ID: ${invoice.invoiceId}');
print('QR Image: ${invoice.qrImage}');
print('Short URL: ${invoice.qPayShortUrl}');
// Check payment status
final check = await client.checkPayment(PaymentCheckRequest(
objectType: 'INVOICE',
objectId: invoice.invoiceId,
));
print('Paid: ${check.count > 0}');
} on QPayException catch (e) {
print('QPay error: ${e.code} - ${e.message}');
} finally {
client.close();
}
}
Configuration
Direct Configuration
const config = QPayConfig(
baseUrl: 'https://merchant.qpay.mn',
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD',
invoiceCode: 'YOUR_INVOICE_CODE',
callbackUrl: 'https://yourapp.com/callback',
);
From Environment Variables
For server-side Dart applications, you can load configuration from environment variables:
final config = QPayConfig.fromEnvironment();
This reads the following environment variables:
| Variable | Description |
|---|---|
QPAY_BASE_URL |
QPay API base URL (e.g. https://merchant.qpay.mn) |
QPAY_USERNAME |
QPay merchant username |
QPAY_PASSWORD |
QPay merchant password |
QPAY_INVOICE_CODE |
Default invoice code |
QPAY_CALLBACK_URL |
Payment callback URL |
Throws ArgumentError if any required variable is missing.
Custom HTTP Client
You can provide your own http.Client for custom configurations or testing:
import 'package:http/http.dart' as http;
final httpClient = http.Client();
final client = QPayClient(config, httpClient: httpClient);
When you provide your own HTTP client, calling client.close() will not close it -- you are responsible for its lifecycle.
Usage
Authentication
The client handles authentication automatically. Tokens are acquired on the first request and refreshed transparently when they expire. You can also manage tokens manually:
// Manually get a new token
final token = await client.getToken();
print('Access token: ${token.accessToken}');
// Manually refresh the token
final refreshed = await client.refreshToken();
Create a Simple Invoice
final invoice = await client.createSimpleInvoice(CreateSimpleInvoiceRequest(
invoiceCode: 'YOUR_INVOICE_CODE',
senderInvoiceNo: 'INV-001',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Coffee x2',
amount: 12000,
callbackUrl: 'https://yourapp.com/callback',
));
print('Invoice ID: ${invoice.invoiceId}');
print('QR: ${invoice.qrText}');
// Deep links for bank apps
for (final url in invoice.urls) {
print('${url.name}: ${url.link}');
}
Create a Full Invoice
final invoice = await client.createInvoice(CreateInvoiceRequest(
invoiceCode: 'YOUR_INVOICE_CODE',
senderInvoiceNo: 'INV-002',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Detailed order',
amount: 25000,
callbackUrl: 'https://yourapp.com/callback',
allowPartial: false,
allowExceed: false,
note: 'Order note',
senderBranchData: SenderBranchData(
name: 'Main Branch',
email: 'branch@example.com',
phone: '99001122',
address: Address(city: 'Ulaanbaatar', district: 'KHN'),
),
invoiceReceiverData: InvoiceReceiverData(
name: 'Customer',
email: 'customer@example.com',
phone: '88001122',
),
lines: [
InvoiceLine(
lineDescription: 'Widget A',
lineQuantity: '2',
lineUnitPrice: '10000',
taxes: [
TaxEntry(taxCode: 'VAT', description: 'VAT 10%', amount: 2000),
],
),
InvoiceLine(
lineDescription: 'Widget B',
lineQuantity: '1',
lineUnitPrice: '5000',
),
],
));
Create an Ebarimt Invoice (Tax Invoice)
final invoice = await client.createEbarimtInvoice(CreateEbarimtInvoiceRequest(
invoiceCode: 'YOUR_INVOICE_CODE',
senderInvoiceNo: 'INV-003',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Tax invoice',
taxType: 'VAT',
districtCode: 'UB01',
callbackUrl: 'https://yourapp.com/callback',
lines: [
EbarimtInvoiceLine(
taxProductCode: 'TP001',
lineDescription: 'Service fee',
lineQuantity: '1',
lineUnitPrice: '50000',
classificationCode: 'CL01',
),
],
));
Cancel an Invoice
await client.cancelInvoice('invoice-id');
Check Payment Status
final result = await client.checkPayment(PaymentCheckRequest(
objectType: 'INVOICE',
objectId: 'invoice-id',
));
if (result.count > 0) {
print('Paid amount: ${result.paidAmount}');
for (final row in result.rows) {
print('Payment ${row.paymentId}: ${row.paymentStatus}');
print(' Amount: ${row.paymentAmount} ${row.paymentCurrency}');
print(' Wallet: ${row.paymentWallet}');
}
}
Get Payment Details
final payment = await client.getPayment('payment-id');
print('Status: ${payment.paymentStatus}');
print('Amount: ${payment.paymentAmount}');
print('Date: ${payment.paymentDate}');
// Card transaction details
for (final ct in payment.cardTransactions) {
print('Card: ${ct.cardType} ${ct.cardNumber}');
print('Settlement: ${ct.settlementStatus}');
}
// P2P transaction details
for (final p2p in payment.p2pTransactions) {
print('Bank: ${p2p.accountBankName}');
print('Account: ${p2p.accountNumber}');
}
List Payments
final payments = await client.listPayments(PaymentListRequest(
objectType: 'INVOICE',
objectId: 'invoice-id',
startDate: '2025-01-01',
endDate: '2025-12-31',
offset: Offset(pageNumber: 1, pageLimit: 20),
));
print('Total: ${payments.count}');
for (final item in payments.rows) {
print('${item.paymentId}: ${item.paymentAmount} ${item.paymentCurrency}');
}
Cancel a Payment
await client.cancelPayment(
'payment-id',
PaymentCancelRequest(
callbackUrl: 'https://yourapp.com/cancel-callback',
note: 'Customer requested cancellation',
),
);
Refund a Payment
await client.refundPayment(
'payment-id',
PaymentRefundRequest(
callbackUrl: 'https://yourapp.com/refund-callback',
note: 'Defective product',
),
);
Create an Ebarimt (Tax Receipt)
final ebarimt = await client.createEbarimt(CreateEbarimtRequest(
paymentId: 'payment-id',
ebarimtReceiverType: 'INDIVIDUAL',
ebarimtReceiver: '99001122',
districtCode: 'UB01',
));
print('Ebarimt ID: ${ebarimt.id}');
print('Lottery: ${ebarimt.ebarimtLottery}');
print('QR Data: ${ebarimt.ebarimtQrData}');
print('VAT: ${ebarimt.vatAmount}');
Cancel an Ebarimt
final cancelled = await client.cancelEbarimt('payment-id');
print('Status: ${cancelled.barimtStatus}');
Error Handling
All API errors throw QPayException with structured information:
try {
await client.cancelInvoice('nonexistent-id');
} on QPayException catch (e) {
print('Status: ${e.statusCode}'); // HTTP status code (e.g. 404)
print('Code: ${e.code}'); // QPay error code (e.g. "INVOICE_NOTFOUND")
print('Message: ${e.message}'); // Human-readable message
print('Body: ${e.rawBody}'); // Raw response body for debugging
// Match against known error codes
if (e.code == QPayException.invoiceNotFound) {
print('Invoice does not exist');
} else if (e.code == QPayException.invoicePaid) {
print('Invoice has already been paid');
} else if (e.code == QPayException.authenticationFailed) {
print('Check your credentials');
}
}
Error Code Constants
The QPayException class provides named constants for all known QPay error codes:
QPayException.authenticationFailed // "AUTHENTICATION_FAILED"
QPayException.invoiceNotFound // "INVOICE_NOTFOUND"
QPayException.invoicePaid // "INVOICE_PAID"
QPayException.invoiceAlreadyCanceled // "INVOICE_ALREADY_CANCELED"
QPayException.invoiceCodeInvalid // "INVOICE_CODE_INVALID"
QPayException.invoiceLineRequired // "INVOICE_LINE_REQUIRED"
QPayException.paymentNotFound // "PAYMENT_NOTFOUND"
QPayException.paymentAlreadyCanceled // "PAYMENT_ALREADY_CANCELED"
QPayException.paymentNotPaid // "PAYMENT_NOT_PAID"
QPayException.permissionDenied // "PERMISSION_DENIED"
QPayException.invalidAmount // "INVALID_AMOUNT"
QPayException.minAmountErr // "MIN_AMOUNT_ERR"
QPayException.maxAmountErr // "MAX_AMOUNT_ERR"
QPayException.merchantNotFound // "MERCHANT_NOTFOUND"
QPayException.merchantInactive // "MERCHANT_INACTIVE"
QPayException.noCredentials // "NO_CREDENDIALS"
QPayException.ebarimtNotRegistered // "EBARIMT_NOT_REGISTERED"
QPayException.transactionRequired // "TRANSACTION_REQUIRED"
// ... and more
Flutter Integration Tips
Display QR Code
The qrImage field in InvoiceResponse contains a base64-encoded PNG image:
import 'dart:convert';
import 'package:flutter/material.dart';
Widget buildQrImage(InvoiceResponse invoice) {
final bytes = base64Decode(invoice.qrImage);
return Image.memory(bytes);
}
Open Bank App Deep Links
Use url_launcher to open bank app deep links from InvoiceResponse.urls:
import 'package:url_launcher/url_launcher.dart';
Future<void> openBankApp(Deeplink deeplink) async {
final uri = Uri.parse(deeplink.link);
if (await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
// Show a list of available banks
Widget buildBankList(InvoiceResponse invoice) {
return ListView.builder(
itemCount: invoice.urls.length,
itemBuilder: (context, index) {
final bank = invoice.urls[index];
return ListTile(
leading: Image.network(bank.logo),
title: Text(bank.name),
subtitle: Text(bank.description),
onTap: () => openBankApp(bank),
);
},
);
}
Payment Polling
Poll for payment status after showing the QR code:
import 'dart:async';
Future<bool> waitForPayment(QPayClient client, String invoiceId) async {
for (var i = 0; i < 60; i++) {
await Future.delayed(Duration(seconds: 5));
final result = await client.checkPayment(PaymentCheckRequest(
objectType: 'INVOICE',
objectId: invoiceId,
));
if (result.count > 0) {
return true; // Payment received
}
}
return false; // Timeout after 5 minutes
}
Provider / Riverpod Setup
// Using a simple singleton
class QPayService {
late final QPayClient _client;
QPayService() {
_client = QPayClient(QPayConfig(
baseUrl: 'https://merchant.qpay.mn',
username: 'USERNAME',
password: 'PASSWORD',
invoiceCode: 'CODE',
callbackUrl: 'https://yourapp.com/callback',
));
}
QPayClient get client => _client;
void dispose() => _client.close();
}
API Reference
QPayClient Methods
| Method | Description |
|---|---|
getToken() |
Authenticate and get a new token pair |
refreshToken() |
Refresh the current access token |
createInvoice(request) |
Create a full invoice with all options |
createSimpleInvoice(request) |
Create a simple invoice with minimal fields |
createEbarimtInvoice(request) |
Create an invoice with ebarimt (tax) info |
cancelInvoice(invoiceId) |
Cancel an existing invoice |
getPayment(paymentId) |
Get payment details by ID |
checkPayment(request) |
Check if a payment has been made |
listPayments(request) |
List payments with filters and pagination |
cancelPayment(paymentId, request) |
Cancel a card payment |
refundPayment(paymentId, request) |
Refund a card payment |
createEbarimt(request) |
Create an electronic tax receipt |
cancelEbarimt(paymentId) |
Cancel an electronic tax receipt |
close() |
Close the underlying HTTP client |
Models
Request models (used as method arguments):
CreateInvoiceRequest-- Full invoice creationCreateSimpleInvoiceRequest-- Simple invoice creationCreateEbarimtInvoiceRequest-- Invoice with tax infoPaymentCheckRequest-- Check payment statusPaymentListRequest-- List payments with filtersPaymentCancelRequest-- Cancel a paymentPaymentRefundRequest-- Refund a paymentCreateEbarimtRequest-- Create a tax receipt
Response models (returned from methods):
TokenResponse-- Authentication token pairInvoiceResponse-- Created invoice with QR code and deep linksPaymentDetail-- Full payment informationPaymentCheckResponse/PaymentCheckRow-- Payment check resultsPaymentListResponse/PaymentListItem-- Payment list resultsEbarimtResponse-- Tax receipt information
Common models (used in requests and responses):
Address-- Physical addressSenderBranchData-- Branch informationSenderStaffData-- Staff informationInvoiceReceiverData-- Invoice receiver infoAccount-- Bank accountTransaction-- Invoice transactionInvoiceLine-- Invoice line itemEbarimtInvoiceLine-- Tax invoice line itemTaxEntry-- Tax/discount/surcharge entryDeeplink-- Bank app deep linkOffset-- Pagination offsetEbarimtItem-- Tax receipt line itemEbarimtHistory-- Tax receipt history record
License
MIT License. See LICENSE for details.
Libraries
- qpay
- QPay V2 API client SDK for Dart/Flutter.