@cfxlabsinc/b2b-services
    Preparing search index...

    Module @cfxlabsinc/bank-transaction-reconciliation-services

    bank-transaction-reconciliation-services

    bankTransactionReconciliationWorkflow and the activities behind it: the one place that decides what reconciliation a b2b.bank_transaction row is owed and carries it out.

    The dispatch spans four domains — withdrawals, deposits, virtual accounts and bank internal transfers — and no existing service library can see all of them. bank-transaction-services cannot reach withdrawal-services (that edge runs the other way, and TypeScript project references cannot be circular), and withdrawal-services and deposit-services are siblings with no edge between them. This package sits above all four and is imported only by temporal-worker and backoffice-api.

    Every producer of a bank_transaction row, via startBankTransactionReconciliationWorkflow in @cfxlabsinc/bank-transaction-services:

    • VictorWebhookEventConsumer (raw.source = "victor_webhook")
    • persistVictorBankTransactions (raw.source = "victor_sync")

    The workflow id is keyed on the bank transaction, so the two racing is a no-op.

    • A returned BankTransactionReconciliationOutcome — RECONCILED, ALREADY_RECONCILED, or NOT_APPLICABLE with the reason recorded.
    • A failed execution naming the gap: an unhandled rail (an outbound FedNow debit), a row with no methodData.method, a rail/quote-method mismatch, book-transfer legs the internal-transfer service rejects (non-retryable, typed with that service's error code), or a retryable condition that never resolved.

    Nothing completes silently having reconciled nothing.

    The ACH entry-detail hold

    Victor completes an inbound ACH entry with its addenda, company fields and settlement date still null and fills them in hours later, with no second webhook. The plan marks such a row with awaitAchEntryDetailUntilEpochMs (four hours after postedAt), and the workflow re-reads Victor's detail until an addenda or a settlement date arrives or that deadline passes, then reconciles either way. A row released without the fields says so at the end of its outcome's reason.

    • AchEntryDetailAdminService (src/AchEntryDetailAdminService.ts) — refresh is the hold's read: it fills the entry fields an unreconciled row lacks and reports whether the late detail has arrived. backfill does the same read for rows written before the hold, and also fills the deposit quote or withdrawal reconciled from each. Both are fill-only.
    • scripts/backfillAchEntryDetail.ts — the operator entry point for backfill. Dry by default, --apply to write. --help lists the flags and environment.

    Operator procedure: docs/runbooks/ach-addenda-missing.md. Design: docs/BANK_TRANSACTIONS.md § 3.

    index
    workflows