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.

Status: Entity model and the 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. postSale on Order close. postPaymentReceived on Settlement settled. postRefundProcessed on refund settled. postChargebackLoss on 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.

MethodPathPurpose
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-notesIssue 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/manualManual 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.