Kotlin SDK
1. Overview
The Gateway Kotlin SDK enables clients (PeakPOS, e-commerce backends, future integrators) to communicate with the Peak Gateway API. Built as a Kotlin Multiplatform (KMP) project, the SDK provides coroutine-based async APIs that work on JVM server, Android, and iOS (future via Kotlin/Native).
Consumers: PeakPOS (Android + server), e-commerce backends, future integrators
Organization / Location rename
The current SDK shape is organization-first:
LocationsApiis the primary surface for location records.OrganizationsApiowns organization-scoped resources and credential profiles.MerchantsApiremains as a deprecated alias for legacy callers.- Existing
merchantIdrequest fields still carry the concrete location id on the wire.
Safe migration posture:
- prefer
client.locationsandclient.organizationsin new code - treat
client.merchantsas a compatibility bridge only - keep legacy
merchantIdwording only where the API field itself is still named that way
Migration notes live in SDK v2 Migration Guide. The data-model summary is in Organizations and Locations.
2. Module Architecture
sdks/kotlin/
├── gateway-sdk-core/ # KMP (JVM, Android, iOS (future))
│ └── 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/
│ │ │ ├── TransactionsApi.kt # Sale, void, refund, query
│ │ │ ├── CheckoutSessionsApi.kt # Hosted checkout sessions
│ │ │ ├── WebhookEventsApi.kt # Persisted event log, replay, retention, DLQ retry
│ │ │ └── MerchantsApi.kt # Deprecated compatibility alias
│ │ ├── 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/
│ │ ├── HttpClient.kt # Ktor wrapper (expect/actual)
│ │ ├── JsonSerializer.kt # kotlinx.serialization
│ │ ├── AuthManager.kt # OAuth token lifecycle
│ │ └── RetryPolicy.kt # Exponential backoff
│ ├── jvmMain/kotlin/ # JVM-specific HTTP engine
│ ├── androidMain/kotlin/ # Android-specific HTTP engine
│ └── iosMain/kotlin/ # iOS-specific HTTP engine (future)
│
└── 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 | JVM, Android, iOS (future) | 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, kotlinx.coroutines, kotlinx.serialization |
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 | Deferred, core works from any JVM backend without wrappers | Future gateway-sdk-server module if demand warrants |
| 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, 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 (future via Kotlin/Native) |
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:0.2.0")
}
// Android, core plus android extensions
dependencies {
implementation("com.myriad.gateway:gateway-sdk-core:0.2.0")
implementation("com.myriad.gateway:gateway-sdk-android:0.2.0")
}
Use the version matching the target release tag. The 0.2.x line is the
first pre-production line shipped with the finalized artifact shape
(single jar per module, Maven-standard POM closure). 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()
.clientId("client_peakpos")
.clientSecret("secret_peakpos")
.environment(Environment.PRODUCTION)
.build()
val androidClient = GatewayAndroidClient(androidConfig.toGatewayConfig())
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",
merchantId = "loc_abc123", // legacy field name; pass the concrete location id
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.TransactionCompleted -> handleCompletion(event.transaction)
is WebhookEvent.BatchSettled -> handleSettlement(event.batch)
is WebhookEvent.PaymentFailed -> 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 androidClient =
GatewayAndroidClient(
GatewayAndroidConfig.Builder()
.clientId("client_peakpos")
.clientSecret("secret_peakpos")
.environment(Environment.STAGING)
.build()
.toGatewayConfig(),
)
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 right place to share the normal Gateway API clients
with iOS, but the repository is not there yet. Today the source tree uses a
KMP-like commonMain / jvmMain layout while the actual shipped target is still
a JVM jar built with kt_jvm_library and JVM Ktor artifacts. Before the Swift
Tap to Pay package can reuse the core SDK, this module needs a real Kotlin
Multiplatform build, platform engine actuals, and an iOS artifact shape
consumable from Swift. The common webhook verifier has already been moved off
JVM-only crypto/time APIs, so the remaining work is primarily build,
platform-engine, and publishing wiring.
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 expected iOS deliverable is an XCFramework or SwiftPM wrapper around the KMP core, with a thin Swift facade where the generated Kotlin/Native API is too awkward for host apps.
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 (future) | Darwin | NSURLSession-based, Kotlin/Native |
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