Primitive spec
Mallow
The ledger primitive. Mallow records the finalised financial state behind every Hober Order close, every Payments settlement, and every payroll cycle — as immutable invoices, credit notes, and double-entry postings. Audit-shaped from day one.
LedgerPostingService::postPaymentReceived path exist; the Order-close → Invoice generation path is wired through Hober. Refund, dispute-loss, and payroll posting methods are the next slices.
What it owns
Mallow owns the books. Once an Order closes, an Invoice is the immutable record of what was sold, taxed, and tendered. Once a Payments Settlement lands, the cash-receipt entry is the immutable record of money in the bank. Compensating actions — refunds, chargeback losses, voids — create new entries; they never mutate or delete existing ones.
Mallow does not query Payments for processor detail. Cents, currency, and the transaction reference are the only fields that cross the boundary — per the cross-primitive convention. Anything more (last4, processor batch id, dispute reason code) stays in Payments and is fetched separately by UIs that need it.
Concepts
- Invoice
- The immutable record of an Order at close. Lines carry the snapshotted unit price, tax class, and computed line tax. Totals are denormalised. The Invoice is what a CFO reads, an auditor reads, a tax authority reads.
- InvoiceLine
- One line on an Invoice, derived 1:1 from an OrderLine at close. Carries the variant id, quantity, snapshotted price, taxClassId, and the computed taxCents.
- CreditNote
- A compensating Invoice for refunds and returns. References the original Invoice and the original lines being credited. Never mutates the original.
- LedgerEntry
- A double-entry posting. Every event that moves money creates one or more LedgerEntries with matched debit and credit legs. Source-typed (
hober_order,payments_settlement,payments_dispute,payroll_cycle) for trace-back. - LedgerPostingService
- The single write surface.
postSaleon Order close.postPaymentReceivedon Settlement settled.postRefundProcessedon refund settled.postChargebackLosson Dispute lost or accepted. Each method emits well-named, predictable journal entries; UIs and reports read the result. - Payout / Settlement record
- The merchant-side view of what hit the bank, derived from Payments Settlement events. Reconciliation surfaces mismatches between the processor's claimed net and the sum of underlying transactions.
- Employee hours & payroll
- Mallow tracks worked-time records against the Trantor Resource that represents the employee, and payouts against the Terminus Person that is the legal payee. The split mirrors the Trantor/Terminus identity split for humans.
API surface
Endpoints are versioned under /mallow/v1/. Most writes flow through service events (Order close, Settlement landed), not direct HTTP; the surface below is for queries, manual postings, and admin tooling.
| Method | Path | Purpose |
|---|---|---|
| GET | /mallow/v1/invoices/{id} | Fetch an Invoice with lines and computed totals. |
| GET | /mallow/v1/invoices?customerId=&from=&to= | List invoices by customer, location, or date window. |
| POST | /mallow/v1/credit-notes | Issue a compensating CreditNote against an Invoice. |
| GET | /mallow/v1/credit-notes/{id} | Fetch a CreditNote with referenced original lines. |
| GET | /mallow/v1/ledger?sourceType=&sourceId=&from=&to= | Query LedgerEntries, filtered by source or window. |
| POST | /mallow/v1/ledger/manual | Manual journal entry (admin scope). Audited. |
| GET | /mallow/v1/payouts?from=&to= | Payout history reconciled against Payments Settlements. |
| GET | /mallow/v1/balances?accountCode=&asOf= | Account balance at a point in time, derived from the ledger. |
Example: posting an Order close
When Hober closes an Order, Mallow generates the Invoice and posts the sale entry. The event-driven flow:
// Hober emits order.closed
{
"orderId": "ord_01JAZB…",
"totalCents": 4500,
"taxCents": 275,
"lines": [ … snapshotted OrderLines … ],
"tenders": [ { "paymentsTransactionId": "txn_01JAZF…", "amountCents": 4500 } ]
}
// Mallow.LedgerPostingService.postSale runs
Invoice in_01JAZG… status=issued totalCents=4500
InvoiceLine ilin_… variantId=pvar_burger_single taxCents=92
InvoiceLine ilin_… …
LedgerEntry DR Accounts Receivable 4500
LedgerEntry CR Sales Revenue -4225
LedgerEntry CR Sales Tax Payable -275
// Later, on Payments Settlement landed
LedgerEntry DR Cash 4500
LedgerEntry CR Accounts Receivable -4500
How it fits with the rest
flowchart LR
Ho[Hober close] --> M(Mallow)
Pa[Payments settled] --> M
Disp[Payments dispute] --> M
Payroll[Payroll cycles] --> M
M -. customer / employee .-> Te[Terminus]
M -- statements --> Sp[Speaker]
Hober triggers Invoice generation on Order close. Payments Settlement events trigger cash-receipt postings; refund-leg transactions trigger refund postings; lost or accepted Disputes trigger chargeback-loss postings. Terminus is the identity ref for customers on Invoices and employees on payouts. Trantor Resources are where worked-time records live for employees. Speaker sends receipts and statements via consent-gated dispatch.