Payment Orchestration and Routing

Prerequisite: Payment Rails

Your checkout flow hardcodes Stripe. A customer’s card declines — network timeout on the issuer side, not insufficient funds. The transaction fails, the customer leaves, and the sale is gone. A second processor would have approved it. But your integration is point-to-point: one product, one processor, one rail. Adding a fallback means building a second integration from scratch, mapping two different API schemas into your domain model, and handling two different webhook formats for settlement. By the third processor, you are maintaining three parallel integrations with diverging failure modes, and every product change requires three deployments.

The orchestration layer exists because direct integration to each payment rail does not scale past two.

The Orchestration Layer

Payment orchestration is the abstraction between your product and the rails. Your product says “move $500 from this customer to this merchant.” The orchestrator decides which rail, which processor, and which fallback chain to use — then translates your intent into the rail-specific API call.

Product Layer        →  "charge $500, card ending 4242"
                         ↓
Orchestration Layer  →  route(amount, method, geography, urgency)
                         ↓
Rail Adapter         →  POST /v1/charges (Stripe)
                         or
                         POST /payments (Adyen)
                         or
                         NACHA file generation (ACH)

The orchestrator owns three decisions: which rail carries the payment, which processor handles it on that rail, and what happens when the primary path fails. Your product code never references a specific processor. It references a payment intent, and the orchestrator resolves the intent to a concrete execution path.

See Payment Rails. The handbook covers what each rail does. This chapter covers how you choose between them at runtime.

Rail Selection Criteria

Every rail trades cost against speed, reversibility, and geographic reach. The orchestrator scores each available rail against the transaction’s requirements and selects the best fit.

Criterion What it determines Example constraint
Cost Which rail is cheapest for this amount ACH: $0.20–0.50 flat vs card: 2.9% + $0.30
Speed How fast the recipient gets usable funds RTP: seconds vs ACH: next business day
Availability Is the rail operational right now SEPA Instant: 24/7 vs ACH: business days only
Transaction limits Can the rail carry this amount RTP: $1M cap vs FedNow: $500K default cap
Reversibility Can the payment be recalled or disputed Card: 120-day chargeback vs wire: irrevocable
Geography Does the rail reach the destination SEPA: EUR-zone vs Faster Payments: UK only

A $50 subscription renewal does not need same-second settlement. ACH at $0.25 is the right rail, not a card at $1.75. A $200,000 vendor payment cannot go over RTP — it exceeds the cap. The orchestrator encodes these constraints as rules, not as product logic.

The Decision Matrix

The routing decision starts with the transaction’s requirements and eliminates rails that cannot satisfy them.

Rail Cost (per txn) Speed Max amount Reversible Geography Use when
ACH $0.20–0.50 1–2 business days No hard cap Returns up to 60 days US only Recurring, non-urgent, cost-sensitive
Same-Day ACH $0.50–1.50 Same business day $1M Returns up to 2 days US only Payroll, vendor payments needing same-day
RTP $0.50–1.00 < 5 seconds $1M Irrevocable US only Instant disbursements, account funding
FedNow $0.01–0.045 < 20 seconds $500K (default) Irrevocable US only Low-cost instant, when participant supports it
Card (credit) 2.6–3.5% + $0.10–0.30 Auth: instant, settle: T+1–2 Issuer-dependent Chargeback: 120 days Global Consumer checkout, recurring billing
Card (debit) 0.5–1.0% + $0.15–0.25 Auth: instant, settle: T+1 Issuer-dependent Chargeback: 120 days Global Lower-cost consumer payments
Wire (Fedwire) $15–30 Minutes to hours No cap Irrevocable US (domestic) Large-value, time-sensitive, irrevocable
SWIFT $25–50 1–5 business days No cap Irrevocable (hard to recall) Global Cross-border, large-value
Stablecoin Gas fees ($0.01–5.00) Seconds to minutes No cap Irrevocable Global (on-chain) Cross-border, 24/7, no banking hours constraint

The orchestrator evaluates top-to-bottom: does the transaction meet the rail’s constraints? Is the rail available right now? Is the recipient reachable on this rail? The first qualifying rail that meets the product’s urgency and cost tolerance wins.

Fallback Chains

