Rust SDK
QPay V2 API SDK for Rust. Fully async with tokio, automatic token management via tokio::sync::Mutex, reqwest HTTP, and serde serialization. All methods return Result<T, QPayError>.
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 fullConfiguration
From Environment Variables
let config = QPayConfig::from_env()?;| 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);Quick Start
use qpay::{QPayClient, QPayConfig, models::CreateSimpleInvoiceRequest};
#[tokio::main]
async fn main() -> Result<(), qpay::QPayError> {
let config = QPayConfig::from_env()?;
let client = QPayClient::new(config);
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);
for url in &invoice.urls {
println!(" {}: {}", url.name, url.link);
}
Ok(())
}Create Invoice
use qpay::models::CreateSimpleInvoiceRequest;
let req = CreateSimpleInvoiceRequest {
invoice_code: "YOUR_INVOICE_CODE".to_string(),
sender_invoice_no: "INV-002".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);Cancel an invoice:
client.cancel_invoice("invoice-id-here").await?;Check Payment
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?;
if result.count > 0 {
println!("Payment received!");
if let Some(amount) = result.paid_amount {
println!("Total paid: {}", amount);
}
for row in &result.rows {
println!(" {} | {} | {} {} | {}",
row.payment_id, row.payment_status,
row.payment_amount, row.payment_currency,
row.payment_wallet);
}
} else {
println!("No payment yet");
}List Payments
use qpay::models::{PaymentListRequest, Offset};
let req = PaymentListRequest {
object_type: "INVOICE".to_string(),
object_id: "invoice_id_here".to_string(),
start_date: "2024-01-01".to_string(),
end_date: "2024-12-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);
}Get details for a single payment:
let detail = client.get_payment("payment-id-here").await?;
println!("Payment {}: {} ({} {})",
detail.payment_id, detail.payment_status,
detail.payment_amount, detail.payment_currency);Webhook Handling
QPay sends a POST request to your callback_url when a payment completes. Here is how to handle it with axum:
use axum::{extract::Json, routing::post, Router};
use qpay::{QPayClient, QPayConfig, models::PaymentCheckRequest};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Deserialize)]
struct CallbackBody {
payment_id: String,
}
async fn callback_handler(
client: Arc<QPayClient>,
Json(body): Json<CallbackBody>,
) -> &'static str {
let req = PaymentCheckRequest {
object_type: "INVOICE".to_string(),
object_id: body.payment_id.clone(),
offset: None,
};
match client.check_payment(&req).await {
Ok(result) if result.count > 0 => {
println!("Payment confirmed: {}", body.payment_id);
}
Ok(_) => {
println!("Payment not found for: {}", body.payment_id);
}
Err(e) => {
eprintln!("Error checking payment: {}", e);
}
}
"ok"
}
#[tokio::main]
async fn main() {
let config = QPayConfig::from_env().unwrap();
let client = Arc::new(QPayClient::new(config));
let app = Router::new()
.route("/api/qpay/callback", post({
let client = client.clone();
move |body| callback_handler(client, body)
}));
// Start server...
}Error Handling
All methods return Result<T, QPayError>:
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 API 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"),
error::ERR_INVOICE_PAID => println!("Invoice already paid"),
_ => println!("Other API error: {}", code),
}
} else {
println!("Non-API error: {}", err);
}
}
}QPayError Variants
| Variant | Description |
|---|---|
QPayError::Api { status_code, code, message, raw_body } | QPay API returned an error response |
QPayError::Http(reqwest::Error) | Network/HTTP error from reqwest |
QPayError::Json(serde_json::Error) | JSON serialization/deserialization error |
QPayError::Config(String) | Configuration error (missing variable, etc.) |
QPayError::Token(String) | Token acquisition failed |
API Reference
| Method | Description | Returns |
|---|---|---|
QPayClient::new(config) | Create client with default HTTP settings | QPayClient |
QPayClient::with_http_client(config, http) | Create client with custom reqwest::Client | QPayClient |
get_token() | Authenticate and get token pair | Result<TokenResponse> |
refresh_token() | Refresh the current access token | Result<TokenResponse> |
create_invoice(&req) | Create invoice with full options | Result<InvoiceResponse> |
create_simple_invoice(&req) | Create invoice with minimal fields | Result<InvoiceResponse> |
create_ebarimt_invoice(&req) | Create invoice with tax info | Result<InvoiceResponse> |
cancel_invoice(id) | Cancel an invoice | Result<()> |
get_payment(id) | Get payment details | Result<PaymentDetail> |
check_payment(&req) | Check payment status | Result<PaymentCheckResponse> |
list_payments(&req) | List payments with filters | Result<PaymentListResponse> |
cancel_payment(id, &req) | Cancel a payment (card only) | Result<()> |
refund_payment(id, &req) | Refund a payment (card only) | Result<()> |
create_ebarimt(&req) | Create electronic tax receipt | Result<EbarimtResponse> |
cancel_ebarimt(payment_id) | Cancel electronic tax receipt | Result<EbarimtResponse> |
All methods are async and return Result<T, QPayError>.
Links
- crates.io: crates.io/crates/qpayÂ
- docs.rs: docs.rs/qpayÂ
- GitHub: github.com/qpay-sdk/qpay-rustÂ
Last updated on