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.
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_).captureMethodisautomatic(capture now) ormanual(authorize a hold, capture later). Status:pending → authorized → captured, withfailed,voided,partially_refundedandrefundedas 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
ProcessorPayoutingest (Stripe + Mock derive): each payout's lines are matched to their Transactions, posted line-kind-aware through the1010 Processor Clearingaccount 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
captureModeofCARD_PRESENT(vs. the defaultCARD_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/intentswith abookingIdresolves the booking's open deposit requirement (the Daneel needs-ledger entry) and opens a hosted-element Stripe PaymentIntent, returning theclient_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-verifiedpayment_intent.succeededwebhook 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.
| Method | Path | Purpose |
|---|---|---|
| POST / GET / PUT / DELETE | /payments/v1/payment-methods | Attach, list, update, or revoke tokenised methods for a Person. PATCH …/{id}/default sets the default. |
| POST | /payments/v1/tokenization-sessions | Open a hosted tokenization session; …/complete finishes it. |
| POST | /payments/v1/intents | With a bookingId, opens a hosted-element deposit intent for the booking. |
| POST | /payments/v1/transactions/capture | Charge a token. captureMethod=manual authorizes a hold instead of capturing. |
| POST | /payments/v1/transactions/{id}/capture-authorized | Capture an authorized hold, optionally for less than the full amount (amountCents). |
| POST | /payments/v1/transactions/{id}/void | Void an authorization before capture. |
| POST | /payments/v1/transactions/{id}/refund | Refund against a capture. |
| GET | /payments/v1/transactions | List or read transactions. |
| POST | /payments/v1/readers/{resourceId}/connection-token | Mint a short-lived connection token for a card-present terminal. …/collect starts a collection on the reader. |
| POST / GET | /payments/v1/processor-configurations | Configure a processor account; …/{id}/verify checks the credentials. Routing rules live at /payments/v1/processor-routing-rules. |
| GET | /payments/v1/payouts | Processor 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).