Primitive spec

Payments

The money-in primitive. Payments models the customer's payment instrument, the charge (a hold or a capture), refunds and voids, the processor's payout, and the dispute — each as its own record, matched to how real payment processors actually work.

Status: Live. Tokenized processor drivers — LatticeKit never touches raw card data. Stripe and Finix, plus Fiska, Maverick (card-not-present) and Dejavoo (card-present terminals), which are built to each processor's published API spec and not yet run against a live sandbox. Built on top: manual capture, stored-credential reuse, processor routing + failover, signature-verified webhooks, a full dispute state machine, card-present (EMV), and real card collection for booking deposits via Stripe Payment Element, right in the customer’s booking flow (live since September 23, 2026). An Odin driver exists as a scaffold only.

What it owns

Payments owns the lifecycle of a charge attempt — from a customer's tokenised payment method to the cents that hit a merchant's bank account. It deliberately does not own a unified "Charge" object. Each processor (Stripe, Finix, Fiska, Maverick, Dejavoo) has its own native lifecycle; a driver maps it onto one Transaction status machine, and the processor stays the source of truth through signature-verified webhooks.

POS terminals live in Trantor as DiscreteResources, not in Payments. Tokenisation is the only allowed handle on card data; raw PAN never enters the system.

Concepts

ProcessorConfiguration
Per-tenant credentials and config for a specific processor (Stripe account, Finix merchant, Fiska or Maverick account). Credentials are envelope-encrypted at rest.
PaymentMethod
A tokenised payment instrument attached to a Terminus Person — card, bank account, digital wallet, gift card. Stores only the processor's token, plus last4, brand, expiry, and a fingerprint. Never the PAN.
Transaction
A charge against a processor token (ptxn_). captureMethod is automatic (capture now) or manual (authorize a hold, capture later). Status: pending → authorized → captured, with failed, voided, partially_refunded and refunded as the other states. A manual hold is captured for up to the authorized amount, or voided to release it; refunds cannot exceed what was captured.
ProcessorRoutingRule
Chooses which ProcessorConfiguration handles a charge, with failover to the next processor when one is unavailable.
Dispute
A chargeback with timeline, evidence, and outcome. Status flows through warning, evidence response, network review, and final win / loss / accept. Evidence is uploaded per network spec.
Payout reconciliation
ProcessorPayout ingest (Stripe + Mock derive): each payout's lines are matched to their Transactions, posted line-kind-aware through the 1010 Processor Clearing account in Mallow, and surfaced in a reconciliation console with an account-balance report — so the bank deposit and the ledger agree by construction.
Card-present (EMV)
In-person chip payments on a physical terminal. The reader is a Trantor DiscreteResource; Payments mints a short-lived connection token the terminal SDK uses to talk to the processor directly, so card data never touches Foundation. A captureMode of CARD_PRESENT (vs. the default CARD_NOT_PRESENT) records where the card was taken. Slice 1 ships the connection-token + capture-mode plumbing; the per-processor terminal drivers follow.
Booking deposits (Payment Element)
The deposit-to-confirm loop, end to end. POST /payments/v1/intents with a bookingId resolves the booking's open deposit requirement (the Daneel needs-ledger entry) and opens a hosted-element Stripe PaymentIntent, returning the client_secret + publishable key for the browser — no card data near Foundation. A Stripe Payment Element widget in the render-runtime collects the card on the public booking flow, and the signature-verified payment_intent.succeeded webhook settles the deposit and confirms the booking. Since September 23, 2026 the booking calendar on a published page shows that step straight after the customer books — before then the step was missing and a deposit booking quietly expired unpaid. A demo business gets a simulated payment instead, with no card fields at all. Two gaps remain: there is no pay-later link yet, so a customer who leaves before paying cannot come back to it, and deposits do not yet post to the books in Mallow.

API surface

Endpoints are versioned under /payments/v1/. PCI scope is constrained to token handling; never the raw card.

Payments endpoints in the Foundation API reference OpenAPI 3.1 schema for Payments with request/response shapes, parameters, and a try-it client.
MethodPathPurpose
POST / GET / PUT / DELETE/payments/v1/payment-methodsAttach, list, update, or revoke tokenised methods for a Person. PATCH …/{id}/default sets the default.
POST/payments/v1/tokenization-sessionsOpen a hosted tokenization session; …/complete finishes it.
POST/payments/v1/intentsWith a bookingId, opens a hosted-element deposit intent for the booking.
POST/payments/v1/transactions/captureCharge a token. captureMethod=manual authorizes a hold instead of capturing.
POST/payments/v1/transactions/{id}/capture-authorizedCapture an authorized hold, optionally for less than the full amount (amountCents).
POST/payments/v1/transactions/{id}/voidVoid an authorization before capture.
POST/payments/v1/transactions/{id}/refundRefund against a capture.
GET/payments/v1/transactionsList or read transactions.
POST/payments/v1/readers/{resourceId}/connection-tokenMint a short-lived connection token for a card-present terminal. …/collect starts a collection on the reader.
POST / GET/payments/v1/processor-configurationsConfigure a processor account; …/{id}/verify checks the credentials. Routing rules live at /payments/v1/processor-routing-rules.
GET/payments/v1/payoutsProcessor payouts with their reconciled lines; …/{id}/repost re-posts one to the ledger.
GET / POST/payments/v1/disputes/{id}View a dispute, submit …/evidence, or …/accept it.
POST/payments/v1/webhooks/{processorConfigurationId}Per-processor webhook inbound. Signature verified, deduped, dispatched.

Example: a tip-on-card flow

Authorize $50 at swipe (the bill plus tip headroom), then capture $45 once the customer adds a $5 tip. The unused $5 falls off the hold:

POST /payments/v1/transactions/capture
{
  "token":            "pm_1Q…",
  "amountCents":      5000,
  "currency":         "USD",
  "captureMethod":    "manual",
  "idempotencyKey":   "ord_01JAZB…-auth",
  "consumerEntityType": "hober_order",
  "consumerEntityId": "ord_01JAZB…"
}
→ 201  Transaction  ptxn_01JAZE…   status=authorized   amountCents=5000
POST /payments/v1/transactions/ptxn_01JAZE…/capture-authorized
{ "amountCents": 4500 }
→ 200  Transaction  ptxn_01JAZE…   status=captured   amountCents=4500

How it fits with the rest

flowchart LR
  Ho[Hober tender] --> Pa(Payments)
  WH[Processor webhooks] --> Pa
  Pa -. owner .-> Te[Terminus]
  Pa -. device .-> T[Trantor]
  Pa -- settled --> M[Mallow]
  Pa -- events --> D[Daneel]
  Pa -- alerts --> Sp[Speaker]
            

Terminus owns the Person that PaymentMethods belong to. Hober OrderPayments reference Transactions by id. On settlement, Mallow writes the cash-receipt ledger entry — cents + currency + transaction ref cross the boundary, never processor detail. Trantor POS Terminals carry the processor's device registration in metadata. Speaker sends payment-failed and dispute-opened notifications via consent-gated dispatch. Daneel workflows subscribe to payments.* events for automation (auto-retry, alert ops, dispute response routing).