BR.

Payment routing orchestrator

Evidence in the hot path,
AI at the edges.

Given a card authorization about to be sent, it decides which payment service provider should receive it — and if that attempt declines, what should happen next.

Try a scenario Read the case study

The instrument

Route a transaction

Pick a scenario and read the decision, or move the controls and watch it re-decide. Nothing here is a mock: every number comes from the same engine the CLI calls.

Scenarios

Eight transactions chosen because each one sits on a decision boundary.

Loading scenarios…

Adjust

The first six digits identify the issuing bank.

Checkout and POS are user-present: someone can complete an authentication step. Recurring is off-session: nobody is there.

Maximize approvalMinimize fees

Percentage points of approval you will trade for a cheaper provider: 0 takes the best approver, 1 takes the cheapest one within 10 pp of it.

Providers down

Marking one down rebuilds the candidate pool and re-scores the survivors.

Deciding…

AI at the edge

Normalize a decline

Providers speak different dialects: an ISO 8583 numeric code, a Stripe-style decline_code, an Adyen-style refusal reason, free text. The retry machine needs one vocabulary. A deterministic table answers first; a model is called only for the long tail; an unresolved code falls back to the generic policy and says so.

Or type your own

Pick a decline and normalize it.

How it works

Three parts, one boundary

The engine is pure. It loads pre-built tables, decides in microseconds, and never touches raw data — which is what makes the decision testable offline and auditable after the fact.

Pipeline diagram Offline, raw attempts are built into routing tables scored with a Wilson lower bound over four segment levels. Online, a transaction plus those tables enter the decide function, which returns a route, a retry plan and a reasoning trail. Offline · batch attempts 1 row = 1 try routing tables Wilson LB · 4 levels Online · pure engine transaction amount · bin · channel decide() segment · score · pick route + retry plan + why every decision carries its reasoning

Offline

Attempts become evidence

Raw events are reconstructed into one row per authorization attempt, then aggregated into approval tables over a four-level segment hierarchy: channel × funding × issuer × amount band at the finest, channel alone at the coarsest. The channel is never dropped — user-present and off-session are different worlds.

Every cell is scored with a Wilson 95% lower bound, so a segment with three approvals out of three is not a 100% segment. Thin evidence is penalized instead of trusted, without needing a separate confidence rule bolted on.

Online

Decide, then plan the retry

Per provider, the engine walks the hierarchy from finest to coarsest until it finds a segment with enough support, scores expected_net = wilson × amount × (1 − fee), and picks. The fee tolerance knob sets how many percentage points of approval an operator will trade for a cheaper provider; providers marked down are excluded before scoring.

Routing and retry are separate decisions. The retry state machine keys on the last decline's class and on whether a user is in the loop, so a funding problem waits on the same provider while a step-up authentication moves channels.

Edges

Where a model belongs

A model deciding where to send $450 does not pass latency or audit. A model translating "05 Do not honor" into a class the state machine already understands does — and that translation is measurable, so it is covered by an eval with a committed baseline and a hallucination gate.

An MCP server exposes the same engine as tools, so an operations analyst can ask an agent why a charge went where it went, and what changes if a provider drops.