Skip to main content

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:

  • LocationsApi is the primary surface for location records.
  • OrganizationsApi owns organization-scoped resources and credential profiles.
  • New SDK-facing code should use organizationId and locationId wherever 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

ModuleTargetsPurposeDependencies
gateway-sdk-core-kmpJVM, Android, iOSDTOs, API client, errors, webhook verification, OAuth token managementktor-client, kotlinx.serialization, kotlinx.coroutines
gateway-sdk-androidAndroid onlyAndroid config/client wrapper plus embedded terminal/runtime and optional managed-host toolinggateway-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

OmittedReasonWhere It Lives
SmartConnect implementation packageInternal runtime detail, not a public SDK surfacegateway-sdk-android/src/main/kotlin/.../smartconnect
Spring auto-configurationNot shipped; the core JVM artifact works from backend services without wrappersAdd explicit application wiring in the service that owns the integration
TransIT API clientInternal library for gateway microservices onlylibs/transit-client/
XTMS API clientInternal library for Merchant Onboarding Service onlylibs/xtms-client/

3. Distribution

PropertyValue
Group IDcom.myriad.gateway
Artifactsgateway-sdk-core-kmp, gateway-sdk-core-kmp-android, gateway-sdk-android
LanguageKotlin 2.2+ (KMP)
BuildBazel (publishes to Google Artifact Registry)
Min Android API26 (Android 8.0), for gateway-sdk-android
KMP TargetsJVM 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:

  • GatewayAndroidConfig and GatewayAndroidClient in com.myriad.gateway.sdk.android for 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 MethodUse 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

PlatformKtor HTTP EngineNotes
JVMCIO or OkHttpServer-side, full coroutine support
AndroidOkHttpAndroid-optimized, respects connectivity state
iOSDarwinShared 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