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.
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.3") // top-level entry point
implementation("com.withpotter.box:box-arr-ui:0.1.0-alpha.3") // 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.