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

ArtifactWhat it isWhere it goes
box-arr AARsThe reusable POS libraries any Android host embeds.Maven at https://maven.withpotter.com (group com.withpotter.box).
Demo APKA 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.apk
⚠️

The 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:

settings.gradle.kts
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven { url = uri("https://maven.withpotter.com/releases") }
  }
}
app/build.gradle.kts
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

ModuleResponsibility
box-commerceTop-level entry point (BoxArr); builds a client and exposes enrollment state helpers.
arr-coreClient interface, domain models, and the enrollment contract.
arr-sessionEnrollment, cashier sessions/shifts, and till sales.
arr-catalogCatalog sync and browsing.
arr-orderCart and order building.
arr-paymentPayment capture.
arr-cryptoHardware-backed signing keys (Android Keystore) and request signing.
box-arr-uiJetpack 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:

MainActivity.kt
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.payments

Key properties of the embedding model:

  • Headless core. BoxArr.initialize(...) returns a BoxArrClient exposing enrollment, sessions, catalog, and payments. The host calls these from its own view models / screens.
  • The host owns payments. BoxConfig.paymentBridge is 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-ui is 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 skips box-arr-ui.

Configuration reference

BoxConfig is immutable; build it once and hand it to BoxArr.initialize.

FieldTypeDefaultNotes
backendUrlStringrequiredBase URL of the Box engine, e.g. https://api.your-box.example.
tenantIdStringrequiredThe tenant/merchant this device belongs to.
paymentBridgePaymentBridgerequiredHost payment delegate; see Payments. The library is headless and never enters the card-data environment.
environmentEnvironmentPRODUCTIONPRODUCTION or SANDBOX.
tlsPinsList<String>[]SHA-256 SPKI pins (sha256/…) for certificate pinning to backendUrl. Empty disables pinning; not recommended in production.
cloudProjectNumberLong?nullGoogle Cloud project number for Play Integrity. Required in production (unless you supply a custom integrityProvider); the library requests an integrity token at enrollment.
integrityProviderIntegrityProvider?nullCustom 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?.
enableHttpLoggingBooleanfalseEnables 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:

Theme.kt
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:

MainActivity.kt
setContent {
  BoxStoreTheme(AcmeTheme) {
    // Your host shell + BoxStorefront(...) / BoxTerminalLink(...) render branded.
  }
}
KnobTypeWhat it controls
brandBoxBrandThe 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).
typefaceBoxTypefacefontFamily (body), numericFamily (prices), displayFamily (headlines), and a uniform scale for high-legibility/kiosk deployments.
shapesBoxShapesCorner radii for cards/controls/pills. Presets: Default, Sharp, Rounded.
layoutStoreLayoutThe 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.

Next steps