Android
The box-arr SDK is the Android client for the Box Retail Rails.
It is a set of libraries a host embeds in its own Android app to enroll a
device to a single merchant, sync the catalog, run a cart, take payments, and
manage cashier sessions against the box’s retail-rails (RR) engine.
The reusable libraries publish as Maven AARs under the com.withpotter.box
group; a ready-to-install demo APK is available for evaluation. The RR
engine the SDK talks to is the @withpotter/rr-engine package composed into
the box engine.
What ships
| Artifact | What it is | Where it goes |
|---|---|---|
box-arr AARs | The reusable POS libraries any Android host embeds. | Maven at https://maven.withpotter.com (group com.withpotter.box). |
| Demo APK | A ready-to-run sample till built from the libraries. | Published for anonymous download on each release. |
The AARs and the demo APK are versioned together and released as a set. The demo app is never published as a library; it ships only as the APK.
Publishing or updating a Box does not replace Android apps already installed on Till hardware. Upgrade the Box engine/dashboard first, then update the host app’s Maven dependencies or install the matching demo APK. Existing operator PINs remain valid. See Live Box upgrades.
Install the demo APK
Download the latest demo APK from the Box downloads page (no auth required) and sideload it onto an Android device:
adb install -r box-demo.apkThe demo APK is a debug build for evaluation. It connects to a preconfigured engine URL baked in at build time; point it at a reachable box engine to test against live data.
Consume the libraries
Add the Potter Maven repository and depend on the modules you need. The
libraries publish under the com.withpotter.box group:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.withpotter.com/releases") }
}
}dependencies {
implementation("com.withpotter.box:box-commerce:0.1.0-alpha.4") // top-level entry point
implementation("com.withpotter.box:box-arr-ui:0.1.0-alpha.4") // Compose till UI
}Modules
| Module | Responsibility |
|---|---|
box-commerce | Top-level entry point (BoxArr); builds a client and exposes enrollment state helpers. |
arr-core | Client interface, domain models, and the enrollment contract. |
arr-session | Enrollment, cashier sessions/shifts, and till sales. |
arr-catalog | Catalog sync and browsing. |
arr-order | Cart and order building. |
arr-payment | Payment capture. |
arr-crypto | Hardware-backed signing keys (Android Keystore) and request signing. |
box-arr-ui | Jetpack Compose UI: store screen, checkout, and sales history. |
How the library works
box-arr is a headless SDK that runs inside the host’s own Android app, not
a standalone app you install. A bank (or ISV) embeds the AARs into their existing
Android application; that app owns the Activity/Application lifecycle,
navigation, and screens. The core library ships no UI; it exposes a client
your code drives.
One call wires every feature module together and returns a BoxArrClient:
val box = BoxArr.initialize(
context,
BoxConfig(
backendUrl = "https://api.your-box.example", // the box engine
tenantId = "acme-coffee", // the merchant this device serves
paymentBridge = HostPaymentBridge(), // your certified payment SDK (required)
cloudProjectNumber = 123456789L, // Play Integrity (required in production)
tlsPins = listOf("sha256/…"), // optional certificate pinning
),
)
// box.enrollment · box.sessions · box.catalog · box.paymentsKey properties of the embedding model:
- Headless core.
BoxArr.initialize(...)returns aBoxArrClientexposingenrollment,sessions,catalog, andpayments. The host calls these from its own view models / screens. - The host owns payments.
BoxConfig.paymentBridgeis a required delegate; the library never talks to a card reader itself. Your app plugs in its certified payment SDK; the library orchestrates the sale and posts the signed result to the engine. - Every call is signed. The SDK generates a hardware-backed key in the Android Keystore at enrollment and signs each till → engine request with it, so the engine cryptographically trusts the device (see Device linking).
- Resume cheaply at launch.
BoxArr.isEnrolled(context)is a cheap (SharedPreferences + Keystore) check the host can call on the main thread to decide whether to resume a linked terminal or run the pairing ceremony, without building the full client. - Bring your own UI, or use the kit.
box-arr-uiis an optional Compose module: a host that supplies no screens gets a fully-rendered, brand-themed store (BoxStorefront), a pairing screen (BoxTerminalLink), checkout, and sales history for free. A host that wants its own look drives the headless client directly and skipsbox-arr-ui.
Configuration reference
BoxConfig is immutable; build it once and hand it to BoxArr.initialize.
| Field | Type | Default | Notes |
|---|---|---|---|
backendUrl | String | required | Base URL of the Box engine, e.g. https://api.your-box.example. |
tenantId | String | required | The tenant/merchant this device belongs to. |
paymentBridge | PaymentBridge | required | Host payment delegate; see Payments. The library is headless and never enters the card-data environment. |
environment | Environment | PRODUCTION | PRODUCTION or SANDBOX. |
tlsPins | List<String> | [] | SHA-256 SPKI pins (sha256/…) for certificate pinning to backendUrl. Empty disables pinning; not recommended in production. |
cloudProjectNumber | Long? | null | Google Cloud project number for Play Integrity. Required in production (unless you supply a custom integrityProvider); the library requests an integrity token at enrollment. |
integrityProvider | IntegrityProvider? | null | Custom attestation source that overrides the default Play Integrity provider, useful for tests or hosts with their own attestation flow. Implement suspend fun requestToken(nonce): String?. |
enableHttpLogging | Boolean | false | Enables OkHttp request/response body logging. Never enable in production. |
Money is kobo (the smallest NGN unit) end-to-end. Set tlsPins and a
production cloudProjectNumber before shipping, and keep enableHttpLogging
off outside of local debugging.
Payments are the host’s
PaymentBridge is a single-method contract your app implements so it can run
its own certified payment flow; the library orchestrates the sale but never
touches PAN, track data, PIN, CVV, or ARQC:
fun interface PaymentBridge {
suspend fun requestPayment(request: PaymentRequest): PaymentOutcome
}The library populates PaymentRequest.orderId and reference before calling
your bridge; you supply the outcome. PaymentOutcome carries a status
(SUCCESS / FAILED / PENDING / ABANDONED), the gatewayResponse, the
Terminal that completed it, and an optional sanitized Authorization
(masked card metadata only). The library signs and posts that result to the
engine; the raw payment credentials never leave the host’s certified SDK.
Branding the terminal
When you use the box-arr-ui screens, the entire UI is driven by a single
StorefrontTheme: colors, type, corner style, and catalog layout. Nothing
is hardcoded in the screens (every color is a named role), so a re-skin touches
only the theme.
The fastest path is to derive a whole coherent palette from one brand color:
import androidx.compose.ui.graphics.Color
import com.withpotter.box.ui.theme.*
val AcmeTheme = StorefrontTheme(
// One seed color → primary, containers, surfaces, contrast (light or dark).
brand = BoxBrand.fromSeed(Color(0xFF1B7F5A)),
// Optional: the bank's own fonts and a global size scale.
typeface = BoxTypeface(fontFamily = Inter, displayFamily = Fraunces, scale = 1f),
// Optional: corner style. Default, Sharp (8dp), or Rounded.
shapes = BoxShapes.Sharp,
// Optional: catalog layout.
layout = StoreLayout.GRID,
)Then wrap the Box UI once, near the top of your Compose tree:
setContent {
BoxStoreTheme(AcmeTheme) {
// Your host shell + BoxStorefront(...) / BoxTerminalLink(...) render branded.
}
}| Knob | Type | What it controls |
|---|---|---|
brand | BoxBrand | The full color palette. Use BoxBrand.fromSeed(color) to derive it from one seed (pass dark = true for a dark palette), or construct every role by hand. Defaults to Potter blue (BoxBrand.Default). |
typeface | BoxTypeface | fontFamily (body), numericFamily (prices), displayFamily (headlines), and a uniform scale for high-legibility/kiosk deployments. |
shapes | BoxShapes | Corner radii for cards/controls/pills. Presets: Default, Sharp, Rounded. |
layout | StoreLayout | The catalog presentation (e.g. GRID). |
Because fromSeed derives surfaces, containers, and readable on-colors from
the seed automatically (with WCAG-aware contrast), most terminals only ever set
a brand color. Components read tokens through BoxTheme.brand / BoxTheme.shapes,
so custom host UI can paint with the same palette.