Plain-English summary
Customers ask to "split the bill" in two different senses, and Foodops handles them very differently today. "We'll pay with cash AND card" — splitting one bill across multiple payment methods — works at the counter right now: the payment screen lets the cashier stack several payment lines until the total is covered. "We each want our own bill" — splitting one order into separate per-guest checks, each paid and receipted independently — is fully built in the backend (equal split or split-by-item, per-part invoices, GST and service-charge distribution, multi-terminal sync) but has no shipped screen in the counter app yet. If a customer asks for separate checks today, the practical answer is multi-tender payment or ringing separate orders.
When you'd use this
- A group pays MVR 60 cash + MVR 40 card on one bill → split payment (live today)
- A table of four wants four separate receipts, items assigned per person → bill split by item (backend-ready; UI has landed on main behind a feature flag — see status below)
- "Just divide it equally between us" → equal bill split (backend-ready; UI has landed on main behind a feature flag)
- Two friends at the counter each paying their own items → simplest reliable path today: ring two separate orders
Feature status (verified 2026-06-11)
| Capability | Status | Evidence |
|---|---|---|
| Multi-tender split payment (one bill, several payment methods) | LIVE on the counter POS | Add-payment-row UI in OrderDetailsPanel (selectedPaymentMethods state); multiple SelectedPaymentMethods accepted by /orders/make-payment |
| Bill split — equal parts | UI landed on foodops.counter.app main (offline-first), gated behind FEATURE_BILL_SPLITTING; production rollout unconfirmed |
BillSplitModal/BillSplitSettle + billSplitActions wired into OrderDetailsPanel.tsx (canSplitBill, activeSplit live query) @ 054a5b3..43c6fad |
| Bill split — by item ("each pays their own") | UI landed on main (item-to-guest assignment flow), same flag gate; production rollout unconfirmed | same commit range |
| Per-part payment + per-part invoice/receipt | UI landed on main — paySplitPart wired to BillSplitSettle, with cross-device sync (syncActiveSplitFromServer) and reject-rollback handling |
same commit range |
Counter SplitPaymentManager.tsx component |
Empty placeholder stub | 13-line stub file |
The bill-split backend shipped into hubits-api-sales main around 2026-05 and is wired for real-time multi-terminal use (SignalR broadcasts). As of foodops.counter.app commit range 054a5b3..43c6fad, a Split Bill UI has landed on main inside OrderDetailsPanel.tsx (BillSplitModal, BillSplitSettle, offline-first billSplitActions, an edit-lock while a split is active, cross-device sync via syncActiveSplitFromServer), gated behind the FEATURE_BILL_SPLITTING flag. Whether that flag is on for any given org in production is unconfirmed — verify flag status before telling users the POS has a working Split Bill button.
Walkthrough — split payment (multi-tender, live today)
This is US-POS-005 / US-POS-EDGE-003.
- Open the order → Go to Payment (or settle from the order details panel).
- In the payment screen (the order details panel), add additional payment-method rows. The same method can be added more than once (e.g., two different cards)
[code: foodops.counter.app/src/components/orders/OrderDetailsPanel.tsx:1491-1497 "Add a payment method row — can add same method multiple times (split payments)" @ main]. - Enter the tendered amount per row (e.g., Cash 60, Card 40). The remaining-due figure updates as rows are applied.
- Pay & Close once the settled total covers the order. The server receives one
make-paymentcall carrying every payment method with its tendered amount (selectedPaymentMethods→/orders/make-payment); rows with zero amounts are dropped server-side[code: OrderDetailsPanel.tsx:1684-1693; hubits-api-sales/OrderPaymentController.cs:219 make-payment @ main]. - The receipt itemises each payment line.
Offline rule: when the POS is offline, only gateway-backed methods are disabled for inline settlement; cash / card / cheque / transfer all stay enabled because they just record the cashier's input. The gate is isPaymentMethodAvailableOffline = !method.requiresGateway [code: foodops.counter.app/src/components/orders/OrderDetailsPanel.tsx:209-217 @ main]. (Note: the old PaymentSettlementModal.test.tsx PAY-UI-006 assertion no longer exists — that component was removed and the split-payment UI now lives in OrderDetailsPanel.)
How bill splits work (backend reference — for when the UI lands)
All under api/orders/bill-splits (session-authenticated) [code: hubits-api-sales/Hubits.Sales.Api/Controllers/BillSplitController.cs @ 20fc7ca]:
| Endpoint | Purpose |
|---|---|
POST /preview |
Dry-run a split (equal or by-item) — returns per-part totals, no save |
POST /create |
Create + activate the split |
GET /{orderId}/active |
Fetch an order's active split |
POST /{billSplitId}/cancel |
Cancel an active split (only while no part is paid) |
GET /parts/{splitPartId} |
Fetch one part |
POST /api/orders/make-split-payment |
Pay one part (its own invoice; multi-tender allowed per part) |
Split methods and rules
- Method 1 — Equal: order divided into N equal parts. N must be 2–20.
- Method 2 — By item: every line item (or a quantity portion of it — assignments carry
Quantity, so one line can be shared across guests) is assigned to a part number; all items must be assigned ("{n} item(s) not assigned to a guest" otherwise). - An order can hold one active split at a time ("Order already has an active split").
- Cannot split a cancelled, completed/closed, or locked order.
- Each part gets a label, its own
SubTotal / TaxTotal / ServiceFeeTotal / DiscountTotal / GrossTotal, and once paid, its ownInvoiceId.
GST / service charge / discount distribution
[code: .../Services/Counter/BillSplits/BillSplitService.cs:348-462 @ 20fc7ca]
- Equal split: each financial field (tax, service fee, discount, line amounts) is divided independently, floored to 2 dp, and part 1 absorbs the rounding remainder — so parts can legitimately differ by a laari and the sum always equals the original bill.
- By-item split: each part's items keep their own net/tax; order-level tax, service fee, and discount are prorated by the part's subtotal ratio, rounded to 2 dp.
Paying a part
POST /api/orders/make-split-payment takes the order, the split part, and a list of payment methods (multi-tender works per part, with alternate-currency support). The server creates an invoice for just that part's amounts, processes the payment, and marks the part paid. Already-paid parts are rejected. When the last part is paid the order auto-closes [code: .../Controllers/OrderPaymentController.cs:925-1050 @ 20fc7ca].
Multi-terminal sync
SignalR broadcasts to every terminal in the organisation: BillSplitCreated, BillSplitCancelled, SplitPartPaid (with an all-parts-paid flag), and OrderClosed when the final part settles — so a split started on one terminal is visible/payable from another.
Key concepts
- Split payment (multi-tender) — several payment methods settling ONE bill/invoice. Live today.
- Bill split — ONE order divided into N separately-payable parts, each with its own invoice and receipt. Backend-ready.
- Part — a guest's share: label, prorated totals, paid flag, invoice link.
- Equal vs By-item — divide everything by N, or assign items (even partial quantities) to guests.
- Active split — at most one per order; the order carries a
HasActiveSplitflag while parts remain unpaid. - Rounding remainder — equal splits floor each part and give part 1 the leftover laari; sums always reconcile.
Common questions
Q: Can the cashier split a bill into separate checks at the POS today? A: No — there's no Split Bill screen in the counter app yet (verified against main, 2026-06-11). The backend is ready; until the UI ships, use multi-tender payment or separate orders.
Q: How do I take "MVR 60 cash + MVR 40 card" on a 100-rufiyaa order? A: Payment screen → Split Payment → add a Cash row (60) and a Card row (40) → Pay & Close. Receipt shows both lines.
Q: Can two people pay by card on one bill? A: Yes — the split-payment dropdown allows the same method to be added multiple times.
Q: When bill splits ship, how will GST be divided? A: Equal splits divide the tax equally (part 1 absorbs the rounding laari). By-item splits keep each item's own tax with its assigned guest and prorate order-level tax/fees/discount by each part's share of the subtotal.
Q: What about the service charge on a split bill?
A: Same as tax — equal share for equal splits, subtotal-proportional for by-item splits. Every part carries an explicit ServiceFeeTotal.
Q: Can one shared dish be split across two guests? A: Yes at the API level — item assignments carry a quantity, so 2× of an item can go 1-and-1 to two parts.
Q: Customer paid their part, then the rest of the table wants to cancel — possible? A: The split itself can't be cancelled once any part is paid ("Cannot cancel split. Some parts may already be paid."). The paid part has a real invoice; unwinding it is a back-office refund (see Returns, Refunds & Voids).
Q: Does each guest get their own receipt? A: Yes — paying a part creates a part-specific invoice, so each guest's receipt shows only their amounts.
Q: Is there a limit on how many ways a bill can split? A: 2 to 20 parts.
Q: Can a closed or locked order be split retroactively? A: No — splits are rejected on cancelled, completed, and locked orders.
Q: If terminals A and B both serve the same table, do they see the split? A: Yes — split creation, cancellation, and part payments broadcast over SignalR to all terminals in the organisation, and the order closes everywhere when the last part is paid.
Q: Does a partial payment then an item void break the split? A: Untested territory — the split snapshots part totals at creation; editing the order's items afterwards is not reconciled into an active split by any code found in this pass. Treat "edit order while split active" as unsupported until the UI ships and defines behaviour.
Edge cases and known issues
"Split Payment" vs "Bill Split" confusion
Support tickets will use both phrases interchangeably. Clarify which the caller means before answering: multiple payment methods (works today) vs separate checks (not at the POS yet).
Equal-split parts differ by MVR 0.01
By design: floor-to-2dp per part, remainder to part 1. Not a bug.
Order edited after split created
No reconciliation path found in code — the split's part totals would go stale against the order. Cancel the split (if unpaid) and re-create after editing.
Dine-in vs counter
Nothing in the bill-split backend is dine-in-specific (it keys off the sales order), but the natural use case is dine-in tables; user story US-POS-004 frames it that way.
Honest gaps (verified 2026-06-11)
- A Split Bill UI consumer now exists in
foodops.counter.appmain (BillSplitModal/BillSplitSettleinOrderDetailsPanel.tsx, offline-first), gated behindFEATURE_BILL_SPLITTING— no longer backend-only in the codebase, though production flag status and a live walkthrough are still unconfirmed. SplitPaymentManager.tsxin the counter is an empty 13-line stub; the live multi-tender UI lives insideOrderDetailsPanel.tsxinstead (the formerPaymentSettlementModal.tsxhas been removed from main).- No per-part printing template verified (part invoices exist; receipt rendering for parts unverified).
- No UI screenshots possible yet for bill splits — nothing to screenshot.
Related
POS Takeaway Order — the payment screen where split payments live
Returns, Refunds & Voids — unwinding a paid part
Dine-In & Tables — the table flows bill splits are built for
User Stories: POS — US-POS-004 (split bill), US-POS-005 (mixed methods), US-POS-EDGE-003 (cash + card)
2026-08-18 — codebase (hubits-api-sales 4f94ad6..98c202d, main). This diff's OrderPaymentController.cs hunk reprises the already-documented 2026-08-09 auto-close change verbatim (Pay/Credit-sale settlement on a terminal-SERVICE-status order auto-closing it via IsInTerminalServiceStatus + CloseOrderInternal, OrderClosed SignalR event, NotifyOrderClosedAsync webhook, 'Payment processed and order closed successfully') — unchanged, not re-noted here. Two smaller changes in this range touch this entry's cited controller file but not its documented split-payment/bill-split claims: (1) UpdatePayment's invoice-status check was refactored from a raw
invoice.Outstanding <= 0comparison to a newInvoiceSettlement.IsSettled(invoice.Outstanding)helper — same threshold, just encapsulated, and not in the make-payment or make-split-payment code paths this entry cites (925-1050, 219). (2) GeneratePaymentDocumentContent (the background-job payment-receipt renderer) now eager-loads Transaction.Customer, Transaction.TransactionCurrency, and Invoice.Transaction.TransactionCurrency, and persists the rendered content via a new single-column GeneratedContentPersistence helper instead of a full SaveChangesAsync — a bugfix for a RuntimeBinderException when a template's {{Customer Name}}/currency placeholders hit an unloaded navigation in a fresh background-job context. No evidence in this diff that split-bill part payments render through this same method, so this neither confirms nor resolves the entry's existing 'no per-part printing template verified' honest gap — still unconfirmed either way. Neither change contradicts anything currently documented. No edits needed. (auto-applied by the truth pipeline; adversarially gated) (auto-applied by the truth pipeline; adversarially gated)2026-08-14 — codebase (hubits-api-sales 4f94ad6..ed3c7d6, main). This diff is a superset of the already-documented 2026-08-09 auto-close change (Pay/Credit-sale settlement on a terminal-SERVICE-status order now closes it, OrderClosed field reflects reality, 'Payment processed and order closed successfully') — that content is unchanged and not re-noted here. New in this wider range: the credit-sale settlement path's credit-limit check is now skipped entirely once the order is already invoiced (
IsOrderAlreadyInvoicedAsync). PreviouslyValidateCreditLimitInternalsummed a customer's approved invoices and added the current order's gross total on top on every call, so a retried credit-sale settlement on an order whose invoice had already been committed by a prior attempt would double-count that order's own amount and could be wrongly refused for exceeding a limit it hadn't actually exceeded — with no way to ever get past the check on retry, since the retry hits the same double-count. This is an idempotency/correctness fix scoped to the credit-sale branch of make-payment; it doesn't touch anything this entry currently claims (the entry documents multi-tender split-payment mechanics and the bill-split make-split-payment path, not credit-limit validation), and there's no evidence it reaches make-split-payment, which sits outside this diff's hunks. Companion commits in this range (generic Idempotency-Key handling for unsafe requests, making success/failure distinguishable across both response envelopes, an onboarding-wizard NoTracking fix, and POS search recent-items ordering) don't intersect this entry's cited surface. No edits needed — noting for completeness alongside the existing 2026-08-09 auto-close history entry. (auto-applied by the truth pipeline; adversarially gated)2026-08-09 — codebase (hubits-api-sales 4f94ad6..cdb3b32, main). NEW CAPABILITY on the plain
/orders/make-paymentendpoint that this entry's multi-tender split-payment walkthrough documents (step 4, 'Pay & Close', cited at OrderPaymentController.cs:219): a Pay (cash/card) or Credit-sale settlement on an order that has already reached a terminal SERVICE status (dine-in Served / takeout Picked / delivery Delivered) now auto-closes the order via a new IsInTerminalServiceStatus + CloseOrderInternal check, additionally firing an OrderClosed SignalR event and NotifyOrderClosedAsync webhook alongside the existing OrderPaid/OrderUpdated. The response's OrderClosed field — previously hardcoded false on the Pay path — now reflects the real outcome, and the success message becomes 'Payment processed and order closed successfully' (or 'Credit sale recorded and order closed successfully') when it fires. This is a distinct mechanism from the bill-split 'auto-close when the last part is paid' behavior this entry already documents (that lives in the make-split-payment path around OrderPaymentController.cs:925-1050, outside this diff's hunks and unaffected) — this change is on the ordinary whole-bill make-payment/credit path used by the split-PAYMENT (multi-tender) walkthrough. Doesn't contradict anything currently documented (the entry never claimed Pay previously left terminal-status orders open), but is worth folding into the walkthrough/table: 'Pay & Close' can now genuinely auto-close an order that was already Served/Picked/Delivered, not just settle it. No evidence this diff touches make-split-payment itself. (auto-applied by the truth pipeline; adversarially gated)2026-08-01 — codebase (hubits-api-sales bb69312..01d4b26, main). Two internal hardening changes touch surface this entry documents, neither contradicting existing claims. (1) BillSplitController's CreateSplit no longer calls _hubContext.Clients.Groups(...).SendAsync("BillSplitCreated", ...) directly — it now captures the order id/org id ahead of the fire-and-forget Task.Run and calls a new shared _orderEvents.PublishByIdAsync("BillSplitCreated", orderId, orgId, OrderEventScope.Content), part of a broader perf change ('give order broadcasts a routing envelope so terminals stop refetching'). The event name and org-wide delivery this entry documents (BillSplitCreated broadcast to every terminal) are unchanged — this is a transport/perf refactor, not a behavior change. (2) OrderPaymentController's RequestId idempotency handling (already noted in this entry's 2026-07-19 history for make-split-payment) is hardened: a new TryReserveOrReplayPaymentAsync claims the RequestId BEFORE processing instead of checking it only after, fixing a real race where a counter offline-queue retry arriving mid-processing could pass the old post-hoc check, run the full payment twice, and (per the in-code comment) write a second invoice and double-bump a credit customer's balance before the unique index caught it. A 2-minute in-flight lease governs stale-reservation takeover. The diff snippet available doesn't show whether this exact reservation path is wired into make-split-payment specifically vs. only make-payment, so scope against the split-payment flow is unconfirmed — worth a follow-up check, but nothing here contradicts the entry's existing make-payment or make-split-payment idempotency/invoice claims; if anything it closes a gap consistent with them. A companion change also adds mandatory org-scoped order-cache invalidation after any ExecuteUpdate write on SalesOrders (not previously mentioned) so GET /orders/{id} and Version don't serve stale data post-payment — again an internal correctness fix, not a documented-behavior change. (auto-applied by the truth pipeline; adversarially gated)
2026-07-23 — codebase (foodops.counter.app dea0b59..61f13d1, main). Minor UI enhancement to the settle screen's overpayment 'Change' indicator in
OrderDetailsCard: previously it rendered the change due as a single figure in the order's base currency symbol; it now iteratesenabledCurrencies(falling back to the base currency alone if none are configured) and shows the change amount converted per-currency via the samerateFor/codeForper-tender currency-rate helpers already documented in this entry's 2026-07-11 verification note (ADR-0011 per-tender currency). This is display-only — it does not touch theselectedPaymentMethods→make-paymentsubmission mechanics, the bill-split (api/orders/bill-splits) endpoints, or the offline gateway-gating logic this entry documents, so no existing claim is contradicted. It only extends the already-tracked multi-currency thread to the change-due display; not significant enough to warrant rewriting the split-payment walkthrough, but worth folding into a future UI-TODO pass on that screen. (auto-applied by the truth pipeline; adversarially gated)2026-07-22 — codebase (hubits-api-sales f26ba1f..407e704, main). GetPaymentMethods now explicitly returns a
RequiresGatewayflag per payment method (commit comment: 'Required so the counter can tell an online-gateway tender (BML) apart from cash/card and open the QR flow instead of settling directly (ADR-0013)'), part of a broader 'implement BML Connect online payment integration and merchant account management' change. This confirms rather than contradicts this entry's existing offline-gating claim (isPaymentMethodAvailableOffline = !method.requiresGatewayin OrderDetailsPanel.tsx) — it's the same flag, now explicitly wired into the payment-methods API response. It also signals a new online-gateway tender type (BML QR flow) is being built that would behave differently from the cash/card/cheque/transfer rows this entry's multi-tender split-payment walkthrough documents (a gateway tender can't just be a same-screen row settled inline). No counter.app consumer of this field or QR-flow UI is shown in this diff, so scope/rollout is unconfirmed. Worth a UI-TODO to watch for a BML/gateway payment row appearing in the split-payment screen and to verify whether it can coexist with a bill-split part payment once that UI is live. (auto-applied by the truth pipeline; adversarially gated)2026-07-22 — codebase (foodops.counter.app 69b3494..72c675f, main). NEW CAPABILITY on the multi-tender settle screen this entry documents: a BML Connect online payment gateway option has landed in
OrderDetailsCard/OrderDetailsPanel.tsx. When the cashier'sselectedPaymentMethodssplit includes a tender whose payment methodrequiresGateway(an 'Online' method), the settle no longer fires the directmake-paymentcall synchronously — it's intercepted (gatewayTendercheck) and handed to a newBMLQRCodeModalviabmlSettlestate (carrying the full builtMakePaymentRequestplus the gateway tender's amount) so the diner scans a QR and pays via their BML app; per an in-code comment the server settles the whole split atomically on the modal's confirm (ADR-0013), and at most one gateway tender is allowed per settle (Credit/FOC settles never carry one). This doesn't contradict this entry's documented mechanics — the non-gateway multi-tender path (cash/card/cheque/transfer, onemake-paymentcall carrying every row) is unchanged and still the common case — but it adds a real new tender type/UI flow to the same settle screen that the split-payment walkthrough doesn't mention yet. Same commit range also gates the (unrelated) whole-order Cancel Order action behind a newSalesOrders.Deletepermission check (canDeleteSalesOrders) — this is a different action from bill-split cancellation (BillSplitController) and doesn't touch any claim in this entry. Worth a UI-TODO to live-walk the BML QR flow once feasible and add 'Online (BML QR)' as a tender option in the split-payment walkthrough/table. (auto-applied by the truth pipeline; adversarially gated)2026-07-19 — codebase (hubits-api-sales 6c6b950..e0517e4, main). Bill-split backend gained offline-sync hardening that this entry doesn't yet mention, though nothing it already documents is contradicted. CreateSplit now accepts an optional client-supplied BillSplitId/PartIds (so an offline-created split adopts the counter's own ids on sync instead of getting server-minted replacements) plus an ExpectedOrderVersion; a mismatch now returns 409 Conflict with a NewVersion for the counter to reconcile against (
BillSplitCreateOptions,IsVersionConflict). CancelSplit's signature changed to also return the bumped order version, but the paid-parts guard itself is unchanged ("Cannot cancel split. Some parts may already be paid." still returned by the controller). make-split-payment gained RequestId-based idempotency (replays a cached response instead of double-charging a retried offline part-payment) and two new early org-ownership checks closing an IDOR gap (Issue 3.5-A: an org-B caller could previously pass an org-A order/split-part id) — both fail closed to the same NotFound already implied by "order/part not found", so no documented behavior changes. The already-paid-part rejection, per-part dedicated invoice, and auto-close-on-last-part logic all remain present unchanged. None of this ships a UI — still no caller of these endpoints found in counter.app or foodops-admin-app as of this diff — but the specific shape of the hardening (offline id-adoption + version-conflict + idempotent retries) mirrors the offline patterns already documented elsewhere in this entry for ordinary multi-tender payments, suggesting an offline-capable bill-split UI is the likely next step. Worth a UI-TODO to watch for a counter-side consumer of these fields once a bill-split screen ships. (auto-applied by the truth pipeline; adversarially gated)2026-07-19 — codebase (foodops.counter.app 054a5b3..43c6fad, main). MAJOR CHANGE: a Split Bill UI has landed on main across three commits — offline-first Equal split (create/pay/cancel, legacy UI, sync + rollback), by-item split (item-to-guest assignment, offline-first), and cross-device sync/close-correctness + reject-cleanup. OrderDetailsPanel.tsx now imports BillSplitModal and BillSplitSettle, gates a new 'Split Bill' launcher (⋯ menu) behind canSplitBill (FEATURE_BILL_SPLITTING flag on, order not locked/paid/credit-sale/completed/already-split), binds a live activeSplit query (useLiveQuery/getActiveSplit) that drives a settle-area Pay Part flow, adds an edit-lock while a split is active (order can't be edited until the split is cancelled — this also answers this entry's previously 'untested territory' question about editing an order mid-split), and wires offline actions (paySplitPart, cancelSplit, syncActiveSplitFromServer, deactivateSplitById, markSplitPartPaidById) with optimistic-rollback handling for server-rejected split creates/payments (toasts + state reset on 'bill-split-payment-rejected' / 'bill-split-create-rejected' events). This directly contradicts the entry's core claim that bill splits are 'API complete, no UI consumer found anywhere in the org' and the instruction 'Do not tell users the POS has a Split Bill button' — both are now wrong at the code level. Caveat: FEATURE_BILL_SPLITTING is a feature flag; nothing in this diff confirms it's turned on for any production org, so 'is it actually live for a given customer' remains unconfirmed. Recommend a live UI walkthrough on a flag-enabled tenant to capture the real Split Bill entry point, screens, and screenshots, and to confirm rollout status before fully re-writing this entry's status table and walkthrough section. (auto-flagged by the truth pipeline; edits retract the 'no UI' claims pending live verification) (auto-applied by the truth pipeline; adversarially gated)
2026-07-11 — codebase (foodops.counter.app 8d1d1af..b120e71, main). The live counter payment screen's currency handling was refactored per ADR-0011: currency is now a per-TENDER property instead of a whole-order switch. The old whole-order currency-change UI (
currencies/showChangeCurrency/isLoadingCurrencies/currentCurrencySymbol, backed by the onlinecurrencyApiService) was removed; each payment-method row inselectedPaymentMethodsnow carries its own optionalcurrencyId, resolved againstenabledCurrencies(id/code/symbol/isBaseCurrency/exchangeRate) sourced from the offline session cache with configured fixed rates — new helpersrateFor/symbolFor/codeForcompute per-row display and coverage math (computeMultiCurrencyCoverage,CASH_PAYMENT_METHOD_IDreplace the removedconvertToCurrency). Net effect: a cashier building a multi-tender split payment can plausibly tender different rows in different currencies against fixed org-configured rates, entirely offline — a real capability this entry's split-payment walkthrough doesn't mention. This does not change the submission mechanics already documented (still onemake-paymentcall carryingselectedPaymentMethods) and there's no evidence it touches the bill-split (per-part) endpoints or the offline gateway-gating logic cited elsewhere in this entry. Worth a UI-TODO to live-walk the per-row currency dropdown and capture screenshots once feasible. (auto-applied by the truth pipeline; adversarially gated)2026-07-06 — codebase (foodops.counter.app 047ad31..0545649, main). The counter payment screen's stale-total handling changed: previously, if the order total moved (e.g. edited from another terminal) right as the cashier hit Pay/Pay & Close,
processPaymentInlinesilently aborted with a toast ('Order total changed') and required the cashier to re-check the amount and press Pay a second time. It now surfaces an explicit confirm-and-charge prompt (newstaleTotalPromptstate carrying stale vs fresh totals) before charging. A new helperisLoneUntouchedTenderdetermines whether the single, unedited payment row can be safely re-pre-filled to the fresh amount ('chargeable') so the cashier can tap a one-step 'Charge ' to settle; if the cashier had already customized/split the payment rows (as in a multi-tender split payment), the prompt is review-only and does not auto-fill, leaving the cashier to adjust the split themselves. Re-submission from the dialog passesacknowledgedFreshOutstandingto skip the stale check on the second call. This does not change the underlyingselectedPaymentMethods→ singlemake-paymentsubmission mechanics documented in this entry's split-payment walkthrough (step 4), but is a real new interaction the walkthrough doesn't yet mention — worth a UI-TODO to capture the confirm dialog once screenshots are feasible. No evidence this diff touches the bill-split (per-part) endpoints or the offline gateway-gating logic cited elsewhere in this entry. (auto-applied by the truth pipeline; adversarially gated)2026-06-29 — codebase (July-10 launch re-verify;
foodops.counter.app@32669a9main,hubits-api-sales@19190b9main). UPHELD: complete bill-split backend —BillSplitControllerroutesapi/orders/bill-splitspreview/create/active/cancel (:48/71/35/108),BillSplitCreatedSignalR (:96); per-part payment viaOrderPaymentController.cs:991 make-split-paymentwith auto-close on last part (CloseOrderInternal→orderClosed→OrderClosedSignalR:1078-1099); and crucially still NO UI consumer anywhere (org-wide search of counter.app + foodops-admin-app forbill-splits/make-split-payment= empty). Multi-tender split payment IS live inOrderDetailsPanel(selectedPaymentMethods, add-row:1491-1497). CORRECTIONS: thePaymentSettlementModal.tsxcomponent was removed from main — re-pointed the split-payment walkthrough + feature-status table + honest-gaps toOrderDetailsPanel.tsx; the offline payment rule re-cited toOrderDetailsPanel.tsx:209-217and corrected to "gateway-backed methods disabled offline" (was "only cash-like methods enabled"); the deadPaymentSettlementModal.test.tsxPAY-UI-006 reference noted as gone. Frontmatter SHAs + last_verified bumped.
Note: Seeded 2026-06-11 · codebase only (audit P1-10 coverage gap) Verified_by:
claude-code· Last verified: 2026-06-11 New entry authored against prod branches:foodops.counter.app@1f77fc6(main),hubits-api-sales@20fc7ca(main). Core finding: a complete bill-split backend exists in the sales API (BillSplitController,BillSplitService, per-part invoices viamake-split-payment, Equal/ByItem methods, 2–20 parts, floor+remainder rounding for equal splits, subtotal-ratio proration for by-item, SignalRBillSplitCreated/BillSplitCancelled/SplitPartPaidbroadcasts, auto-close on last part) — but no frontend anywhere in the haafai org calls it (counter clone grep + org-wide code search both empty; counterSplitPaymentManager.tsxis a 13-line stub). Multi-tender split payments ARE live inPaymentSettlementModalwith an offline non-cash restriction (PAY-UI-006 tests). No live UI walk; split-payment walkthrough labels are source-derived with UI-TODOs.
Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.