Java SDK for the QPay V2 API. Provides a type-safe client with automatic token management, builder-pattern request construction, and comprehensive error handling.
- Java 17+
- Maven or Gradle
<dependency>
<groupId>io.github.qpay-sdk</groupId>
<artifactId>qpay-java</artifactId>
<version>1.0.0</version>
</dependency>implementation 'io.github.qpay-sdk:qpay-java:1.0.0'implementation("io.github.qpay-sdk:qpay-java:1.0.0")importmn.qpay.sdk.*;
importmn.qpay.sdk.models.*;
// Configure the clientQPayConfigconfig = QPayConfig.builder()
.baseURL("https://merchant.qpay.mn")
.username("YOUR_USERNAME")
.password("YOUR_PASSWORD")
.invoiceCode("YOUR_INVOICE_CODE")
.callbackURL("https://yoursite.com/payment/callback")
.build();
// Create the clientQPayClientclient = newQPayClient(config);
// Create an invoiceInvoiceResponseinvoice = client.createSimpleInvoice(
CreateSimpleInvoiceRequest.builder()
.invoiceCode(config.getInvoiceCode())
.senderInvoiceNo("ORDER-001")
.invoiceDescription("Payment for Order #001")
.amount(50000.0)
.callbackURL(config.getCallbackURL())
.build()
);
System.out.println("Invoice ID: " + invoice.getInvoiceId());
System.out.println("QR Image: " + invoice.getQrImage());
System.out.println("Short URL: " + invoice.getQPayShortUrl());QPayConfigconfig = QPayConfig.builder()
.baseURL("https://merchant.qpay.mn")
.username("YOUR_USERNAME")
.password("YOUR_PASSWORD")
.invoiceCode("YOUR_INVOICE_CODE")
.callbackURL("https://yoursite.com/payment/callback")
.build();All fields are required. The builder throws IllegalArgumentException if any field is null or blank.
QPayConfigconfig = QPayConfig.fromEnv();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 |
Throws QPayException if any required variable is missing.
The SDK handles token lifecycle automatically:
- Auto-authentication: The first API call triggers authentication via
POST /v2/auth/token. - Token reuse: Valid access tokens are reused across requests.
- Auto-refresh: Expired access tokens are refreshed using the refresh token via
POST /v2/auth/refresh. - Fallback: If refresh fails, the SDK re-authenticates with username/password.
- Thread safety: Token access is synchronized for safe concurrent use.
You can also manage tokens manually:
// Manually get a tokenTokenResponsetoken = client.getToken();
// Manually refreshTokenResponserefreshed = client.refreshToken();importmn.qpay.sdk.models.common.*;
CreateInvoiceRequestrequest = CreateInvoiceRequest.builder()
.invoiceCode("YOUR_INVOICE_CODE")
.senderInvoiceNo("ORDER-002")
.invoiceDescription("Detailed order payment")
.amount(100000.0)
.callbackURL("https://yoursite.com/payment/callback")
.allowPartial(false)
.allowExceed(false)
.note("VIP customer order")
.senderBranchCode("BRANCH_01")
.senderBranchData(
CreateInvoiceRequest.SenderBranchData.builder()
.name("Main Branch")
.email("branch@company.mn")
.phone("77001122")
.address(Address.builder()
.city("Ulaanbaatar")
.district("Sukhbaatar")
.street("Peace Avenue 22")
.build())
.build()
)
.invoiceReceiverData(
CreateInvoiceRequest.InvoiceReceiverData.builder()
.register("AA12345678")
.name("Customer Name")
.email("customer@mail.com")
.phone("99112233")
.build()
)
.lines(List.of(
InvoiceLine.builder()
.taxProductCode("TAX001")
.lineDescription("Product A")
.lineQuantity("2")
.lineUnitPrice("50000")
.build()
))
.build();
InvoiceResponseinvoice = client.createInvoice(request);
System.out.println("Invoice ID: " + invoice.getInvoiceId());
System.out.println("QR Text: " + invoice.getQrText());
// Show available payment deeplinksfor (Deeplinkurl : invoice.getUrls()) {
System.out.println(url.getName() + ": " + url.getLink());
}CreateSimpleInvoiceRequestrequest = CreateSimpleInvoiceRequest.builder()
.invoiceCode("YOUR_INVOICE_CODE")
.senderInvoiceNo("ORDER-003")
.invoiceDescription("Simple payment")
.amount(25000.0)
.callbackURL("https://yoursite.com/payment/callback")
.build();
InvoiceResponseinvoice = client.createSimpleInvoice(request);client.cancelInvoice("INVOICE_ID");importmn.qpay.sdk.models.common.Offset;
PaymentCheckRequestrequest = PaymentCheckRequest.builder()
.objectType("INVOICE")
.objectId("INVOICE_ID")
.offset(newOffset(1, 10))
.build();
PaymentCheckResponseresponse = client.checkPayment(request);
System.out.println("Payment count: " + response.getCount());
System.out.println("Paid amount: " + response.getPaidAmount());
for (PaymentCheckResponse.PaymentCheckRowrow : response.getRows()) {
System.out.println("Payment " + row.getPaymentId() + ": " + row.getPaymentStatus());
}PaymentDetaildetail = client.getPayment("PAYMENT_ID");
System.out.println("Status: " + detail.getPaymentStatus());
System.out.println("Amount: " + detail.getPaymentAmount());
System.out.println("Currency: " + detail.getPaymentCurrency());
System.out.println("Wallet: " + detail.getPaymentWallet());PaymentListRequestrequest = PaymentListRequest.builder()
.objectType("INVOICE")
.objectId("INVOICE_ID")
.startDate("2024-01-01")
.endDate("2024-12-31")
.offset(newOffset(1, 20))
.build();
PaymentListResponseresponse = client.listPayments(request);
System.out.println("Total payments: " + response.getCount());
for (PaymentListResponse.PaymentListItemitem : response.getRows()) {
System.out.println(item.getPaymentId() + " - " + item.getPaymentAmount() + " MNT");
}client.cancelPayment("PAYMENT_ID", "https://yoursite.com/cancel-callback", "Customer requested cancellation");client.refundPayment("PAYMENT_ID", "https://yoursite.com/refund-callback", "Product returned");CreateEbarimtRequestrequest = CreateEbarimtRequest.builder()
.paymentId("PAYMENT_ID")
.ebarimtReceiverType("individual")
.ebarimtReceiver("AA12345678")
.districtCode("01")
.classificationCode("CL001")
.build();
EbarimtResponseebarimt = client.createEbarimt(request);
System.out.println("Ebarimt ID: " + ebarimt.getId());
System.out.println("Status: " + ebarimt.getBarimtStatus());
System.out.println("QR Data: " + ebarimt.getEbarimtQrData());
System.out.println("Lottery: " + ebarimt.getEbarimtLottery());EbarimtResponsecancelled = client.cancelEbarimt("PAYMENT_ID");
System.out.println("Cancelled status: " + cancelled.getBarimtStatus());All API errors throw QPayException, which is a RuntimeException:
try {
client.createSimpleInvoice(request);
} catch (QPayExceptione) {
System.err.println("Status code: " + e.getStatusCode());
System.err.println("Error code: " + e.getCode());
System.err.println("Message: " + e.getMessage());
System.err.println("Raw body: " + e.getRawBody());
}Use ErrorCodes constants for reliable error matching:
try {
client.getPayment("invalid-id");
} catch (QPayExceptione) {
if (ErrorCodes.PAYMENT_NOTFOUND.equals(e.getCode())) {
System.out.println("Payment does not exist");
} elseif (ErrorCodes.AUTHENTICATION_FAILED.equals(e.getCode())) {
System.out.println("Authentication failed, check credentials");
} else {
System.out.println("Unexpected error: " + e);
}
}| Code | Description |
|---|---|
AUTHENTICATION_FAILED | Invalid username/password |
INVOICE_NOTFOUND | Invoice does not exist |
INVOICE_PAID | Invoice already paid |
INVOICE_ALREADY_CANCELED | Invoice already cancelled |
INVOICE_CODE_INVALID | Invalid invoice code |
PAYMENT_NOTFOUND | Payment does not exist |
PAYMENT_NOT_PAID | Payment not yet completed |
PAYMENT_ALREADY_CANCELED | Payment already cancelled |
INVALID_AMOUNT | Invalid payment amount |
PERMISSION_DENIED | Insufficient permissions |
MERCHANT_NOTFOUND | Merchant does not exist |
MERCHANT_INACTIVE | Merchant account is inactive |
EBARIMT_NOT_REGISTERED | Ebarimt not registered |
try {
// some operation that might throw
} catch (Exceptione) {
QPayExceptionqe = QPayException.asQPayError(e);
if (qe != null) {
// Handle QPay-specific error
} else {
// Handle other errors
}
}| Method | HTTP | Path | Description |
|---|---|---|---|
getToken() | POST | /v2/auth/token | Authenticate and get token pair |
refreshToken() | POST | /v2/auth/refresh | Refresh access token |
createInvoice(CreateInvoiceRequest) | POST | /v2/invoice | Create invoice with full options |
createSimpleInvoice(CreateSimpleInvoiceRequest) | POST | /v2/invoice | Create invoice with minimal fields |
cancelInvoice(String) | DELETE | /v2/invoice/{id} | Cancel an invoice |
getPayment(String) | GET | /v2/payment/{id} | Get payment details |
checkPayment(PaymentCheckRequest) | POST | /v2/payment/check | Check payment status |
listPayments(PaymentListRequest) | POST | /v2/payment/list | List payments |
cancelPayment(String, String, String) | DELETE | /v2/payment/cancel/{id} | Cancel a card payment |
refundPayment(String, String, String) | DELETE | /v2/payment/refund/{id} | Refund a card payment |
createEbarimt(CreateEbarimtRequest) | POST | /v2/ebarimt_v3/create | Create electronic tax receipt |
cancelEbarimt(String) | DELETE | /v2/ebarimt_v3/{id} | Cancel electronic tax receipt |
You can provide a custom HttpClient for advanced configuration (proxy, timeouts, etc.):
HttpClientcustomHttp = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
QPayClientclient = newQPayClient(config, customHttp);mvn testThe test suite uses JUnit 5 and OkHttp MockWebServer to test all client methods, error handling, and automatic token management without requiring a live QPay API connection.
MIT License - see LICENSE for details.