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 live in
gateway-sdk-android; Android-core and SoftPOS implementation classes are
embedded and must not be added separately.
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
}
canTakePayment reflects whether the current reader/runtime is available for a
new attempt. Pending confirmation, reconciliation, and server-replay records
remain visible through the recovery state and banners, but they do not lock an
unrelated payment. A deterministic pre-dispatch failure is recorded as terminal;
only an ambiguous failure after dispatch remains pending for reconciliation.
The SDK does not currently persist PAN, CVV, track data, or keyed-entry payloads for offline authorization. Encrypted post-approval server replay is not the same as offline card-data capture. Offline keyed-entry store-and-forward remains disabled until its PCI scope, acquirer authorization, retention, fraud controls, and certified UI contract are approved.
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). It does not depend on embedded SoftPOS implementation classes or
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.
Customer Maven Installation
The Android SDK ships from a single workflow, sdk-publish.yml. Every push to
main publishes a Maven-only, immutable dev-<commit> version; GitHub releases
publish the version derived from their release tag.
The customer Maven repository is
https://us-east1-maven.pkg.dev/pinpoint-gateway/gateway-client-maven.
In settings.gradle.kts, configure the authenticated repository:
import org.gradle.authentication.http.HttpHeaderAuthentication
import org.gradle.api.credentials.HttpHeaderCredentials
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://us-east1-maven.pkg.dev/pinpoint-gateway/gateway-client-maven")
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = "Bearer ${System.getenv("GOOGLE_ACCESS_TOKEN")}"
}
authentication {
create<HttpHeaderAuthentication>("header")
}
}
google()
mavenCentral()
}
}
Before running this configuration, request an explicit repository-reader principal for the customer Maven repository. Bearer/ADC access is required; there is no anonymous download path.
For local development, obtain the bearer token with:
export GOOGLE_ACCESS_TOKEN=$(gcloud auth print-access-token)
Choose one consumer path; do not combine these dependencies. JVM consumers use
only gateway-sdk-core-kmp. Android consumers use gateway-sdk-android
instead, and must not add gateway-sdk-core-kmp, because the self-contained
Android AAR would duplicate com.myriad.gateway classes.
JVM consumer
dependencies {
implementation("com.myriad.gateway:gateway-sdk-core-kmp:<version>")
}
Android consumer
dependencies {
implementation("com.myriad.gateway:gateway-sdk-android:<version>")
}
Android consumer using the optional NearPay rail
dependencies {
implementation("com.myriad.gateway:gateway-sdk-android:<version>")
implementation("com.myriad.gateway:gateway-sdk-android-nearpay:<version>")
}
Android-core and SoftPOS implementation classes are embedded in
gateway-sdk-android and must not be added separately. NearPay is optional:
include gateway-sdk-android-nearpay only alongside the base Android artifact
for the NearPay rail. Managed runtime discovery finds it when Gateway selects
TTP_NEARPAY; do not add a separate NearPay repository.
Deferred Integration Work
This distribution does not activate CardPointe (#1552), alter Play Integrity provider ownership (#1694), or implement offline store-and-forward/manual-entry capture (#1695). CardPointe awaits customer credential creation and a Fiserv request; provider-mode Play Integrity awaits Google Play SDK Console approval; manual-entry capture remains outside this release. These linked issues remain deferred integration work, not features delivered by this SDK publication.
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 the internal SoftPOS source module, outside
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.