Gateway Android SDK
Overview
gateway-sdk-android has three package tiers:
com.myriad.gateway.sdk.androidStandard Android wrapper for the plain Gateway admin/pay HTTP clients.com.myriad.gateway.sdk.android.hostThe primary terminal/runtime SDK surface for apps that need to support both Nexgo and Tap to Pay without caring that SmartConnect exists internally.com.myriad.gateway.sdk.android.referencehostOptional managed-host tooling for registration, heartbeat shaping, workspace snapshots, and operator-facing device-management flows.
Everything under com.myriad.gateway.sdk.android.smartconnect is internal
implementation detail and should not be imported by app code.
Choose Your Path
HTTP-only Android app
Use:
GatewayAndroidConfigGatewayAndroidClient
This is just the Android-friendly shell around the core Kotlin SDK.
Embedded terminal app
Use:
GatewayAndroidAppGatewayAndroidHostRuntimeConfigGatewayAndroidDeviceRuntimeConfigGatewayAndroidHostStateGatewayAndroidUiState
This is the default integration path for a POS app that needs one API surface for both Nexgo and Tap to Pay.
Managed host / device-management app
Use referencehost only if the app also needs:
- device registration payloads
- heartbeat payloads
- workspace snapshots
- command or operator-facing host tooling
Primary types:
GatewayAndroidReferenceHostGatewayAndroidReferenceHostLauncherGatewayAndroidReferenceHostAppGatewayAndroidDeviceManagementSurface
Package Structure
com.myriad.gateway.sdk.android
Purpose:
- normal Gateway HTTP clients on Android
- lifecycle-friendly wrapper over the core Kotlin SDK
Files:
GatewayAndroidConfig.ktGatewayAndroidClient.kt
com.myriad.gateway.sdk.android.host
Purpose:
- public terminal/runtime API
- public state, snapshot, config, transaction, recovery, and bootstrap models
The main entrypoint is GatewayAndroidApp.
Important model groups:
- runtime and device selection
- host state and UI state
- recovery and replay inspection
- transport and bootstrap primitives
com.myriad.gateway.sdk.android.referencehost
Purpose:
- managed-host and device-management tooling
- registration and heartbeat request shaping
- workspace and operator snapshot helpers
This package is optional. Treat it as an additive tooling layer, not the default way to start taking payments.
The public device-management requirement models also live in
gateway-sdk-android itself, so Android consumers do not need a separate
soft-pos-sdk artifact just to inspect readiness, attention level, or queued
device-management work.
com.myriad.gateway.sdk.android.host.internal
Purpose:
- seam/orchestration layer behind the public API
These classes exist to keep the public package small:
GatewayAndroidHostAdapterGatewayAndroidAppSessionGatewayAndroidHostSessionGatewayAndroidHostManagerGatewayAndroidHostIntegration
They should not be part of the integration story.
com.myriad.gateway.sdk.android.smartconnect
Purpose:
- runtime implementation
- transport
- recovery persistence
- replay durability
- protocol-specific storage and state
Apps should not import this package directly.
Core Concepts
GatewayAndroidHostRuntimeConfig
This is the base runtime wiring:
filesDir- transport
- storage location strategy
- terminal platform info collector
- recovery backfill client
- clock / operation id hooks
- gateway name
The default terminalPlatformInfoCollector best-effort gathers Android device
metadata for device-management payloads, so apps do not need to hand-build the
full terminal profile just to register a device or send heartbeats.
GatewayAndroidDeviceRuntimeConfig
This is how the app declares device family:
GatewayAndroidDeviceFamily.NEXGOGatewayAndroidDeviceFamily.TTP
That config resolves into GatewayAndroidDeviceRuntimeSelection, which keeps
the selected runtime label and gateway name consistent.
GatewayAndroidApp
This is the main product surface.
It owns:
- startup / reload
- transaction execution
- pending-operation recovery
- offline server replay queue management
- app snapshot/state projection
GatewayAndroidAppSnapshot
This is the “what should the app render right now?” model.
It includes:
- runtime readiness
- selected device family/runtime
- recovery inspection
- replay backlog state
- terminal-device snapshot
- device-management snapshot when applicable
GatewayAndroidTerminalPlatformInfo
This is now primarily an override model, not a required full payload.
The SDK auto-collects best-effort Android metadata for registration, heartbeat, and managed-host snapshots:
- manufacturer
- model
- Android version
- screen resolution
- timezone
- battery / charging status when available
- network type when available
If the app wants to override specific fields, pass only those fields in
GatewayAndroidTerminalPlatformInfo; the SDK merges them on top of the
collected metadata.
GatewayAndroidHostState and GatewayAndroidUiState
Use these for app rendering:
GatewayAndroidHostStateruntime truth: lifecycle, pending recovery, replay backlog, attention levelGatewayAndroidUiStateapp-facing banner and actionable recovery items
Storage and Key Material
The public bootstrap primitives are now real GatewayAndroid* types:
GatewayAndroidStoreLocationStrategyGatewayAndroidDefaultStoreLocationStrategyGatewayAndroidStoragePathsGatewayAndroidKeyProviderGatewayAndroidRootKeySourceGatewayAndroidDerivedKeyProviderGatewayAndroidInitializer
That means app code no longer has to import SmartConnect-branded bootstrap
types just to configure local storage or encryption keys. The static
key/root-key fixtures live in the unpublished :gateway-sdk-android-testing
library and are not part of the published AAR; production key material is the
Keystore-backed provider the managed entry point wires by default.
Managed Production Runtime Setup
Production hosts integrate through one approved entry point:
GatewayAndroidApp.withManagedRuntime(...). Gateway Cloud selects the
runtime family (NEXGO, TTP_NATIVE, TTP_NEARPAY); the SDK owns device
enrollment (Play Integrity + Android Keystore proof-of-possession),
credential storage/refresh, key material, and client authorization. The
host never branches on the device family and never handles raw credential
tokens.
import com.myriad.gateway.sdk.android.cardpresent.deviceauth.GatewayAndroidManagedCredentialConfig
import com.myriad.gateway.sdk.android.host.GatewayAndroidApp
import com.myriad.gateway.sdk.android.host.GatewayAndroidHostCapabilities
// withManagedRuntime and ensureStarted are suspend functions — call them from a
// coroutine (e.g. lifecycleScope.launch { ... } at app start).
suspend fun startGateway(): GatewayAndroidApp {
val app =
GatewayAndroidApp.withManagedRuntime(
host = GatewayAndroidHostCapabilities(applicationContext) { currentActivity },
gatewayClient = gatewayClient, // backend-vended bearer tokenProvider
credentialConfig =
GatewayAndroidManagedCredentialConfig(
organizationId = "org_...",
cloudProjectNumber = GCP_PROJECT_NUMBER, // Play Integrity, from build config
),
) // NearPay deployments just bundle gateway-sdk-android-nearpay — the SDK
// discovers its runtime module automatically when Gateway Cloud selects it
val started = app.ensureStarted()
if (started.canTakePayment) {
// Render payment-ready UI.
}
return app
}
Lab / Test Fixture Setup (never production)
Lower-level runtime constructors have been removed from the public SDK
surface — GatewayAndroidApp.withManagedRuntime(...) is the only way to
create a runtime. In-repo lab and test fixtures reach the SDK-internal
constructors through Bazel associates (friend-module) access, and the
static credential/root-key sources live in the separate, unpublished
:gateway-sdk-android-testing library. Neither is reachable from a consumer
app: there is no opt-out.
Managed Host Example
Managed-host tooling wraps an existing managed app — it is not a second way to construct a runtime:
import com.myriad.gateway.sdk.android.host.GatewayAndroidTerminalPlatformInfo
import com.myriad.gateway.sdk.android.referencehost.asReferenceHost
val referenceHost = app.asReferenceHost() // app from GatewayAndroidApp.withManagedRuntime(...)
val started = referenceHost.ensureStarted()
val registration = started.registrationRequest(deviceId = "device-123")
val managedHost =
started.managedHostSnapshot(
deviceId = "device-123",
platformInfo = GatewayAndroidTerminalPlatformInfo(deviceName = "Front Counter"),
)
Recovery Model
The Android runtime supports two operational recovery concepts:
- pending-operation recovery
- offline server replay queue flushing
It does not expose a fake offline terminal authorization mode. The public API is intentionally explicit about that boundary.
Key public types:
GatewayAndroidPendingOperationGatewayAndroidRecoveryInspectionGatewayAndroidRecoveryResolutionGatewayAndroidRecoveryItem
Migration Rule
For any new Android integration:
- import from
hostfor normal runtime/payment work - import from
referencehostonly for device-management tooling - do not import from
smartconnect
If app code knows smartconnect exists, the package boundary is being used
incorrectly.
Sample Path
The shipped sample lives under com.myriad.gateway.sdk.android.peakpay.
It demonstrates:
GatewayAndroidAppas the primary runtime surfacereferencehostas optional managed-host tooling layered on top- device-management payloads built from SDK-collected Android metadata with optional app overrides
Consumer Smoke Target
In addition to the sample, gateway-sdk-android ships a consumer-path smoke
target that models what a downstream monorepo POS app would look like when
adopting the published artifact:
- source:
sdks/kotlin/gateway-sdk-android/src/consumerSmoke - tests:
sdks/kotlin/gateway-sdk-android/src/consumerSmokeTest - Bazel targets:
//sdks/kotlin/gateway-sdk-android:consumer-smokeand//sdks/kotlin/gateway-sdk-android:consumer-smoke-test
This module depends only on :gateway-sdk-android (the same artifact we
publish), does not depend on soft-pos-sdk, and does not import anything
from the smartconnect or host.internal packages. If a public-surface
rename accidentally leaks an internal type onto an app-facing API, the
consumer smoke target stops compiling in CI.
Consumer smoke covers the documented adoption paths:
- HTTP-only client via
GatewayAndroidClient/GatewayAndroidConfig. - Managed production runtime via
GatewayAndroidApp.withManagedRuntime— the approved production path. - Embedded terminal + managed-host lab paths (exercised only from
src/consumerSmokeTest, which uses SDK-internal constructors via Bazel friend-module access — these are not part of the public surface).
Run it locally with bazel test //sdks/kotlin/gateway-sdk-android:consumer-smoke-test.
Release Wiring
The Android SDK ships from a single workflow, sdk-publish.yml. A release
lands artifacts in two places:
| Destination | What goes there | Version resolution |
|---|---|---|
Google Artifact Registry (us-east1-maven.pkg.dev/pinpoint-gateway/gateway-maven) | gateway-sdk-android-{VERSION}.aar + POM, gateway-sdk-core-kmp-{VERSION}.jar + POM, and gateway-sdk-core-kmp-android-{VERSION}.aar + POM | Git tag on HEAD → SDK_VERSION_OVERRIDE → fallback in sdks/kotlin/publishing/version.bzl |
| GitHub Release assets | gateway-sdk-android-{VERSION}.aar + .pom, gateway-sdk-core-kmp-{VERSION}.jar + .pom, gateway-sdk-core-kmp-android-{VERSION}.aar + .pom, plus iOS equivalents | Same resolution. POMs are rewritten with the resolved version before attach so they match what Artifact Registry served. |
Consumers using Gradle (with rules_jvm_external or the standard Gradle
dependency block) against Artifact Registry get the Maven-standard AAR layout
and can resolve the full dependency closure (Android depends on core at the
same version). Consumers pulling from the GitHub Release get the exact same
closure in a single download — no separate Artifact Registry auth required for
bring-up.
The current artifact shape is:
- Maven coordinates:
com.myriad.gateway:gateway-sdk-android,com.myriad.gateway:gateway-sdk-core-kmp, andcom.myriad.gateway:gateway-sdk-core-kmp-android. gateway-sdk-androidis an AAR (Android Archive). The POM declares<packaging>aar</packaging>. AGP and Gradle resolve.aarcoordinates automatically when this packaging type is present.gateway-sdk-core-kmpis the JVM jar for the shared KMP API-client layer.gateway-sdk-core-kmp-androidis the matching Android core AAR used bygateway-sdk-android.- No Maven
classifier, no sources jars in the release bundle.
Ownership Boundaries
This SDK draws a sharp boundary between what gateway owns and what the monorepo POS app owns. Treat the table below as the single source of truth when adding a new capability.
| Area | Gateway owns (this SDK / server) | Monorepo POS app owns |
|---|---|---|
| Payment runtime | Card-present runtime, SmartConnect transport, sale/auth/capture/refund/void/tip-adjust mapping, timeout/receipt/processor/terminal config, pending-operation recovery, post-approval server replay | Cart model, receipt rendering, tender selection UI |
| Device enrollment | Registration/heartbeat payloads, managed-host snapshot shaping, device-bound credential contract | Device identity persistence, operator-facing enrollment UX |
| Readiness / health | GatewayAndroidHostState, GatewayAndroidUiState, attention-level model, device-management capability inspection | Mapping readiness → screen/banner placement, error retries at UI level |
| Attestation / device auth | On-device credential shape, live Play Integrity/Nexgo enrollment and refresh transport, server issuance endpoints | Lifecycle ownership — when to call ensureValid, which operator triggers enrollment |
| Auth / credentials | OAuth client credentials, Firebase Auth token exchange, device-bound credential issuance and refresh | Secure storage of client secret + root key material, per-operator session |
| Recovery UX copy | Recovery item dispositions, state machine, banner types | Strings, localization, screen flow |
| Card data | Never handled by app layer | Never handled by app layer (SDK owns the wire) |
| Nexgo Cloud / XTMS | Merchant/device provisioning, terminal binding, SmartConnect parameter pushes, credential rotation, push status, Gateway runtime commands | MDM/vendor-cloud administration outside explicit Gateway commands |
Rules of thumb:
- If a change affects wire format, network retry semantics, or the persistence of recovery/replay state, it belongs in the SDK.
- If a change affects user-facing copy, navigation, or cross-feature orchestration (tax, split-tender, receipts), it belongs in the monorepo POS app.
- If both sides need to change, the SDK lands the neutral contract first and the app adopts it — never the other way around.
- XTMS app push, firmware/OTA, RKI/certificate distribution, Easy Deploy templates, and fleet MDM functions are not POS app responsibilities and are not generic SDK APIs. They remain vendor-cloud/operations functions unless Gateway exposes them as explicit runtime commands: runtime wipe, credential rotation, reconciliation trigger, SDK state reset, payment-runtime reprovision, SmartConnect prepare, or SmartConnect status.
Device-First Authorization Contract (Peak Pay)
Peak Pay uses a device-first authorization model: payment and device-management traffic is authenticated by an attested device credential rather than a bearer-token client secret. Both the SDK and the server sides are shipped.
What the SDK ships
Contract file:
sdks/kotlin/gateway-sdk-android/src/main/kotlin/com/myriad/gateway/sdk/android/host/GatewayAndroidDeviceAuth.kt
Public types:
GatewayAndroidDeviceCredential— the on-device credential shape (token, attestation id, device id, issued/expires, optional refresh hint).GatewayAndroidDeviceCredentialState—ABSENT/VALID/REFRESH_DUE/EXPIRED/REVOKED.GatewayAndroidDeviceCredentialSnapshot— snapshot the SDK hands to the app for UI/readiness binding.GatewayAndroidDeviceCredentialSource— the pluggable surface through which the SDK obtains and refreshes credentials.
The production implementation is SDK-owned and assembled by
GatewayAndroidManagedDeviceCredentialSource.create(...)
(cardpresent.deviceauth package), or implicitly by
GatewayAndroidApp.withManagedRuntime(host, ...):
- Android Keystore P-256 proof-of-possession signer (private key never leaves secure hardware)
- Play Integrity standard-API attestation (server nonce bound via
requestHash) - core-client transport against the auth-service device endpoints
(
/api/v1/devices/enroll[/nonce],/api/v1/devices/{id}/token/*) - AES-256-GCM encrypted durable credential storage keyed by an Android Keystore key — the app never persists raw credential tokens, expiry fields, or activation seed data
GatewayAndroidStaticDeviceCredentialSource and the stub
attestation/signing providers are test/local fixtures only —
ProductionSourceGuardTest enforces that production SDK code never
references them. Never pass a device credential token as
GatewayConfig.accessToken; GatewayAndroidClient fails fast if one is
detected there.
Apps can bind GatewayAndroidDeviceCredentialSnapshot.state into the
same readiness / capability surface they already use for
GatewayAndroidHostState — REVOKED/EXPIRED stop payment just like an
outstanding operator recovery does.
The kernel-specific attestation evidence and verification anchors continue
to live in //sdks/kotlin/soft-pos-sdk, which intentionally stays out of
gateway-sdk-android's dependency graph.
Nexgo terminal activation diagnostics
Normal Android enrollment requires Play Integrity. Gateway-managed Nexgo
terminal enrollment is the exception: GatewayAndroidLiveDeviceCredentialSource
automatically sends attestationMode = "NEXGO_TERMINAL" when the enrollment
uses platform = "ANDROID", linkedCardPresentProvider = "NEXGO", and a
Nexgo/XTMS terminal serial in linkedCardPresentSerialNumber. Raw API callers
can send the same mode explicitly. The request must also include a Gateway
locationId. Gateway then validates that the requested location belongs to the
organization and that an active transit_devices inventory row binds the
terminal to that same location.
Attestation-policy enrollment rejections return the generic 401 device-attestation failure payload. The detailed reason is logged server-side with one of these codes:
PLAY_INTEGRITY_REQUIRED— the request used the normal platform path without attestation evidence. This is expected for non-terminal Android devices and for Nexgo clients that did not opt into terminal activation.NEXGO_MISSING_LOCATION— the request omittedlocationId, or Gateway cannot resolve a merchant/location row for it.NEXGO_MISSING_XTMS_BINDING— Gateway has no active Nexgo/XTMS-backedtransit_devicesinventory row for the submitted terminal serial.NEXGO_MERCHANT_LOCATION_MISMATCH— the location is outside the requested organization, inactive, or the terminal inventory row is bound to another location.NEXGO_UNSUPPORTED_TERMINAL_TYPE— the request is not Android/Nexgo, or the inventory row is for a non-terminal device type.
API-key / OAuth coexistence
The device-first path is additive. API-key and OAuth client-credentials authentication remain supported on the Android SDK; the device credential is layered on top for card-present Peak Pay flows that the processor requires a device-bound token for. API keys remain supported for the Android SDK flows that use them today.