ADR-0002. Event-driven posting; record finance at payment, not checkout
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-04-28 |
| Deciders | finance-team, sale-team |
| Supersedes | — |
Context
- Finance must not be on the critical path of POS checkout — a finance outage cannot block taking money.
- Revenue is only real when money actually settles. Recording at checkout (before payment) would book income for orders that are later abandoned or fail payment.
- COGS and inventory-asset value are owned by
@nx/inventory, which knows cost basis only after it issues stock — finance cannot compute it. - The chosen payment account/category is a customer-time decision carried on the sale payment payload (
payment.attempt.finance), not known at checkout.
Decision
We will make finance react to Kafka events, never call it synchronously from checkout. Finance subscribes to five topics and posts vouchers as side effects:
| Inbound event | Voucher |
|---|---|
PAYMENT_SUCCESS (sale) | RECEIPT against the account on attempt.finance.source.id |
PURCHASE_ORDER_RECEIVED (inventory) | PAYMENT to vendor (+ inventory asset leg) |
INVENTORY_ISSUED_FOR_SALE (inventory) | ADJUSTMENT — DEBIT COGS / CREDIT INVENTORY |
INVENTORY_ADJUSTED (inventory) | single-line ADJUSTMENT on INVENTORY |
MERCHANT CDC (commerce) | reconcile default + control accounts |
Income is booked on PAYMENT_SUCCESS, not at checkout. If the payment payload carries no chosen account (attempt.finance.source.id), finance INFO-skips rather than guessing.
Consequences
| Pros | Cons |
|---|---|
| Finance outage never blocks checkout | Postings are eventually-consistent, not synchronous |
| Income reflects settled money, not intent | Requires idempotency for at-least-once delivery (see ADR-0003) |
| COGS/asset posting uses inventory's authoritative cost basis | Finance depends on upstream payload completeness (account id) |
| Clean separation of concerns across services | Debugging spans multiple services + the broker |
Alternatives Considered
| Option | Pros | Cons | Why rejected |
|---|---|---|---|
| Synchronous HTTP from sale checkout | Simple, immediate | Couples checkout latency/availability to finance | Violates "finance off the critical path" |
| Record at checkout (intent) | Earliest signal | Books revenue for unpaid/abandoned orders | Wrong accounting |
| Finance computes COGS itself | Fewer events | Finance would duplicate inventory costing logic | Cost basis is inventory's domain |
References
packages/finance/src/components/kafka/component.ts(SUBSCRIBED_TOPICS,_dispatchMessage)packages/finance/src/services/finance-worker.service.ts(allhandle*methods)- Sale ADR-0003 — payment via webhook
- Integration