Getting Started
This guide walks you through integrating QPay payments into your application — from installing an SDK to receiving your first payment.
Prerequisites
Before you begin, make sure you have:
- A QPay Merchant Account — Contact QPay to register as a merchant and obtain your credentials.
- API Credentials — You will receive a username, password, and invoice code from QPay.
- A Callback URL — A publicly accessible HTTPS endpoint where QPay will send payment notifications when a customer completes a payment.
[!TIP] For development and testing, use the sandbox environment at
https://merchant-sandbox.qpay.mn. Switch tohttps://merchant.qpay.mnwhen you are ready to go live.
Step 1: Choose Your SDK
Pick the SDK that matches your tech stack. All SDKs share the same design patterns, so the concepts you learn with one apply to all.
| Language | Package | Install |
|---|---|---|
| Go | qpay-go | go get github.com/qpay-sdk/qpay-go |
| JavaScript / TypeScript | qpay-js | npm install qpay-js |
| Python | qpay-py | pip install qpay-py |
| PHP | qpay-php | composer require usukhbayar/qpay-php |
| Ruby | qpay-sdk | gem install qpay-sdk |
| Dart / Flutter | qpay | dart pub add qpay |
| Rust | qpay | cargo add qpay |
| Java | qpay-java | Maven / Gradle |
| .NET (C#) | QPay | dotnet add package QPay |
| Swift | qpay-swift | Swift Package Manager |
| cURL | qpay-curl | git clone |
If you are using a web framework, also check the Framework Packages (Laravel, Django, Spring Boot, Express, NestJS, Rails, ASP.NET Core, Flutter, FastAPI) for deeper integration with built-in webhook routing, DI, and configuration.
Step 2: Installation
Install the SDK for your language:
Go
go get github.com/qpay-sdk/qpay-goJavaScript / TypeScript
npm install qpay-jsPython
pip install qpay-pyPHP
composer require usukhbayar/qpay-phpJava (Maven)
<dependency>
<groupId>io.github.qpay-sdk</groupId>
<artifactId>qpay-java</artifactId>
<version>1.0.0</version>
</dependency>Java (Gradle)
implementation 'io.github.qpay-sdk:qpay-java:1.0.0'Ruby
gem install qpay-sdk.NET
dotnet add package QPayDart / Flutter
dart pub add qpaySwift (Package.swift)
.package(url: "https://github.com/qpay-sdk/qpay-swift.git", from: "1.0.0")Rust
cargo add qpayStep 3: Configuration
All QPay SDKs support the same set of environment variables. This is the recommended way to configure your credentials — it keeps secrets out of your source code.
Environment Variables
| Variable | Required | Description | Example |
|---|---|---|---|
QPAY_BASE_URL | Yes | QPay API base URL | https://merchant.qpay.mn |
QPAY_USERNAME | Yes | Your merchant username | MY_MERCHANT |
QPAY_PASSWORD | Yes | Your merchant password | s3cret_p@ss |
QPAY_INVOICE_CODE | Yes | Your default invoice code | MY_INVOICE_CODE |
QPAY_CALLBACK_URL | Yes | Payment notification URL | https://example.com/api/qpay/callback |
Create a .env file in your project root:
QPAY_BASE_URL=https://merchant-sandbox.qpay.mn
QPAY_USERNAME=your_username
QPAY_PASSWORD=your_password
QPAY_INVOICE_CODE=YOUR_INVOICE_CODE
QPAY_CALLBACK_URL=https://yoursite.com/api/qpay/callback[!WARNING] Never commit your
.envfile to version control. Add it to your.gitignore.
Loading Configuration
Every SDK provides a function to load config from environment variables:
Manual Configuration
You can also configure the client directly in code:
// JavaScript / TypeScript
const client = new QPayClient({
baseUrl: 'https://merchant.qpay.mn',
username: 'YOUR_USERNAME',
password: 'YOUR_PASSWORD',
invoiceCode: 'YOUR_INVOICE_CODE',
callbackUrl: 'https://yoursite.com/api/qpay/callback',
});Step 4: Create Your First Invoice
An invoice is how you request payment from a customer. When you create an invoice, QPay returns a QR code image (base64-encoded) and deep links for mobile banking apps.
Go
package main
import (
"context"
"fmt"
"log"
qpay "github.com/qpay-sdk/qpay-go"
)
func main() {
cfg, err := qpay.LoadConfigFromEnv()
if err != nil {
log.Fatal(err)
}
client := qpay.NewClient(cfg)
invoice, err := client.CreateSimpleInvoice(context.Background(), &qpay.CreateSimpleInvoiceRequest{
InvoiceCode: cfg.InvoiceCode,
SenderInvoiceNo: "ORDER-001",
InvoiceReceiverCode: "terminal",
InvoiceDescription: "Payment for Order #001",
Amount: 50000,
CallbackURL: cfg.CallbackURL,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Invoice ID: %s\n", invoice.InvoiceID)
fmt.Printf("QR Image: %s\n", invoice.QRImage) // base64 PNG
fmt.Printf("Short URL: %s\n", invoice.QPay_ShortURL)
}JavaScript / TypeScript
import { QPayClient, loadConfigFromEnv } from 'qpay-js';
const config = loadConfigFromEnv();
const client = new QPayClient(config);
const invoice = await client.createSimpleInvoice({
invoiceCode: config.invoiceCode,
senderInvoiceNo: 'ORDER-001',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Payment for Order #001',
amount: 50000,
callbackUrl: config.callbackUrl,
});
console.log('Invoice ID:', invoice.invoiceId);
console.log('QR Image:', invoice.qrImage); // base64 PNG
console.log('Short URL:', invoice.qPayShortUrl);Python
from qpay import QPayClient, QPayConfig, CreateSimpleInvoiceRequest
config = QPayConfig.from_env()
with QPayClient(config) as client:
invoice = client.create_simple_invoice(
CreateSimpleInvoiceRequest(
invoice_code=config.invoice_code,
sender_invoice_no="ORDER-001",
invoice_receiver_code="terminal",
invoice_description="Payment for Order #001",
amount=50000.0,
callback_url=config.callback_url,
)
)
print(f"Invoice ID: {invoice.invoice_id}")
print(f"QR Image: {invoice.qr_image}") # base64 PNG
print(f"Short URL: {invoice.qpay_short_url}")PHP
use QPay\Config;
use QPay\QPayClient;
use QPay\Models\CreateSimpleInvoiceRequest;
$config = Config::fromEnv();
$client = new QPayClient($config);
$invoice = $client->createSimpleInvoice(new CreateSimpleInvoiceRequest(
invoiceCode: $config->invoiceCode,
senderInvoiceNo: 'ORDER-001',
invoiceReceiverCode: 'terminal',
invoiceDescription: 'Payment for Order #001',
amount: 50000.0,
callbackUrl: $config->callbackUrl,
));
echo "Invoice ID: " . $invoice->invoiceId . "\n";
echo "QR Image: " . $invoice->qrImage . "\n"; // base64 PNG
echo "Short URL: " . $invoice->qPayShortUrl . "\n";Invoice Response
The invoice response contains everything you need to present payment options to the customer:
| Field | Description |
|---|---|
invoiceId | Unique invoice identifier (use this for payment checks) |
qrImage | Base64-encoded PNG of the QR code — display this to the customer |
qPayShortUrl | Short URL that opens the QPay payment page |
urls | Array of deep links for specific mobile banking apps |
[!TIP] Display the QR code for desktop users and use the deep links (
urls) to redirect mobile users directly to their banking app.
Step 5: Check Payment Status
After the customer scans the QR code or opens a banking app link, you need to verify whether the payment was completed. There are two approaches: polling and webhooks.
Polling (Check Payment)
Call the checkPayment method with the invoice ID to see if any payments have been made:
Go
result, err := client.CheckPayment(ctx, &qpay.PaymentCheckRequest{
ObjectType: "INVOICE",
ObjectID: invoice.InvoiceID,
Offset: &qpay.Offset{
PageNumber: 1,
PageLimit: 10,
},
})
if result.Count > 0 {
fmt.Printf("Payment received! Paid amount: %.0f MNT\n", result.PaidAmount)
} else {
fmt.Println("Waiting for payment...")
}JavaScript / TypeScript
const result = await client.checkPayment({
objectType: 'INVOICE',
objectId: invoice.invoiceId,
offset: { pageNumber: 1, pageLimit: 10 },
});
if (result.count > 0) {
console.log('Payment received! Paid amount:', result.paidAmount);
} else {
console.log('Waiting for payment...');
}Python
from qpay import PaymentCheckRequest, Offset
result = client.check_payment(
PaymentCheckRequest(
object_type="INVOICE",
object_id=invoice.invoice_id,
offset=Offset(page_number=1, page_limit=10),
)
)
if result.count > 0:
print(f"Payment received! Paid amount: {result.paid_amount} MNT")
else:
print("Waiting for payment...")PHP
use QPay\Models\PaymentCheckRequest;
$result = $client->checkPayment(new PaymentCheckRequest(
objectType: 'INVOICE',
objectId: $invoice->invoiceId,
pageNumber: 1,
pageLimit: 10,
));
if ($result->count > 0) {
echo "Payment received! Paid amount: {$result->paidAmount} MNT\n";
} else {
echo "Waiting for payment...\n";
}[!NOTE] In a real application, poll at reasonable intervals (e.g., every 3-5 seconds) and stop after a timeout. Do not poll indefinitely.
Step 6: Handle Webhooks (Callbacks)
QPay sends an HTTP POST request to your QPAY_CALLBACK_URL when a payment is completed. This is more efficient than polling and is recommended for production use.
How Callbacks Work
- You set
callbackUrlwhen creating the invoice. - When the customer completes the payment, QPay sends a POST request to that URL.
- Your server receives the notification and verifies the payment using
checkPayment. - You update the order status in your database.
Example: Express.js Webhook Handler
import express from 'express';
import { QPayClient, loadConfigFromEnv } from 'qpay-js';
const app = express();
app.use(express.json());
const client = new QPayClient(loadConfigFromEnv());
app.post('/api/qpay/callback', async (req, res) => {
const { invoice_id } = req.body;
// Always verify the payment with QPay -- never trust the callback alone
const result = await client.checkPayment({
objectType: 'INVOICE',
objectId: invoice_id,
offset: { pageNumber: 1, pageLimit: 10 },
});
if (result.count > 0) {
// Payment confirmed -- update your order status
console.log(`Payment confirmed for invoice ${invoice_id}`);
console.log(`Paid amount: ${result.paidAmount}`);
// await updateOrderStatus(invoice_id, 'paid');
}
res.status(200).json({ status: 'ok' });
});Example: Python (Flask) Webhook Handler
from flask import Flask, request, jsonify
from qpay import QPayClient, QPayConfig, PaymentCheckRequest, Offset
app = Flask(__name__)
config = QPayConfig.from_env()
client = QPayClient(config)
@app.post("/api/qpay/callback")
def qpay_callback():
data = request.get_json()
invoice_id = data.get("invoice_id")
# Always verify the payment with QPay
result = client.check_payment(
PaymentCheckRequest(
object_type="INVOICE",
object_id=invoice_id,
offset=Offset(page_number=1, page_limit=10),
)
)
if result.count > 0:
print(f"Payment confirmed for invoice {invoice_id}")
print(f"Paid amount: {result.paid_amount}")
# update_order_status(invoice_id, "paid")
return jsonify({"status": "ok"}), 200Example: Go Webhook Handler
http.HandleFunc("/api/qpay/callback", func(w http.ResponseWriter, r *http.Request) {
var body struct {
InvoiceID string `json:"invoice_id"`
}
json.NewDecoder(r.Body).Decode(&body)
// Always verify the payment with QPay
result, err := client.CheckPayment(r.Context(), &qpay.PaymentCheckRequest{
ObjectType: "INVOICE",
ObjectID: body.InvoiceID,
Offset: &qpay.Offset{PageNumber: 1, PageLimit: 10},
})
if err != nil {
http.Error(w, "check failed", http.StatusInternalServerError)
return
}
if result.Count > 0 {
log.Printf("Payment confirmed for invoice %s, amount: %.0f", body.InvoiceID, result.PaidAmount)
// updateOrderStatus(body.InvoiceID, "paid")
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})[!WARNING] Always verify the payment by calling
checkPaymentafter receiving a callback. Never rely solely on the callback data — it could be spoofed by a third party.
Integration Flow Summary
Here is the complete payment integration flow:
1. Customer places an order on your site
2. Your server calls createSimpleInvoice() with the order amount
3. QPay returns a QR code + deep links
4. You display the QR code / redirect to banking app
5. Customer pays via their bank
6. QPay sends a callback to your server (POST)
7. Your server calls checkPayment() to verify
8. You mark the order as paidWhat’s Next?
Now that you have the basics working, explore these resources:
- SDK Documentation — Detailed guides for each language with full API reference
- API Reference — Complete endpoint documentation for authentication, invoices, payments, and ebarimt
- Framework Packages — Pre-built integrations for Laravel, Django, Spring Boot, Express, NestJS, Rails, ASP.NET Core, Flutter, and FastAPI
- CMS Plugins — Drop-in payment plugins for WooCommerce, Shopify, OpenCart, Magento, PrestaShop, WordPress, and Odoo
- Error Codes — Comprehensive error reference for debugging and troubleshooting