Appearance
Accounting (the posting function)
Vecnet's posting layer replaces Tonder's AccountingService.accountingPreparer
- SQS
accountUpdaterHandlerpipeline with synchronous Prisma transactions that always close the double-entry pair before returning.
Entry points
Every code path that touches the ledger does the same thing:
- Open a Journal for the event (
lib/journals/open.ts:openJournal). - Resolve the account ids lazily (
lib/accounts/resolve.ts:resolveAccountId) — house and per-merchant accounts get created on first use. - Write the balanced pair inside a Prisma
$transaction. - Update the Journal's summary with denormalised totals (
gross,fee,iva,net,rr).
Callers:
| Event | File | Journal category |
|---|---|---|
| Payment success | lib/transactions/create.ts | payment |
| Payment decline | lib/transactions/create.ts | payment (no gross, just decline fee) |
| Refund | lib/transactions/refund.ts | refund |
| Rolling-reserve hold (per payment) | lib/rolling-reserve/apply.ts | rolled into the parent payment journal |
| Rolling-reserve release | lib/rolling-reserve/release.ts | rolling_reserve_released |
| Settlement close | lib/settlements/compose.ts | settlement_approve |
| Manual adjustment | lib/settlements/adjust.ts | settlement_adjustment |
| Confirm payment received | app/api/admin/settlements/[id]/mark-paid/route.ts | settlement_confirm |
Fee writer — three legs with IVA
lib/fees/ledger-writer.ts:writeFeeLedgerEntries is the only place fee entries are produced. Each call:
OUT fee + IVA (merchant pays Vecnet):
DR merchant_pending feeCents + ivaCents
CR vecnet_revenue feeCents
CR vecnet_iva_payable ivaCents (omitted when ivaCents = 0)IN fee (Vecnet pays the processor — no IVA on costs):
DR vecnet_cost inFeeCents
CR vecnet_clearing inFeeCentsAll three legs share the same journalId and category (fee_acceptance_out, fee_iva_out, etc.). Reports filter by category; balance queries filter by account kind.
Gross payment pair
For a succeeded payment:
DR vecnet_clearing grossCents
CR merchant_pending grossCentsFor a refund (reverses gross + writes a new refund-fee pair):
DR merchant_pending refundedCents
CR vecnet_clearing refundedCentsRolling reserve
On every succeeded payment with an active reserve config:
DR merchant_pending heldCents releasedAt = now + holdingPeriodDays
CR merchant_rolling_reserve heldCents releasedAt = now + holdingPeriodDaysThe release worker matches releasedAt <= now on the credit leg and writes the inverse pair under a fresh rolling_reserve_released journal, deduping by transactionId so re-runs never double-release.
Settlement composer
lib/settlements/compose.ts:composeSettlement({ merchantId, periodStart, periodEnd, channel }) runs once per channel per cycle:
- Open a
settlement_approveJournal. - Aggregate every unlinked LedgerEntry in
[periodStart, periodEnd)filtered to this channel (join throughtransaction.channel; non-tx entries default toonline). - Write the settlement fee (OUT + IN + IVA legs) via
writeFeeLedgerEntries. - Compute
netCents = gross − totalOutFees − reserveHeld + reserveReleased. If< minimumPayoutCents, skip — the entries roll into the next period. - Otherwise insert the
Settlementrow instatus = closed, generate the syntheticdisplayId, link every entry from this channel viasettlementId. - Backfill the Journal's
settlementId+ summary.
The settlement composer is the only place outside of orchestrator-time that writes new fee ledger entries.
Confirm payment — settlement payout
When finops enters the SPEI reference at the "Confirm payment" step:
DR merchant_pending netCents
CR vecnet_clearing netCentsCategory settlement_payout. Journal category settlement_confirm. The Settlement row's status flips accepted → paid in the same transaction.
Immutability invariant
lib/db-extensions.ts:applyImmutableLedger enforces append-only at the Prisma client level. The only fields the extension permits updating on LedgerEntry are settlementId, journalId, accountId — used by the composer to backfill linkage. Any other mutation throws.
Corrections are new entries — typically a settlement_adjustment Journal written via lib/settlements/adjust.ts.