The primary rail fails. The orchestrator needs a plan.

A fallback chain is an ordered list of alternative execution paths, each with entry conditions. The chain fires when the primary path returns a retryable failure — not on every error. A hard decline (insufficient funds, fraud block) does not trigger a fallback to another processor because the second processor will decline for the same reason. A soft decline (network timeout, processor outage, rate limit) does.

fallback_chain = [
    {"processor": "stripe",  "rail": "card",  "conditions": ["primary"]},
    {"processor": "adyen",   "rail": "card",  "conditions": ["soft_decline", "timeout"]},
    {"processor": "checkout", "rail": "card", "conditions": ["soft_decline", "timeout"]},
]

def execute(payment_intent):
    for step in fallback_chain:
        if not meets_conditions(step, payment_intent):
            continue
        result = adapters[step["processor"]].charge(payment_intent)
        if result.success or result.is_hard_decline:
            return result
        payment_intent.record_attempt(step, result)
    return FailureResult("all_processors_exhausted")

The classification of decline codes into hard and soft is processor-specific. Stripe’s card_declined with reason insufficient_funds is hard. Stripe’s card_declined with reason processing_error is soft. The orchestrator maintains a mapping of every processor’s decline codes to the hard/soft classification.

Retrying a hard decline on a second processor wastes money (you pay the authorization fee on both attempts) and can trigger fraud detection. The issuer already said no. A different acquirer does not change the issuer’s answer.

Cost Optimization

For a platform processing $100M/year in payments, a 50 basis point reduction in processing cost saves $500,000 annually. The orchestrator is the lever.

Debit routing. In the US, the Durbin Amendment requires that merchants have a choice of at least two unaffiliated debit networks for each card. A Visa debit card can route over the Visa network (Durbin-regulated interchange: $0.21 + 0.05% of transaction amount + $0.01 fraud adjustment) or over a PIN debit network like STAR or NYCE (interchange: $0.04–0.10). For a $200 transaction, Visa interchange is $0.32; STAR interchange is $0.08. The orchestrator routes to the lowest-cost network automatically.

Rail downgrade for non-urgent payments. A marketplace paying out sellers daily does not need card-rail speed. Moving disbursements from instant card push ($0.50–1.00 per payout) to next-day ACH ($0.20–0.30) on a platform doing 50,000 payouts/month saves $15,000–35,000/month.

BIN-level routing. Approval rates vary by processor and card BIN range. A processor with a direct connection to a specific issuer approves more transactions from that issuer’s BINs. The orchestrator tracks historical approval rates by BIN prefix and routes to the processor with the highest success rate for that BIN — avoiding both lost revenue and the cost of failed authorization attempts.

Latency-Aware Routing

Different products have different speed requirements. The orchestrator matches rail latency to product need.

Product requirement Target settlement Preferred rail
Consumer checkout Auth: < 2 seconds Card network
Payroll disbursement Same business day Same-Day ACH
Gig worker instant payout < 30 seconds RTP or push-to-card
Bill payment Next business day ACH
Cross-border vendor payment < 1 hour Stablecoin or SWIFT gpi
Marketplace seller payout Within 24 hours ACH or next-day wire

A gig economy platform paying drivers after each shift needs sub-minute settlement. RTP delivers funds in under 5 seconds. Push-to-debit (via Visa Direct or Mastercard Send) delivers to the driver’s card in under 30 seconds. ACH takes until the next business day — the driver finishing a Friday night shift does not get paid until Monday. The latency requirement eliminates ACH as an option regardless of cost.

Success Rate Optimization

A 1% improvement in authorization rate on $100M of card volume recovers $1M in otherwise-lost transactions. The orchestrator tracks success rates across three dimensions: processor, BIN range, and time of day.

Processor-level tracking. Each processor’s approval rate varies by card type and geography. Processor A approves 94% of domestic Visa transactions but only 87% of international Mastercard. Processor B approves 91% of domestic Visa but 93% of international Mastercard. The orchestrator routes each transaction to the processor with the highest historical approval rate for that card type and geography combination.

Retry with network tokens. When a card-on-file transaction declines, retrying with a network token (Visa VTS, Mastercard MDES) instead of the raw PAN can recover 2–4% of declines. The token maps to the current card number even if the physical card was replaced — recovering transactions that fail because the stored PAN is stale.

