Skip to content

Accounting (the posting function)

Vecnet's posting layer replaces Tonder's AccountingService.accountingPreparer

  • SQS accountUpdaterHandler pipeline 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:

  1. Open a Journal for the event (lib/journals/open.ts:openJournal).
  2. Resolve the account ids lazily (lib/accounts/resolve.ts:resolveAccountId) — house and per-merchant accounts get created on first use.
  3. Write the balanced pair inside a Prisma $transaction.
  4. Update the Journal's summary with denormalised totals (gross, fee, iva, net, rr).

Callers:

EventFileJournal category
Payment successlib/transactions/create.tspayment
Payment declinelib/transactions/create.tspayment (no gross, just decline fee)
Refundlib/transactions/refund.tsrefund
Rolling-reserve hold (per payment)lib/rolling-reserve/apply.tsrolled into the parent payment journal
Rolling-reserve releaselib/rolling-reserve/release.tsrolling_reserve_released
Settlement closelib/settlements/compose.tssettlement_approve
Manual adjustmentlib/settlements/adjust.tssettlement_adjustment
Confirm payment receivedapp/api/admin/settlements/[id]/mark-paid/route.tssettlement_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      inFeeCents

All 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     grossCents

For a refund (reverses gross + writes a new refund-fee pair):

DR merchant_pending     refundedCents
CR vecnet_clearing      refundedCents

Rolling reserve

On every succeeded payment with an active reserve config:

DR merchant_pending           heldCents       releasedAt = now + holdingPeriodDays
CR merchant_rolling_reserve   heldCents       releasedAt = now + holdingPeriodDays

The 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:

  1. Open a settlement_approve Journal.
  2. Aggregate every unlinked LedgerEntry in [periodStart, periodEnd) filtered to this channel (join through transaction.channel; non-tx entries default to online).
  3. Write the settlement fee (OUT + IN + IVA legs) via writeFeeLedgerEntries.
  4. Compute netCents = gross − totalOutFees − reserveHeld + reserveReleased. If < minimumPayoutCents, skip — the entries roll into the next period.
  5. Otherwise insert the Settlement row in status = closed, generate the synthetic displayId, link every entry from this channel via settlementId.
  6. 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      netCents

Category 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.

Vecnet — Build Spec v0.2 · Obsidian Terminal