Swift SDK
A Swift SDK for the QPay V2 API. Built with Swift concurrency (async/await) and actor isolation for thread-safe token management. Supports iOS 15+, macOS 12+, and server-side Swift.
Requirements: Swift 5.9+, iOS 15.0+ / macOS 12.0+
Installation
Swift Package Manager
Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/qpay-sdk/qpay-swift.git", from: "1.0.0")
]Then add "QPay" to your target’s dependencies:
.target(
name: "YourApp",
dependencies: ["QPay"]
)Xcode
- Open your project in Xcode
- Go to File > Add Package Dependencies…
- Enter the repository URL:
https://github.com/qpay-sdk/qpay-swift.git - Select the version rule and click Add Package
Configuration
Direct Initialization
import QPay
let config = QPayConfig(
baseURL: "https://merchant.qpay.mn",
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
invoiceCode: "YOUR_INVOICE_CODE",
callbackURL: "https://yoursite.com/qpay/callback"
)From Environment Variables
For server-side Swift applications:
let config = try QPayConfig.fromEnvironment()| Variable | Description |
|---|---|
QPAY_BASE_URL | QPay API base URL |
QPAY_USERNAME | QPay merchant username |
QPAY_PASSWORD | QPay merchant password |
QPAY_INVOICE_CODE | Default invoice code |
QPAY_CALLBACK_URL | Payment callback URL |
Custom URLSession
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 60
let client = QPayClient(config: config, session: URLSession(configuration: sessionConfig))Quick Start
import QPay
let config = QPayConfig(
baseURL: "https://merchant.qpay.mn",
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
invoiceCode: "YOUR_INVOICE_CODE",
callbackURL: "https://yoursite.com/qpay/callback"
)
let client = QPayClient(config: config)
let invoice = try await client.createSimpleInvoice(
CreateSimpleInvoiceRequest(
invoiceCode: config.invoiceCode,
senderInvoiceNo: "ORDER-001",
invoiceReceiverCode: "terminal",
invoiceDescription: "Payment for Order #001",
amount: 50000,
callbackURL: config.callbackURL
)
)
print("Invoice ID: \(invoice.invoiceID)")
print("QR Image: \(invoice.qrImage)")
print("Short URL: \(invoice.qPayShortURL)")
for url in invoice.urls {
print(" \(url.name): \(url.link)")
}Create Invoice
let invoice = try await client.createSimpleInvoice(
CreateSimpleInvoiceRequest(
invoiceCode: "YOUR_INVOICE_CODE",
senderInvoiceNo: "ORDER-002",
invoiceReceiverCode: "terminal",
invoiceDescription: "Coffee order",
amount: 15000,
callbackURL: "https://yoursite.com/qpay/callback"
)
)Cancel an invoice:
try await client.cancelInvoice(invoiceID: "invoice-id-here")Check Payment
let check = try await client.checkPayment(
PaymentCheckRequest(
objectType: "INVOICE",
objectID: invoice.invoiceID,
offset: Offset(pageNumber: 1, pageLimit: 10)
)
)
if check.count > 0 {
print("Payment received! Paid: \(check.paidAmount ?? 0) MNT")
for row in check.rows {
print(" \(row.paymentID): \(row.paymentStatus) (\(row.paymentAmount) MNT)")
}
} else {
print("No payment yet")
}List Payments
let list = try await client.listPayments(
PaymentListRequest(
objectType: "INVOICE",
objectID: invoice.invoiceID,
startDate: "2024-01-01",
endDate: "2024-12-31",
offset: Offset(pageNumber: 1, pageLimit: 20)
)
)
print("Total: \(list.count)")
for item in list.rows {
print(" \(item.paymentID) | \(item.paymentAmount) \(item.paymentCurrency) | \(item.paymentStatus)")
}Get details for a single payment:
let detail = try await client.getPayment(paymentID: "payment-id-here")
print("Payment \(detail.paymentID): \(detail.paymentStatus)")Webhook Handling
QPay sends a POST request to your callbackURL when a payment completes. In server-side Swift (e.g., Vapor):
import Vapor
import QPay
func routes(_ app: Application) throws {
let config = try QPayConfig.fromEnvironment()
let qpayClient = QPayClient(config: config)
app.post("api", "qpay", "callback") { req async throws -> HTTPStatus in
struct CallbackBody: Content {
let payment_id: String
}
let body = try req.content.decode(CallbackBody.self)
let result = try await qpayClient.checkPayment(
PaymentCheckRequest(
objectType: "INVOICE",
objectID: body.payment_id
)
)
if result.count > 0 {
// Payment verified -- update your order status
req.logger.info("Payment confirmed: \(body.payment_id)")
}
return .ok
}
}Error Handling
All methods throw QPayError, which provides detailed error information:
do {
let invoice = try await client.createSimpleInvoice(request)
} catch let error as QPayError {
switch error {
case .configMissing(let variable):
print("Missing config: \(variable)")
case .apiError(let statusCode, let code, let message, let rawBody):
print("API error \(statusCode): \(code) - \(message)")
if code == QPayErrorCode.invoiceNotFound {
print("Invoice does not exist")
} else if code == QPayErrorCode.invoicePaid {
print("This invoice has already been paid")
} else if code == QPayErrorCode.authenticationFailed {
print("Invalid credentials")
}
case .requestFailed(let reason):
print("Request failed: \(reason)")
case .decodingFailed(let reason):
print("Could not parse response: \(reason)")
case .encodingFailed(let reason):
print("Could not encode request: \(reason)")
case .unexpected(let reason):
print("Unexpected: \(reason)")
}
}QPayError Variants
| Variant | Description |
|---|---|
.configMissing(variable) | Required configuration variable is missing |
.apiError(statusCode, code, message, rawBody) | QPay API returned an error |
.requestFailed(reason) | Network or HTTP request failure |
.decodingFailed(reason) | JSON response could not be decoded |
.encodingFailed(reason) | Request body could not be encoded |
.unexpected(reason) | An unexpected error occurred |
SwiftUI Example
import SwiftUI
import QPay
struct PaymentView: View {
@State private var qrImage: UIImage?
@State private var invoiceID: String?
@State private var errorMessage: String?
@State private var isLoading = false
let client: QPayClient
var body: some View {
VStack(spacing: 20) {
if isLoading {
ProgressView("Creating invoice...")
}
if let qrImage {
Image(uiImage: qrImage)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250)
}
if let errorMessage {
Text(errorMessage)
.foregroundColor(.red)
}
Button("Pay 50,000 MNT") {
Task { await createInvoice() }
}
.disabled(isLoading)
}
.padding()
}
func createInvoice() async {
isLoading = true
defer { isLoading = false }
do {
let invoice = try await client.createSimpleInvoice(
CreateSimpleInvoiceRequest(
invoiceCode: "YOUR_CODE",
senderInvoiceNo: "APP-\(UUID().uuidString.prefix(8))",
invoiceReceiverCode: "terminal",
invoiceDescription: "In-app purchase",
amount: 50000,
callbackURL: "https://yoursite.com/callback"
)
)
invoiceID = invoice.invoiceID
if let data = Data(base64Encoded: invoice.qrImage) {
qrImage = UIImage(data: data)
}
} catch let error as QPayError {
errorMessage = error.errorDescription
} catch {
errorMessage = error.localizedDescription
}
}
}API Reference
| Method | Description | Returns |
|---|---|---|
getToken() | Authenticate and get a new token pair | TokenResponse |
refreshTokenCall() | Refresh the current access token | TokenResponse |
createInvoice(_:) | Create a full invoice with all options | InvoiceResponse |
createSimpleInvoice(_:) | Create a simple invoice | InvoiceResponse |
createEbarimtInvoice(_:) | Create an invoice with ebarimt (tax) data | InvoiceResponse |
cancelInvoice(invoiceID:) | Cancel an invoice | Void |
getPayment(paymentID:) | Get payment details | PaymentDetail |
checkPayment(_:) | Check payment status | PaymentCheckResponse |
listPayments(_:) | List payments with date range | PaymentListResponse |
cancelPayment(paymentID:request:) | Cancel a card payment | Void |
refundPayment(paymentID:request:) | Refund a card payment | Void |
createEbarimt(_:) | Create a tax receipt | EbarimtResponse |
cancelEbarimt(paymentID:) | Cancel a tax receipt | EbarimtResponse |
All methods are async throws.
Links
- GitHub: github.com/qpay-sdk/qpay-swiftÂ
Last updated on