Kotlin SDK
1. Overview
The Gateway Kotlin SDK enables clients (PeakPOS, e-commerce backends, and third-party integrators) to communicate with the Peak Gateway API. Built as a Kotlin Multiplatform (KMP) project, the SDK provides coroutine-based async APIs for JVM servers, Android, and the shared iOS Gateway API core.
Consumers: PeakPOS (Android + server), e-commerce backends, third-party integrators
Organization / Location model
The SDK shape is organization-first:
LocationsApiis the primary surface for location records.OrganizationsApiowns organization-scoped resources and credential profiles.- New SDK-facing code should use
organizationIdandlocationIdwherever the model exposes those fields.
The data-model summary is in Organizations and Locations.
2. Module Architecture
sdks/kotlin/
├── gateway-sdk-core/ # KMP (JVM, Android, iOS)
│ └── src/
│ ├── commonMain/kotlin/com/myriad/gateway/
│ │ ├── GatewayAdminClient.kt # Admin/portal entry point
│ │ ├── GatewayPayClient.kt # Payment-facing entry point
│ │ ├── GatewayConfig.kt # Configuration
│ │ ├── GatewayException.kt # Exception hierarchy
│ │ ├── api/ # Client module implementations
│ │ ├── model/
│ │ │ ├── Transaction.kt
│ │ │ ├── TransactionResult.kt # Sealed class for results
│ │ │ ├── TransactionStatus.kt
│ │ │ ├── SaleRequest.kt
│ │ │ ├── CardDataSource.kt
│ │ │ ├── IndustryType.kt
│ │ │ ├── CheckoutSession.kt
│ │ │ └── ...
│ │ ├── webhook/
│ │ │ ├── WebhookVerifier.kt # Signature verification
│ │ │ └── WebhookEvent.kt # Sealed class for event types
│ │ └── internal/
│ │ ├── GatewayHttpClient.kt # Ktor wrapper
│ │ ├── GatewayPlatform.kt # Platform-specific defaults
│ │ ├── AuthManager.kt # OAuth token lifecycle
│ │ ├── RetryPolicy.kt # Exponential backoff
│ │ └── CorrelationIdExtractor.kt
│ ├── jvmMain/kotlin/ # JVM-specific HTTP engine
│ ├── androidMain/kotlin/ # Android-specific HTTP engine
│ └── iosMain/kotlin/ # iOS crypto actuals; Darwin engine dependency
│
└── gateway-sdk-android/ # Android-only runtime + terminal surfaces
└── src/main/kotlin/com/myriad/gateway/sdk/android/
├── GatewayAndroidConfig.kt # Android config builder
├── GatewayAndroidClient.kt # Android wrapper over admin/pay clients
├── host/ # Public embedded terminal/runtime API
│ ├── GatewayAndroidApp.kt
│ ├── GatewayAndroidDeviceRuntime.kt
│ ├── GatewayAndroidTransactionModels.kt
│ └── ...
├── referencehost/ # Optional managed-host tooling
│ ├── GatewayAndroidReferenceHost.kt
│ ├── GatewayAndroidReferenceHostApp.kt
│ ├── GatewayAndroidDeviceManagementSurface.kt
│ └── ...
├── peakpay/ # Sample app proving the public host API
│ ├── PeakPaySampleApp.kt
│ └── PeakPaySampleMain.kt
└── smartconnect/ # Internal runtime implementation
└── ...
Module Summary
| Module | Targets | Purpose | Dependencies |
|---|---|---|---|
gateway-sdk-core-kmp | JVM, Android, iOS | DTOs, API client, errors, webhook verification, OAuth token management | ktor-client, kotlinx.serialization, kotlinx.coroutines |
gateway-sdk-android | Android only | Android config/client wrapper plus embedded terminal/runtime and optional managed-host tooling | gateway-sdk-core-kmp-android, kotlinx.coroutines, kotlinx.serialization |
Support docs do not duplicate the full Kotlin client module inventory. The
authoritative admin/pay surface is the property list in
sdks/kotlin/gateway-sdk-core/src/commonMain/kotlin/com/myriad/gateway/GatewayAdminClient.kt
and
sdks/kotlin/gateway-sdk-core/src/commonMain/kotlin/com/myriad/gateway/GatewayPayClient.kt.
Android-only host and reference-host surfaces are source-owned by
sdks/kotlin/gateway-sdk-android/src/main/kotlin/com/myriad/gateway/sdk/android/.
Not Included
| Omitted | Reason | Where It Lives |
|---|---|---|
| SmartConnect implementation package | Internal runtime detail, not a public SDK surface | gateway-sdk-android/src/main/kotlin/.../smartconnect |
| Spring auto-configuration | Not shipped; the core JVM artifact works from backend services without wrappers | Add explicit application wiring in the service that owns the integration |
| TransIT API client | Internal library for gateway microservices only | libs/transit-client/ |
| XTMS API client | Internal library for Merchant Onboarding Service only | libs/xtms-client/ |
3. Distribution
| Property | Value |
|---|---|
| Group ID | com.myriad.gateway |
| Artifacts | gateway-sdk-core-kmp, gateway-sdk-core-kmp-android, gateway-sdk-android |
| Language | Kotlin 2.2+ (KMP) |
| Build | Bazel (publishes to Google Artifact Registry) |
| Min Android API | 26 (Android 8.0), for gateway-sdk-android |
| KMP Targets | JVM 25, Android, iOS via Kotlin/Native XCFramework |
Published versions resolve from an exact Git tag on HEAD, then
SDK_VERSION_OVERRIDE, then the fallback in
sdks/kotlin/publishing/version.bzl.
Dependency Setup
// Server (JVM), core only
dependencies {
implementation("com.myriad.gateway:gateway-sdk-core-kmp:3.0.0")
}
// Android, core plus android extensions
dependencies {
implementation("com.myriad.gateway:gateway-sdk-core-kmp-android:3.0.0")
implementation("com.myriad.gateway:gateway-sdk-android:3.0.0")
}
Use the version matching the target release tag. The current artifact shape is
a Maven-standard closure: gateway-sdk-core-kmp publishes a JVM jar,
gateway-sdk-core-kmp-android publishes an Android AAR, and
gateway-sdk-android publishes an Android AAR that depends on core at the same
version. Both Artifact Registry and GitHub Release assets carry that shape.
Android Entry Points
gateway-sdk-android now has three package tiers:
GatewayAndroidConfigandGatewayAndroidClientincom.myriad.gateway.sdk.androidfor the core admin/pay APIs.com.myriad.gateway.sdk.android.host.*for the default embedded terminal/runtime surface.com.myriad.gateway.sdk.android.referencehost.*for optional managed-host and device-management tooling.
com.myriad.gateway.sdk.android.smartconnect.* is implementation detail and is
not part of the supported public integration path.
The public contract is enforced by tests in
sdks/kotlin/gateway-sdk-android/src/test and by the sample proof under
src/sample.
For managed-device flows, gateway-sdk-android now auto-collects best-effort
Android metadata for registration, heartbeat, and managed-host payloads. Apps
can pass GatewayAndroidTerminalPlatformInfo only for targeted overrides such
as a branded deviceName or extra metadata.
The Android artifact also carries its own public device-management requirement
models, so Android consumers do not need a separate soft-pos-sdk dependency
to inspect managed-host readiness or queued work.
import com.myriad.gateway.Environment
import com.myriad.gateway.sdk.android.GatewayAndroidClient
import com.myriad.gateway.sdk.android.GatewayAndroidConfig
val androidConfig =
GatewayAndroidConfig.Builder()
.tokenProvider { fetchGatewayAccessTokenFromBackend() }
.environment(Environment.PRODUCTION)
.build()
val androidClient =
GatewayAndroidClient(
androidConfig.toGatewayConfig(),
authorizationProvider = androidConfig.toAuthorizationProvider(),
)
try {
val admin = androidClient.admin
val pay = androidClient.pay
// Use admin/pay APIs from a lifecycle-managed Android wrapper.
} finally {
androidClient.close()
}
For terminal/runtime setup, see Gateway Android SDK.
4. Usage Examples
Initialization
import com.myriad.gateway.GatewayAdminClient
import com.myriad.gateway.Environment
import com.myriad.gateway.GatewayConfig
val client = GatewayAdminClient(
config = GatewayConfig(
clientId = "client_peakpos",
clientSecret = "secret_peakpos",
environment = Environment.PRODUCTION,
connectTimeoutMs = 5000,
readTimeoutMs = 30000
)
)
Process Sale (Server-Side, Card-Not-Present)
import com.myriad.gateway.Environment
import com.myriad.gateway.GatewayConfig
import com.myriad.gateway.GatewayPayClient
import com.myriad.gateway.model.*
val payClient = GatewayPayClient(
GatewayConfig(
clientId = "client_peakpos",
clientSecret = "secret_peakpos",
environment = Environment.PRODUCTION,
)
)
val saleResult = payClient.transactions.sale(
SaleRequest(
amount = 2500L,
currency = "USD",
cardDataSource = CardDataSource.MANUAL,
industryType = IndustryType.ECOMMERCE,
metadata = mapOf("orderId" to "web-12345")
)
)
when (saleResult) {
is TransactionResult.Approved -> {
println("Approved: ${saleResult.approvalCode}")
println("Transaction ID: ${saleResult.transactionId}")
}
is TransactionResult.Declined -> {
println("Declined: ${saleResult.responseMessage}")
}
is TransactionResult.Error -> {
println("Error: ${saleResult.message}")
}
}
Checkout Session (Hosted Payments)
val session = payClient.checkoutSessions.create(
CheckoutSessionRequest(
amount = 5000L,
currency = "USD",
successUrl = "https://merchant.com/success",
cancelUrl = "https://merchant.com/cancel",
locationId = "loc_abc123",
metadata = mapOf("orderId" to "web-67890")
)
)
// Redirect customer to session.checkoutUrl
Void / Refund
// Void
val voidResult = payClient.transactions.void(transactionId = "txn_abc123")
// Refund (partial)
val refundResult = payClient.transactions.refund(
transactionId = "txn_abc123",
amount = 1500L
)
Query Transactions
val transactions = client.transactions.list(
locationId = "loc_abc123",
status = TransactionStatus.APPROVED,
from = LocalDate.of(2026, 3, 1),
to = LocalDate.of(2026, 3, 3),
limit = 50
)
transactions.forEach { txn ->
println("${txn.transactionId}: ${txn.amount} - ${txn.status}")
}
Webhook Event Operations
val details = client.webhookEvents.getDetails("evt_123")
client.webhookEvents.replayFailedDeliveries(details.event.id)
client.webhookEvents.retryDeliveryFromDlq("del_456")
val retention = client.webhookEvents.findRetentionConfigForOrganization("org_123")
println(retention?.deliveryDays)
Webhook Verification
import com.myriad.gateway.webhook.WebhookVerifier
import com.myriad.gateway.webhook.WebhookEvent
val verifier = WebhookVerifier(webhookSecret = "whsec_xxxxxxxxxxxx")
// In your webhook handler
val delivery = verifier.verifyDelivery(
payload = requestBody,
signature = request.getHeader("X-Gateway-Signature"),
timestamp = request.getHeader("X-Gateway-Timestamp"),
headers =
mapOf(
"traceparent" to request.getHeader("traceparent"),
"X-Request-Id" to request.getHeader("X-Request-Id"),
),
)
val event: WebhookEvent = delivery.event
println("gatewayWebhookCorrelationId=${delivery.correlationId}")
when (event) {
is WebhookEvent.PaymentCompleted -> handleCompletion(event.transaction)
is WebhookEvent.PaymentSettled -> handleSettlement(event.locationId, event.totalAmount)
is WebhookEvent.PaymentDeclined -> handleFailure(event.transaction)
}
Android Wrapper Pattern
gateway-sdk-android is a thin lifecycle-friendly wrapper over the core admin
and pay clients. It does not currently ship a Compose ViewModel or UI
state abstraction.
import com.myriad.gateway.Environment
import com.myriad.gateway.sdk.android.GatewayAndroidClient
import com.myriad.gateway.sdk.android.GatewayAndroidConfig
import kotlinx.coroutines.launch
val androidConfig =
GatewayAndroidConfig.Builder()
.tokenProvider { fetchGatewayAccessTokenFromBackend() }
.environment(Environment.STAGING)
.build()
val androidClient =
GatewayAndroidClient(
androidConfig.toGatewayConfig(),
authorizationProvider = androidConfig.toAuthorizationProvider(),
)
lifecycleScope.launch {
val health = androidClient.pay.health()
println(health.status)
}
5. Authentication
The core SDK supports two authentication shapes:
| Auth Method | Use Case |
|---|---|
Client credentials (clientId + clientSecret) | Default SDK-managed OAuth for server-to-server or trusted app/service callers |
Pre-issued bearer token (accessToken) | When another layer already obtained the token and you want the SDK to reuse it |
When accessToken is not supplied, token refresh is handled automatically by
the SDK. Access tokens are cached and refreshed before expiry.
6. Error Handling
try {
val result = client.transactions.sale(request)
} catch (e: GatewayException.AuthenticationError) {
// Invalid client credentials or expired token
} catch (e: GatewayException.RateLimitError) {
// Retry after e.retryAfterSeconds
} catch (e: GatewayException.NetworkError) {
// No connectivity
// SDK automatically retries with exponential backoff if configured
} catch (e: GatewayException.ServerError) {
// 5xx from gateway
} catch (e: GatewayException.ValidationError) {
// Invalid request parameters, check e.fieldErrors
}
7. KMP Architecture Notes
gateway-sdk-core is the shared Gateway API client layer for JVM, Android, and
iOS. The Gradle KMP build declares JVM, Android, iosX64, iosArm64, and
iosSimulatorArm64 targets; Bazel wraps that build for JVM/Android Maven
artifacts plus the GatewaySdkCore XCFramework zip and SwiftPM checksum used
by the SDK release workflow.
The iOS KMP artifact is the shared Gateway API core. It is not the Swift Tap-to-Pay package and it does not own entitlement-bound Apple Tap to Pay adapters, Swift actors, SwiftUI samples, secure-storage lifecycle glue, or other host-app platform code.
For iOS alignment, share only the Gateway API layer through KMP:
GatewayAdminClient,GatewayPayClient, DTOs, errors, OAuth refresh, retry, JSON serialization, and webhook verification- not Apple Tap to Pay kernel bindings, Swift actors, SwiftUI samples, iOS secure-storage/lifecycle glue, or entitlement-bound platform adapters
The iOS deliverable is the GatewaySdkCore XCFramework plus SwiftPM checksum
published by the SDK release workflow. Swift-facing host and Tap-to-Pay code
lives in sdks/swift/terminal-sdk-ios-ttp/.
Platform-Specific Engines
| Platform | Ktor HTTP Engine | Notes |
|---|---|---|
| JVM | CIO or OkHttp | Server-side, full coroutine support |
| Android | OkHttp | Android-optimized, respects connectivity state |
| iOS | Darwin | Shared Gateway API core, packaged as GatewaySdkCore XCFramework |
What's Shared (commonMain)
- All DTOs and request/response models
- API client interfaces and implementations
- Error types and exception hierarchy
- Webhook signature verification
- OAuth token management and auto-refresh
- Retry and timeout logic
- JSON serialization (kotlinx.serialization)
What's Platform-Specific (expect/actual)
- HTTP engine selection
- Logging backend (where added by the host app)
- Secure credential storage policy is owned by the embedding app, not by
gateway-sdk-core