Skip to content

POS Architecture

Core Principle

Emali2 POS is a bounded operational system, not the source of truth for platform money.

There are two ledgers:

  • Platform ledger
  • Wallet money
  • Till balances
  • Settlements and transfers
  • Organization-portal financial truth
  • POS operational tender ledger
  • Cash
  • External card
  • External mobile money
  • QR
  • Loyalty redemption
  • Emali tender references

Only Emali tenders create or reconcile against the platform ledger.

Bounded Contexts

flowchart LR
  ORG["Main Platform<br/>Organizations, users, units, wallet ledger"] --> POS["POS Service<br/>Inventory, sales, sessions, receipts"]
  POS --> BO["POS Backoffice Web"]
  BO -->|"Organization, card-present, and onboarding APIs"| ORG
  POS --> CW["POS Cashier Web"]
  POS --> AND["POS Android"]
  POS --> OBJ["Private S3-compatible storage<br/>Item images, receipt assets"]
  POS --> EXT["External Integrations<br/>Acquirer, ERP, ecommerce, webhooks"]

Entity Scope

  • Organization
  • Legal or commercial parent
  • OrganizationUnit
  • Flat child operational unit
  • MERCHANT, AGENT, BILLER
  • Merchant unit
  • POS-enabled store
  • Inventory-enabled store
  • Owns terminals, sessions, store stock, and store reporting

Agent and merchant units can belong to the same organization but remain operationally separate.

Client Strategy

  • Use POS Cashier Web for countertop supermarket lanes.
  • Use POS Android for handheld, mobile, or supervisor-driven flows.
  • Keep inventory and merchant operations in POS Backoffice Web.

Data Strategy

  • Product master is organization-scoped.
  • Store assortment, price overrides, and stock are merchant-unit scoped.
  • Sessions and sales are terminal scoped.
  • Image bytes are stored in private S3-compatible storage and exposed through opaque, managed URLs on the POS API host.

Database identity boundary

POS owns a separate PostgreSQL database and never reads a platform database. The runtime POS service authenticates as an application role that owns its database and pos schema so Flyway and transactional DML work, but it cannot create databases or roles, replicate, bypass row security, or act as a superuser. A separately stored bootstrap-administrator credential is available only to PostgreSQL and guarded one-shot administration Jobs. Physical backup uses a third SCRAM LOGIN REPLICATION role with no application-table privileges. These identities, Secrets, and network paths are independent of country, fiscal authority, and payment provider.

Terminal identity-session boundary

Android, browser and dedicated hardware terminals prove possession of their enrolled device key on protected mutations. After proof succeeds, POS records only the opaque OIDC session ID and subject against that terminal, never the access or refresh token. Terminal revocation atomically queues every bound session, then uses the POS machine identity to ask Core to verify subject ownership and terminate that exact identity-provider session. A PostgreSQL dispatcher retries temporary failures, so local terminal revocation remains committed even when IAM is briefly unavailable.

If the enrolled device and its private key are unavailable, an authorized manager can invoke the separate idempotent recovery boundary. It locks the terminal and named open shift, automatically voids only fully unpaid open carts with no unresolved electronic tender, submits the shift, seals the signed close-of-day pack and revokes the device atomically. Partially paid or uncertain electronic sales fail closed for explicit reconciliation. This control is country-neutral and does not inspect or select any payment or fiscal adapter.

Offline Strategy

  • POS Android
  • Offline-first
  • Local Room storage
  • Sync and replay later
  • POS Cashier Web
  • Online-first
  • Session recovery from backend
  • Browser local persistence only for operator convenience, not as the authoritative offline store
  • POS Backoffice
  • Online-only in v1
  1. Keep current POS routes stable.
  2. Continue extracting POS into emali2-pos-service.
  3. Publish POS domain events:
  4. sale.created
  5. sale.paid
  6. sale.refunded
  7. receipt.generated
  8. shift.opened
  9. shift.closed
  10. stock.changed
  11. Add service-account integrations between POS and the main platform.