Skip to Content
SDKsSwift

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.

GitHub

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

  1. Open your project in Xcode
  2. Go to File > Add Package Dependencies…
  3. Enter the repository URL: https://github.com/qpay-sdk/qpay-swift.git
  4. 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()
VariableDescription
QPAY_BASE_URLQPay API base URL
QPAY_USERNAMEQPay merchant username
QPAY_PASSWORDQPay merchant password
QPAY_INVOICE_CODEDefault invoice code
QPAY_CALLBACK_URLPayment 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

VariantDescription
.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

MethodDescriptionReturns
getToken()Authenticate and get a new token pairTokenResponse
refreshTokenCall()Refresh the current access tokenTokenResponse
createInvoice(_:)Create a full invoice with all optionsInvoiceResponse
createSimpleInvoice(_:)Create a simple invoiceInvoiceResponse
createEbarimtInvoice(_:)Create an invoice with ebarimt (tax) dataInvoiceResponse
cancelInvoice(invoiceID:)Cancel an invoiceVoid
getPayment(paymentID:)Get payment detailsPaymentDetail
checkPayment(_:)Check payment statusPaymentCheckResponse
listPayments(_:)List payments with date rangePaymentListResponse
cancelPayment(paymentID:request:)Cancel a card paymentVoid
refundPayment(paymentID:request:)Refund a card paymentVoid
createEbarimt(_:)Create a tax receiptEbarimtResponse
cancelEbarimt(paymentID:)Cancel a tax receiptEbarimtResponse

All methods are async throws.

Last updated on