Appearance
Transaction processing (orchestrator)
Each finances flow keeps Tonder's semantics and replaces the plumbing.
Trigger: the Listener receives a rail notification (Tonder Backend or Menta Backend), normalizes it into a transaction_events row (or pgmq message) with a correlation_id. A worker consumes it.
Per-event steps (inside one transaction)
- Dedup / order: advisory lock on
entity_id; if a journal already exists for(process_id, category), returnALREADY_PROCESSED. - Normalize: map the rail payload → internal payin or withdrawal shape. (Tonder's
PAYOUT→WITHDRAWAL,WAA_DEPOSIT→TOPUPnormalizations: keep the concept, no EventBridge naming.) - Validate required fields →
MISSING_INFOif absent. - Cutoff: event older than 48h →
ALREADY_PROCESSED(realtime only). See the 2-day cutoff. - Module check:
FEES_CALCULATIONenabled for this entity+acquirer? If not →MODULE_DISABLED. - Enrich: fill
currency_codeif missing (a SQL lookup now, not a Mongo call). - Fees → Accounting.
Result codes carry over: PROCESSED, ALREADY_PROCESSED, MISSING_INFO, MODULE_DISABLED, NOT_SUPPORTED_DETAIL_TYPE.
Settlement / routing / reserve-release events skip fees and go straight to accounting (Tonder's processBySettlement).
Persist every attempt
Before (or alongside) the steps above, the orchestrator upserts a transactions row for every event regardless of outcome — success, declined, failed, pending, expired. Journals are created only for the successful financial subset; the attempt row is what powers acceptance/conversion/risk metrics. A declined or expired notification produces a transactions row and no journal.