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,BILLERMerchant 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 Webfor countertop supermarket lanes. - Use
POS Androidfor 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.
Platform projection event boundary
Core remains authoritative for organizations, merchant stores and staff.
POS accepts only a minimal PLATFORM_POS_PROJECTION_CHANGED trigger at the
machine-only internal API. Acceptance requires both a pos-api audience OAuth
client-credentials token and a versioned HMAC signature. The event is stored in
the POS inbox by eventId; exact replay is harmless and reuse with different
content is rejected.
The event never carries trusted projection fields. A lease-based dispatcher
claims it with PostgreSQL SKIP LOCKED, then retrieves the complete scoped
snapshot through the existing Core APIs and transactionally replaces the local
projection. Failed work is retried with sanitized diagnostics. The independent
15-minute reconciliation scan remains enabled, so a lost event cannot leave a
merchant permanently stale. This protocol contains no country, payment rail,
tax authority or fiscal-device naming.
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
Recommended Evolution
- Keep current POS routes stable.
- Continue extracting POS into
emali2-pos-service. - Publish POS domain events:
sale.createdsale.paidsale.refundedreceipt.generatedshift.openedshift.closedstock.changed- Add service-account integrations between POS and the main platform.