05 · Money spine (POS → Invoice)
The architectural backbone. Almost every sale should end as an Invoice through Core Invoicing.
Canonical sale flow
flowchart TD
A[Platform session + tenant] --> B{Register ready?}
B -->|Modern POS| C[Open shift + PIN staff]
B -->|Some modes| D[Cart may still require session]
C --> E[POST /api/pos/carts]
D --> E
E --> F[POST carts/:id/items]
F --> G[Optional: seatId/tableId metadata]
G --> H[Tender cash/card/crypto/gift]
H --> I[POST carts/:id/finalize]
I --> J[CoreInvoicingService.createPOSSaleInvoice]
J --> K[(Invoices + line items tenant main)]
I --> L[Kitchen tickets if preparable stations]
I --> M[Inventory / recipe consumption]
I --> N[Transaction tracking / analytics hooks]
H --> O{Crypto?}
O -->|yes| P[Crypto invoice + watchers]
P --> I
Deep trace: OAO Appendix B · backend/routes/pos.routes.js · coreInvoicing.service.js
Who reuses this path
| Client | Cart API | Notes |
|---|---|---|
| ModernPOS / StandardPOS | /api/pos/* | Primary |
| MobilePOS / Kiosk.jsx | Same POS cart API + session | Not the unattended kiosk router by default |
| Storefront | POS createCart + finalize | Good reuse — no parallel checkout stack |
/api/kiosk · /api/mobile | Separate routers | Device-auth for unattended; hardened with secrets |
Retired anti-pattern
Legacy Stripe/Coinbase router that wrote a separate
payments table was disabled
(ENABLE_LEGACY_3P=0). Do not resurrect outside invoicing.
Register ceremony
stateDiagram-v2
[*] --> Closed
Closed --> Open: POST /register/open + float
Open --> Authenticated: POST /register/authenticate PIN
Authenticated --> Selling: canTransactOnRegister
Selling --> Authenticated: idle
Authenticated --> Closed: POST /register/close
Open --> Closed: admin-close
Hardcoded for all Modern POS tenants today — may be heavy for solo farm stands (product question, not silent bug).
Full POS inventory
Modes, Mobile parity, register, and API naming traps:
14 · POS & Mobile POS