Decline recovery. The orchestrator tracks which decline codes are recoverable with a retry and which are not. A do_not_honor from issuer A might succeed on a retry 30 minutes later (the issuer’s fraud system flagged it temporarily). A stolen_card never succeeds. The orchestrator maintains a retry policy per decline code, with backoff intervals and maximum retry counts.

Multi-Processor Routing

Running a single processor is an operational risk. Processor outages last minutes to hours and affect all transactions during that window. Multi-processor routing distributes volume across two or more processors, with automatic failover when one degrades.

The routing strategy depends on volume and business model:

Strategy How it works When to use
Active-passive All traffic to primary; failover to secondary on outage < $10M/year, simplicity priority
Weighted split 70/30 or 60/40 split across processors $10M–100M/year, want continuous validation of backup
BIN-optimized Route each BIN to the processor with highest approval rate for that BIN > $100M/year, margin-sensitive
Geographic Route by cardholder region to closest processor with best issuer relationships Global platforms

A weighted split at 70/30 ensures the secondary processor is always warm — its integration is continuously tested with real transactions. Pure active-passive risks discovering during an outage that the secondary integration has a bug that went unnoticed for six months.

The Adapter Pattern

Each rail speaks a different language. ACH is a fixed-width NACHA file. Cards are REST APIs with processor-specific schemas. RTP is ISO 20022 XML. The adapter pattern normalizes these into a common interface.

class PaymentAdapter:
    def authorize(self, intent: PaymentIntent) -> AuthResult: ...
    def capture(self, auth: AuthResult) -> CaptureResult: ...
    def void(self, auth: AuthResult) -> VoidResult: ...
    def refund(self, capture: CaptureResult, amount: Money) -> RefundResult: ...
    def status(self, reference: str) -> TransactionStatus: ...

class StripeAdapter(PaymentAdapter):
    def authorize(self, intent):
        response = self.client.payment_intents.create(
            amount=intent.amount.minor_units,
            currency=intent.amount.currency,
            capture_method="manual",
            payment_method=intent.payment_method_id,
        )
        return AuthResult.from_stripe(response)

class NachaAdapter(PaymentAdapter):
    def authorize(self, intent):
        entry = NachaEntryDetail(
            transaction_code="27",  # checking debit
            routing_number=intent.routing_number,
            account_number=intent.account_number,
            amount=intent.amount.minor_units,
        )
        self.batch.add_entry(entry)
        return AuthResult.pending(reference=entry.trace_number)

The cost of adding a new rail or processor is one adapter implementation. The product code, the routing logic, and the fallback chains do not change. Stripe’s API returns a PaymentIntent object; Adyen returns a /payments response; the adapter maps both into your AuthResult. The difference is invisible upstream.

Each new adapter costs 2–4 engineering weeks to build and certify: API integration, webhook handling, decline code mapping, settlement reconciliation, and sandbox testing. The orchestration layer makes that a fixed cost per rail, not a multiplicative cost across every product that uses payments.

Monitoring and Observability

The orchestrator produces the data that tells you whether your payment system is healthy.

Metric What it measures Alert threshold
Authorization rate Successful auths / total attempts < 92% (5-min rolling)
Processor latency (p95) Time from request to auth response > 3 seconds
Fallback rate Transactions using non-primary path > 5% sustained
Cost per transaction Blended processing cost Deviation > 10% from baseline
Decline code distribution Proportion of each decline reason Spike in any single code > 2x normal
Rail availability Successful health checks per rail Any rail < 99.5% availability

A sudden spike in soft declines on your primary processor — from 3% to 12% over five minutes — indicates a processor degradation before their status page updates. The orchestrator detects this through its own metrics and shifts traffic to the secondary processor automatically. You learn about the outage from your own alerting, not from the processor’s incident report two hours later.

Track cost per transaction as a blended rate across all rails and processors. A routing change that improves authorization rate by 0.5% but shifts volume to a more expensive processor may net negative. The orchestrator’s job is to optimize the composite: (authorization_rate × average_transaction_value) - processing_cost. Revenue recovered from higher approval rates must exceed the cost increase from routing to a more expensive but more reliable processor.