iOS SDK (Swift)
Integrate PayUs payments into your application. Authenticate with OAuth credentials, discover terminals, and process payments — all with built-in token management and error handling.
Download SDK
Pre-built SDK package ready to drop into your project (~18 KB).
Prerequisites
- Ask your PayUs admin to create an OAuth App in the portal (Admin → OAuth Apps)
- Get your App ID and App Secret (the secret is shown once — save it)
- Note which scopes were granted (e.g.
payments:direct,terminals:read) - Note your Branch ID (visible in the admin portal under Users)
- Supported currencies: NZD (New Zealand Dollar) and AUD (Australian Dollar)
- (Optional) Ask for Sandbox Mode to test with simulated responses — no real money
Sandbox mode lets you test every scenario (success, decline, timeout, rate limit) using magic test amounts.
Installation
// Package.swift
.package(url: "https://github.com/onemyposmate/sdk-ios", from: "1.0.0")
// Then depend on the "OneMyPosMate" productQuick Start
// 1. Create the client
let client = OneMyPosMateClient(
baseUrl: "https://payus.co.nz",
system: "pos"
)
// 2. Authenticate with your OAuth app credentials
try await client.auth.loginWithClientCredentials(
appId: "YOUR_APP_ID",
appSecret: "YOUR_APP_SECRET"
)
// 3. Discover terminals in your branch
let terminals = try await client.terminals.list(branchId: 17)
// 4. Process a payment through a terminal
let resp = try await client.payments.payNow(
PayNowRequest(
grandTotal: "10.00",
referenceId: "REF-\(Int(Date().timeIntervalSince1970))",
branchId: 17,
configId: terminals[0].configId,
channel: "WECHAT"
)
)Authentication
The SDK handles OAuth token exchange, caching, and auto-refresh. You provide your appId and appSecret — the SDK does the rest.
// Authenticate with OAuth app credentials (appId + appSecret)
// Obtain these from your PayUs admin (Portal → OAuth Apps)
let client = OneMyPosMateClient(
baseUrl: "https://payus.co.nz",
system: "pos"
)
try await client.auth.loginWithClientCredentials(
appId: "YOUR_APP_ID",
appSecret: "YOUR_APP_SECRET"
)
// Token cached in Keychain (KeychainTokenStore)
// Every call auto-refreshes if within 60s of expiry
let token = try await client.auth.currentToken()
// Actor-isolated single-flight Task prevents concurrent refreshes
// If token is revoked, loginWithClientCredentials() must be called againToken Lifecycle
Tokens expire after 7 days. The SDK auto-refreshes 60 seconds before expiry. If an admin revokes your OAuth app or changes your scopes, you need to re-authenticate.
// Tokens expire after 7 days — the SDK auto-refreshes
// If the admin revokes your OAuth app, new tokens are blocked
// If scopes change, get a new token to pick up new permissions
// Check if authenticated
if await client.auth.isAuthenticated() {
// Token is valid — proceed with API calls
}
// Force a token refresh
try await client.auth.refreshToken()
// Custom token storage (e.g. for macOS without Keychain)
let client = OneMyPosMateClient(
baseUrl: "https://payus.co.nz",
system: "pos",
tokenStore: InMemoryTokenStore()
)Error Handling
All SDK methods throw typed exceptions with structured error codes. The SDK auto-retries on ERR_UNAUTHORIZED (once) and ERR_SYSTEM_ERROR (3x).
do {
try await client.payments.payNow(request)
} catch let error as OneMyPosMateError {
switch error.errorCode {
case .ERR_FORBIDDEN:
// Missing OAuth scope — ask admin to grant it
break
case .ERR_GATEWAY_NOT_FOUND:
// Terminal has no gateway configured
break
case .ERR_DUPLICATE_TRANSACTION:
// referenceId already used — generate a unique one
break
case .ERR_REFUND_EXCEEDED:
// Refund amount exceeds remaining balance
break
case .ERR_UNAUTHORIZED:
// Token expired and auto-refresh failed
// Call loginWithClientCredentials() again
break
case .ERR_RATE_LIMITED:
// Too many requests — back off and retry
break
case .ERR_SYSTEM_ERROR:
// Server error — SDK retries 3x automatically
break
default: break
}
}Error Codes Reference
| Code | HTTP | Meaning | SDK Behavior |
|---|---|---|---|
| ERR_FORBIDDEN | 403 | Missing required OAuth scope | Throws immediately — check your granted scopes |
| ERR_GATEWAY_NOT_FOUND | 422 | Terminal has no gateway configured | Throws immediately — contact admin |
| ERR_DUPLICATE_TRANSACTION | 409 | referenceId already used | Throws immediately — use a unique referenceId |
| ERR_REFUND_EXCEEDED | 400 | Refund amount exceeds remaining balance | Throws immediately — check remaining balance |
| ERR_UNAUTHORIZED | 401 | Token expired or revoked | Auto-retries once with fresh token |
| ERR_RATE_LIMITED | 429 | Too many requests | Throws immediately — implement backoff |
| ERR_SYSTEM_ERROR | 500 | Server error | Auto-retries 3x with exponential backoff |
Required Scopes
Each SDK method requires a specific OAuth scope. If your app doesn't have the required scope, the server returns 403 ERR_FORBIDDEN. Ask your admin to update your app's scopes.
| SDK Method | Required Scope |
|---|---|
| client.payments.payNow() | payments:direct |
| client.payments.saveTransaction() | payments:direct |
| client.refunds.refund() | refunds:write |
| client.refunds.cancel() | refunds:write |
| client.transactions.getDetails() | transactions:read |
| client.transactions.getRecent() | transactions:read |
| client.reports.channelSummary() | reports:read |
| client.reports.settle() | reports:read |
| client.terminals.list() | terminals:read |
| client.terminals.sendTrigger() | terminals:trigger |