Android SDK (Kotlin)

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 (~400 KB).

Download .zip

Prerequisites

  1. Ask your PayUs admin to create an OAuth App in the portal (Admin → OAuth Apps)
  2. Get your App ID and App Secret (the secret is shown once — save it)
  3. Note which scopes were granted (e.g. payments:direct, terminals:read)
  4. Note your Branch ID (visible in the admin portal under Users)
  5. Supported currencies: NZD (New Zealand Dollar) and AUD (Australian Dollar)
  6. (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

// build.gradle.kts (app module)
dependencies {
    implementation("io.onemyposmate:sdk:1.0.0")
}

Quick Start

// 1. Create the client
val client = OneMyPosMateClient.Builder(
    baseUrl = "https://payus.co.nz",
    system = "pos"
).build()

// 2. Authenticate with your OAuth app credentials
client.auth.loginWithClientCredentials(
    appId = "YOUR_APP_ID",
    appSecret = "YOUR_APP_SECRET"
)

// 3. Discover terminals in your branch
val terminals = client.terminals.list(branchId = 17)

// 4. Process a payment through a terminal
val resp = client.payments.payNow(
    PayNowRequest(
        grandTotal = "10.00",
        referenceId = "REF-${System.currentTimeMillis()}",
        branchId = 17,
        configId = terminals.first().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)
val client = OneMyPosMateClient.Builder(
    baseUrl = "https://payus.co.nz",
    system = "pos"
).build()

client.auth.loginWithClientCredentials(
    appId = "YOUR_APP_ID",
    appSecret = "YOUR_APP_SECRET"
)

// Token is now cached and auto-refreshed
// Every subsequent API call uses the cached JWT
val token = client.auth.currentToken()

// Token refreshes automatically 60s before expiry
// Single-flight lock prevents concurrent refreshes
// If token is revoked, loginWithClientCredentials() must be called again

Token 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 immediately
// If scopes change, get a new token to pick up the new permissions

// Check if authenticated
if (client.auth.isAuthenticated()) {
    // Token is valid — proceed with API calls
}

// Force a token refresh
client.auth.refreshToken()

// Handle scope errors
try {
    client.payments.payNow(request)
} catch (e: OneMyPosMateException) {
    if (e.errorCode == ErrorCode.ERR_FORBIDDEN) {
        // Your OAuth app doesn't have the required scope
        // Ask your admin to grant 'payments:direct'
    }
}

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

try {
    client.payments.payNow(request)
} catch (e: OneMyPosMateException) {
    when (e.errorCode) {
        ErrorCode.ERR_FORBIDDEN ->
            // Missing OAuth scope — ask admin to grant it
        ErrorCode.ERR_GATEWAY_NOT_FOUND ->
            // Terminal has no gateway configured
        ErrorCode.ERR_DUPLICATE_TRANSACTION ->
            // referenceId already used — generate a unique one
        ErrorCode.ERR_REFUND_EXCEEDED ->
            // Refund amount exceeds remaining balance
        ErrorCode.ERR_UNAUTHORIZED ->
            // Token expired and auto-refresh failed
            // Call loginWithClientCredentials() again
        ErrorCode.ERR_RATE_LIMITED ->
            // Too many requests — back off and retry
        ErrorCode.ERR_SYSTEM_ERROR ->
            // Server error — SDK retries 3x automatically
        else -> // Unknown error
    }
}

Error Codes Reference

CodeHTTPMeaningSDK Behavior
ERR_FORBIDDEN403Missing required OAuth scopeThrows immediately — check your granted scopes
ERR_GATEWAY_NOT_FOUND422Terminal has no gateway configuredThrows immediately — contact admin
ERR_DUPLICATE_TRANSACTION409referenceId already usedThrows immediately — use a unique referenceId
ERR_REFUND_EXCEEDED400Refund amount exceeds remaining balanceThrows immediately — check remaining balance
ERR_UNAUTHORIZED401Token expired or revokedAuto-retries once with fresh token
ERR_RATE_LIMITED429Too many requestsThrows immediately — implement backoff
ERR_SYSTEM_ERROR500Server errorAuto-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 MethodRequired 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