1 stable release
| 1.0.0 | Feb 26, 2026 |
|---|
#530 in Authentication
56KB
983 lines
qpay
QPay V2 API SDK for Rust. Async client with automatic token management, invoice creation, payment operations, and ebarimt (electronic tax receipt) support.
Installation
Add to your Cargo.toml:
[dependencies]
qpay = "1.0.0"
tokio = { version = "1", features = ["full"] }
Or using cargo add:
cargo add qpay
cargo add tokio --features full
Quick Start
use qpay::{QPayClient, QPayConfig, models::CreateSimpleInvoiceRequest};
#[tokio::main]
async fn main() -> Result<(), qpay::QPayError> {
// Configure from environment variables
let config = QPayConfig::from_env()?;
let client = QPayClient::new(config);
// Create an invoice
let req = CreateSimpleInvoiceRequest {
invoice_code: "YOUR_INVOICE_CODE".to_string(),
sender_invoice_no: "INV-001".to_string(),
invoice_receiver_code: "terminal".to_string(),
invoice_description: "Payment for order #001".to_string(),
sender_branch_code: None,
amount: 10000.0,
callback_url: "https://example.com/callback".to_string(),
};
let invoice = client.create_simple_invoice(&req).await?;
println!("Invoice ID: {}", invoice.invoice_id);
println!("QR Image: {}", invoice.qr_image);
println!("Short URL: {}", invoice.qpay_short_url);
Ok(())
}
Configuration
From environment variables
let config = QPayConfig::from_env()?;
Required 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 |
Manual configuration
let config = QPayConfig::new(
"https://merchant.qpay.mn",
"your_username",
"your_password",
"YOUR_INVOICE_CODE",
"https://example.com/callback",
);
Custom HTTP client
use std::time::Duration;
let http = reqwest::Client::builder()
.timeout(Duration::from_secs(60))
.build()
.unwrap();
let client = QPayClient::with_http_client(config, http);
Usage
Authentication
Token management is fully automatic. The client obtains and refreshes tokens as needed before each request. You can also manage tokens manually:
// Get a new token
let token = client.get_token().await?;
println!("Access token: {}", token.access_token);
// Refresh the current token
let new_token = client.refresh_token().await?;
Create an invoice (simple)
use qpay::models::CreateSimpleInvoiceRequest;
let req = CreateSimpleInvoiceRequest {
invoice_code: "YOUR_INVOICE_CODE".to_string(),
sender_invoice_no: "INV-001".to_string(),
invoice_receiver_code: "terminal".to_string(),
invoice_description: "Order payment".to_string(),
sender_branch_code: None,
amount: 50000.0,
callback_url: "https://example.com/callback".to_string(),
};
let invoice = client.create_simple_invoice(&req).await?;
println!("Invoice ID: {}", invoice.invoice_id);
println!("QR text: {}", invoice.qr_text);
// Show deeplinks for bank apps
for url in &invoice.urls {
println!("{}: {}", url.name, url.link);
}
Create an invoice (full options)
use qpay::models::*;
let req = CreateInvoiceRequest {
invoice_code: "YOUR_INVOICE_CODE".to_string(),
sender_invoice_no: "INV-002".to_string(),
sender_branch_code: Some("BRANCH_01".to_string()),
sender_branch_data: Some(SenderBranchData {
name: Some("Main Branch".to_string()),
email: Some("branch@example.com".to_string()),
..Default::default()
}),
sender_staff_data: None,
sender_staff_code: None,
invoice_receiver_code: "terminal".to_string(),
invoice_receiver_data: Some(InvoiceReceiverData {
register: Some("AA12345678".to_string()),
name: Some("Customer Name".to_string()),
email: Some("customer@example.com".to_string()),
phone: Some("99001122".to_string()),
address: None,
}),
invoice_description: "Detailed invoice".to_string(),
enable_expiry: Some("true".to_string()),
allow_partial: Some(false),
minimum_amount: None,
allow_exceed: Some(false),
maximum_amount: None,
amount: 100000.0,
callback_url: "https://example.com/callback".to_string(),
sender_terminal_code: None,
sender_terminal_data: None,
allow_subscribe: None,
subscription_interval: None,
subscription_webhook: None,
note: Some("Special instructions".to_string()),
transactions: None,
lines: Some(vec![
InvoiceLine {
tax_product_code: Some("TAX001".to_string()),
line_description: "Product A".to_string(),
line_quantity: "2".to_string(),
line_unit_price: "50000".to_string(),
note: None,
discounts: None,
surcharges: None,
taxes: None,
},
]),
};
let invoice = client.create_invoice(&req).await?;
Create an invoice with ebarimt (tax)
use qpay::models::*;
let req = CreateEbarimtInvoiceRequest {
invoice_code: "YOUR_INVOICE_CODE".to_string(),
sender_invoice_no: "INV-TAX-001".to_string(),
sender_branch_code: None,
sender_staff_data: None,
sender_staff_code: None,
invoice_receiver_code: "terminal".to_string(),
invoice_receiver_data: None,
invoice_description: "Tax invoice".to_string(),
tax_type: "1".to_string(),
district_code: "23".to_string(),
callback_url: "https://example.com/callback".to_string(),
lines: vec![
EbarimtInvoiceLine {
tax_product_code: Some("TAX001".to_string()),
line_description: "Taxable product".to_string(),
barcode: None,
line_quantity: "1".to_string(),
line_unit_price: "10000".to_string(),
note: None,
classification_code: None,
taxes: None,
},
],
};
let invoice = client.create_ebarimt_invoice(&req).await?;
Cancel an invoice
client.cancel_invoice("invoice_id_here").await?;
Check payment status
use qpay::models::{PaymentCheckRequest, Offset};
let req = PaymentCheckRequest {
object_type: "INVOICE".to_string(),
object_id: "invoice_id_here".to_string(),
offset: Some(Offset {
page_number: 1,
page_limit: 10,
}),
};
let result = client.check_payment(&req).await?;
println!("Payment count: {}", result.count);
if let Some(amount) = result.paid_amount {
println!("Total paid: {}", amount);
}
for row in &result.rows {
println!("Payment {} - Status: {} - Amount: {}",
row.payment_id, row.payment_status, row.payment_amount);
}
Get payment details
let payment = client.get_payment("payment_id_here").await?;
println!("Status: {}", payment.payment_status);
println!("Amount: {} {}", payment.payment_amount, payment.payment_currency);
println!("Date: {}", payment.payment_date);
println!("Wallet: {}", payment.payment_wallet);
List payments
use qpay::models::{PaymentListRequest, Offset};
let req = PaymentListRequest {
object_type: "INVOICE".to_string(),
object_id: "invoice_id_here".to_string(),
start_date: "2026-01-01".to_string(),
end_date: "2026-01-31".to_string(),
offset: Offset {
page_number: 1,
page_limit: 20,
},
};
let result = client.list_payments(&req).await?;
println!("Total: {}", result.count);
for item in &result.rows {
println!("{}: {} {} ({})",
item.payment_id, item.payment_amount,
item.payment_currency, item.payment_status);
}
Cancel a payment
use qpay::models::PaymentCancelRequest;
let req = PaymentCancelRequest {
callback_url: Some("https://example.com/cancel-callback".to_string()),
note: Some("Cancelled by customer request".to_string()),
};
client.cancel_payment("payment_id_here", &req).await?;
Refund a payment
use qpay::models::PaymentRefundRequest;
let req = PaymentRefundRequest {
callback_url: Some("https://example.com/refund-callback".to_string()),
note: Some("Refund for order #001".to_string()),
};
client.refund_payment("payment_id_here", &req).await?;
Create ebarimt (electronic tax receipt)
use qpay::models::CreateEbarimtRequest;
let req = CreateEbarimtRequest {
payment_id: "payment_id_here".to_string(),
ebarimt_receiver_type: "83".to_string(), // "83" = individual, "80" = organization
ebarimt_receiver: None, // Set to register number for organizations
district_code: Some("23".to_string()),
classification_code: None,
};
let ebarimt = client.create_ebarimt(&req).await?;
println!("Ebarimt ID: {}", ebarimt.id);
println!("Lottery: {}", ebarimt.ebarimt_lottery);
println!("QR: {}", ebarimt.ebarimt_qr_data);
println!("Status: {}", ebarimt.barimt_status);
Cancel ebarimt
let ebarimt = client.cancel_ebarimt("payment_id_here").await?;
println!("Cancelled: {}", ebarimt.barimt_status);
Error Handling
All methods return Result<T, QPayError>. Error variants:
| Variant | Description |
|---|---|
QPayError::Api |
QPay API returned an error response (status code, error code, message) |
QPayError::Http |
Network/HTTP error from reqwest |
QPayError::Json |
JSON serialization/deserialization error |
QPayError::Config |
Configuration error (missing environment variable, etc.) |
QPayError::Token |
Token acquisition failed |
Checking for API errors
use qpay::{is_qpay_error, error};
match client.create_simple_invoice(&req).await {
Ok(invoice) => println!("Success: {}", invoice.invoice_id),
Err(err) => {
if let Some((status_code, code, message)) = is_qpay_error(&err) {
println!("QPay error {}: {} - {}", status_code, code, message);
match code {
error::ERR_INVOICE_NOT_FOUND => println!("Invoice not found"),
error::ERR_INVALID_AMOUNT => println!("Invalid amount"),
error::ERR_AUTHENTICATION_FAILED => println!("Auth failed"),
error::ERR_PERMISSION_DENIED => println!("Permission denied"),
_ => println!("Other API error: {}", code),
}
} else {
println!("Non-API error: {}", err);
}
}
}
Error code constants
The qpay::error module exports all QPay error code constants for pattern matching:
use qpay::error::*;
// Invoice errors
ERR_INVOICE_NOT_FOUND // "INVOICE_NOTFOUND"
ERR_INVOICE_PAID // "INVOICE_PAID"
ERR_INVOICE_ALREADY_CANCELED // "INVOICE_ALREADY_CANCELED"
ERR_INVOICE_CODE_INVALID // "INVOICE_CODE_INVALID"
ERR_INVOICE_LINE_REQUIRED // "INVOICE_LINE_REQUIRED"
// Payment errors
ERR_PAYMENT_NOT_FOUND // "PAYMENT_NOTFOUND"
ERR_PAYMENT_ALREADY_CANCELED // "PAYMENT_ALREADY_CANCELED"
ERR_PAYMENT_NOT_PAID // "PAYMENT_NOT_PAID"
// Auth errors
ERR_AUTHENTICATION_FAILED // "AUTHENTICATION_FAILED"
ERR_PERMISSION_DENIED // "PERMISSION_DENIED"
ERR_NO_CREDENTIALS // "NO_CREDENDIALS"
// Merchant errors
ERR_MERCHANT_NOT_FOUND // "MERCHANT_NOTFOUND"
ERR_MERCHANT_INACTIVE // "MERCHANT_INACTIVE"
// Ebarimt errors
ERR_EBARIMT_NOT_REGISTERED // "EBARIMT_NOT_REGISTERED"
ERR_EBARIMT_CANCEL_NOT_SUPPORTED // "EBARIMT_CANCEL_NOTSUPPERDED"
ERR_EBARIMT_QR_CODE_INVALID // "EBARIMT_QR_CODE_INVALID"
// Amount errors
ERR_INVALID_AMOUNT // "INVALID_AMOUNT"
ERR_MIN_AMOUNT_ERR // "MIN_AMOUNT_ERR"
ERR_MAX_AMOUNT_ERR // "MAX_AMOUNT_ERR"
API Reference
QPayConfig
| Method | Description |
|---|---|
QPayConfig::new(base_url, username, password, invoice_code, callback_url) |
Create config with explicit values |
QPayConfig::from_env() |
Load config from environment variables |
QPayClient
| Method | Description |
|---|---|
QPayClient::new(config) |
Create client with default HTTP settings |
QPayClient::with_http_client(config, http) |
Create client with custom reqwest::Client |
Auth
| Method | Description |
|---|---|
client.get_token() |
Authenticate and get token pair |
client.refresh_token() |
Refresh the current access token |
Invoice
| Method | Description |
|---|---|
client.create_invoice(&req) |
Create invoice with full options |
client.create_simple_invoice(&req) |
Create invoice with minimal fields |
client.create_ebarimt_invoice(&req) |
Create invoice with tax information |
client.cancel_invoice(id) |
Cancel an invoice |
Payment
| Method | Description |
|---|---|
client.get_payment(id) |
Get payment details |
client.check_payment(&req) |
Check payment status for an invoice |
client.list_payments(&req) |
List payments with filters |
client.cancel_payment(id, &req) |
Cancel a payment (card only) |
client.refund_payment(id, &req) |
Refund a payment (card only) |
Ebarimt
| Method | Description |
|---|---|
client.create_ebarimt(&req) |
Create electronic tax receipt |
client.cancel_ebarimt(payment_id) |
Cancel electronic tax receipt |
License
MIT
Dependencies
~6–13MB
~220K SLoC