Skip to main content

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:

  • LocationsApi is the primary surface for location records.
  • OrganizationsApi owns organization-scoped resources and credential profiles.
  • MerchantsApi remains as a deprecated alias for legacy callers.
  • Existing merchantId request fields still carry the concrete location id on the wire.

Safe migration posture:

  • prefer client.locations and client.organizations in new code
  • treat client.merchants as a compatibility bridge only
  • keep legacy merchantId wording 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

ModuleTargetsPurposeDependencies
gateway-sdk-coreJVM, Android, iOS (future)DTOs, 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, kotlinx.coroutines, kotlinx.serialization

Not Included

OmittedReasonWhere It Lives
SmartConnect implementation packageInternal runtime detail, not a public SDK surfacegateway-sdk-android/src/main/kotlin/.../smartconnect
Spring auto-configurationDeferred, core works from any JVM backend without wrappersFuture gateway-sdk-server module if demand warrants
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, 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 (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:

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

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