Data as of Sep 18, 2026 · Based on 292 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
To dynamically route transactions to the cheapest processor, explore dedicated payment orchestration platforms. Payrails and
ProcessOut provide robust tools for real-time cost-based routing rules. For teams prioritizing open-source control and cost-efficiency, Hyperswitch is a frequent recommendation. If your operations are concentrated in specific regions like LATAM or APAC,
is noted for its AI-driven optimization in those markets.
Yes. What you’re describing is a **payment orchestration layer**: your application sends one payment request to the orchestrator, and the orchestrator chooses among multiple processors based on transaction economics and performance.…
Yes. What you’re describing is a payment orchestration layer: your application sends one payment request to the orchestrator, and the orchestrator chooses among multiple processors based on transaction economics and performance.
A good architecture would look like:
┌──────────────────┐
Customer ──────────▶│ Your Checkout/API │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Payment │
│ Orchestrator │
├──────────────────┤
│ Routing Engine │
│ Cost Calculator │
│ Risk Rules │
│ Failover/Retry │
│ Token Vault │
└───────┬──────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Processor A Processor B Processor C
I wouldn't simply choose the processor with the lowest advertised fee. For each transaction, calculate something like:
effective_cost =
processor_fee
+ network/interchange cost
+ FX cost
+ expected_retry_cost
+ expected_failure_cost
Then combine that with authorization probability:
expected_value(route) =
authorization_probability × transaction_margin
- effective_cost
For example:
Transaction:
$100
Visa
US-issued card
US merchant
Processor A:
cost: $2.10
auth probability: 98.5%
Processor B:
cost: $1.70
auth probability: 94.0%
Processor C:
cost: $2.00
auth probability: 99.1%
The router shouldn't blindly select B just because $1.70 is lowest. A slightly more expensive route can have a substantially better expected outcome. Modern routing systems similarly consider cost alongside card characteristics, geography, and real-time processor performance.
Start with rules + telemetry, not ML.
POST /v1/payments
{
"amount": 10000,
"currency": "USD",
"payment_method": "pm_xxx",
"customer": "cus_xxx",
"capture": true
}
Your application shouldn't need to know which processor receives it.
Create a common interface:
interface Processor {
authorize(payment: Payment): Promise<Result>;
capture(paymentId: string): Promise<Result>;
refund(paymentId: string, amount?: number): Promise<Result>;
void(paymentId: string): Promise<Result>;
}
Then implement:
StripeAdapter
AdyenAdapter
CheckoutAdapter
WorldpayAdapter
...
Start with deterministic rules:
rules:
- if:
currency: USD
card_country: US
routes:
- processor_a
- processor_c
- processor_b
- if:
currency: EUR
routes:
- processor_c
- processor_a
Then add dynamic inputs:
processor
card network
BIN / issuer country
merchant country
currency
transaction amount
MCC
recurring vs one-time
processor health
authorization rate
latency
effective cost
Maintain rolling metrics such as:
auth_rate_5m
auth_rate_1h
p95_latency
technical_error_rate
cost_per_transaction
timeout_rate
The router can then produce:
{
"processor": "processor_c",
"score": 0.973,
"reason": {
"estimated_cost": 1.94,
"estimated_auth_rate": 0.991,
"health": "healthy"
}
}
If the selected processor has a technical failure, route to the next eligible processor.
Be careful with retries: you need idempotency keys and transaction-state tracking so that a timeout doesn't accidentally result in two successful charges.
This is one of the biggest architectural issues. If your card tokens are processor-specific, switching processors can require re-tokenization or re-collecting payment details. Processor-agnostic orchestration therefore benefits enormously from a vault/tokenization strategy that isn't locked to one processor.
I'd make the router produce an ordered list rather than a single processor:
eligible processors
↓
remove unavailable/ineligible processors
↓
calculate actual transaction cost
↓
estimate authorization probability
↓
apply merchant/risk/business rules
↓
rank routes
↓
execute first route
↓
technical failure?
yes → next route
no → finish
↓
record outcome
That gives you a clean path from a simple rules engine today to statistical/ML optimization later.
If you're building this for your own company, integrating directly with several PSPs/processors may make sense.
If you're building it as a product for other merchants, the problem gets substantially harder: PCI scope, token portability, processor contracts, reconciliation, settlement differences, disputes, 3DS/SCA, refunds, webhooks, and merchant-specific routing rules all become first-class concerns. Orchestration doesn't eliminate those responsibilities; it moves them into your platform.
There are also existing orchestration platforms such as Spreedly that already provide multi-provider connectivity and routing, so whether you should build vs. buy depends heavily on your transaction volume, geographic footprint, and how differentiated the routing engine needs to be.
If you're building this as a product, I can also lay out a production-grade architecture (API + database schema + routing algorithm + processor adapter interface + idempotency/failover model) for an MVP.
Implementing **Least Cost Routing (LCR)** via a Payment Orchestration Platform (POP) is one of the fastest ways to shave percentage points off your processing fees . However, doing it right requires balancing processing fees against **authorization rates** —because routing a transaction to a "cheaper" processor that…
Implementing Least Cost Routing (LCR) via a Payment Orchestration Platform (POP) is one of the fastest ways to shave percentage points off your processing fees . However, doing it right requires balancing processing fees against authorization rates —because routing a transaction to a "cheaper" processor that subsequently declines it costs you more in lost revenue than you saved.
Top payment orchestration platforms support dynamic, cost-based routing along with how they handle the mechanics of LCR:
To build a true LCR engine, your orchestrator evaluates a transaction through a strict hierarchy:
To help narrow down which platform fits your architecture, tell me:
Yes. What you want is a **payment orchestration layer**: one API in front of multiple processors, with a routing engine deciding where each transaction goes. A good architecture would look like: ```
Yes. What you want is a payment orchestration layer: one API in front of multiple processors, with a routing engine deciding where each transaction goes.
A good architecture would look like:
┌──────────────────────┐
│ Your Checkout │
└──────────┬───────────┘
│
Payment API
│
┌───────────▼───────────┐
│ Payment Orchestrator │
│ │
│ 1. Eligibility │
│ 2. Cost calculation │
│ 3. Risk/health │
│ 4. Routing decision │
│ 5. Failover/retry │
└───────┬───┬───┬───────┘
│ │ │
Stripe Adyen Worldpay
│ │ │
└───┴───┘
For every transaction, calculate something like:
effective_cost =
processor_fee
+ network/interchange cost
+ expected_cost_of_failure
+ expected_retry_cost
Then choose the cheapest processor that is actually eligible.
For example:
Transaction:
$120
USD
US-issued Visa credit
Merchant: US
Recurring: no
Processor Fee Auth rate Expected cost
-----------------------------------------------------
Stripe $3.78 94% $6.58
Adyen $3.42 96% $5.02
Worldpay $3.18 91% $9.66
→ Route to Adyen
The authorization-rate component is critical. Sending everything to the nominally cheapest processor can actually lose money if that processor declines more transactions. Dynamic routing systems commonly consider processor performance alongside cost.
def choose_processor(payment, processors):
candidates = [
p for p in processors
if p.supports(payment)
and p.is_healthy()
and p.is_configured_for(payment.merchant)
]
scored = []
for p in candidates:
fee = p.calculate_fee(payment)
approval = p.predicted_approval_rate(payment)
latency = p.predicted_latency(payment)
expected_failure_cost = (
(1 - approval) * payment.margin
)
score = (
fee
+ expected_failure_cost
+ latency * LATENCY_WEIGHT
)
scored.append((score, p))
return min(scored, key=lambda x: x[0])[1]
I'd initially make this rules + economics, rather than jumping directly to an ML model. Once you have enough volume, you can replace predicted_approval_rate() with a model trained on BIN × processor × country × currency × payment type × amount × historical outcome.
Don't make your orchestrator blindly retry every decline.
You need to distinguish:
Hard decline → don't retry
Soft decline → potentially retry
Processor timeout → fail over
Processor outage → fail over
Invalid request → don't retry
3DS/auth failure → usually don't blindly retry
Fraud rejection → don't circumvent controls
Otherwise your "optimization" layer can create duplicate authorizations, unnecessary processor fees, or undesirable fraud behavior. Stripe's current orchestration documentation, for example, explicitly has restrictions around 3DS and unsupported features when performing cross-processor retries.
There are already products in this category. docs.stripe.com supports multi-processor routing and cross-processor retries, although its documentation currently describes the feature as private preview. Stripe Docs docs.paymentkit.com is another option that exposes routing across multiple processors.
If you're building this as a product, though, I'd make the differentiator more specific than "cheapest processor." The compelling product is:
"For every payment, choose the route that maximizes contribution margin subject to an authorization-rate SLA." That lets you optimize profit per attempted transaction, rather than merely minimizing processing fees.
effective_cost =
processor_fee
+ network/interchange cost
+ expected_cost_of_failure
+ expected_retry_cost
Then choose the cheapest processor that is actually eligible.
For example:
Transaction:
$120
USD
US-issued Visa credit
Merchant: US
Recurring: no
Processor Fee Auth rate Expected cost
-----------------------------------------------------
Stripe $3.78 94% $6.58
Adyen $3.42 96% $5.02
Worldpay $3.18 91% $9.66
→ Route to Adyen
The authorization-rate component is critical. Sending everything to the nominally cheapest processor can actually lose money if that processor declines more transactions. Dynamic routing systems commonly consider processor performance alongside cost.
To dynamically route transactions to the cheapest payment processor in real time, you need a **Payment Orchestration Platform (POP)** featuring a **smart/intelligent routing engine** capable of cost-based optimization.[](https://corefy.com/blog/best-payment-routing-software)…
To dynamically route transactions to the cheapest payment processor in real time, you need a Payment Orchestration Platform (POP) featuring a smart/intelligent routing engine capable of cost-based optimization.
When configuring this, the orchestrator evaluates hard constraints (like avoiding a down processor), geographical/interchange rules (like local vs. cross-border acquirers), and finally applies cost-based logic to select the lowest interchange-plus or flat-rate provider for that exact transaction profile.
Top payment orchestration platforms that support advanced, rule-based, or cost-optimized intelligent routing include:
To help narrow down which orchestrator fits your stack, could you share:
A **payment orchestrator** is a routing layer between your checkout/payment API and multiple processors (PSPs/acquirers). It can choose the processor per transaction using cost, approval probability, latency, geography, and business rules. This pattern is commonly called **payment orchestration** or **smart payment…
A payment orchestrator is a routing layer between your checkout/payment API and multiple processors (PSPs/acquirers). It can choose the processor per transaction using cost, approval probability, latency, geography, and business rules. This pattern is commonly called payment orchestration or smart payment routing.
A good architecture would look like this:
Customer Checkout
|
v
Payment Orchestrator API
|
+---------------+---------------+
| | |
v v v
Stripe Adyen Worldpay
| | |
+---------------+---------------+
|
v
Settlement + Analytics
Expose one interface to your application:
POST /payments
{
"amount": 4999,
"currency": "USD",
"payment_method": "card",
"customer_country": "US",
"card_bin": "424242",
"merchant_id": "merchant_123"
}
The orchestrator decides where it goes.
Create a connector abstraction:
interface PaymentProcessor {
authorize(payment: Payment): Promise<Result>;
capture(id: string): Promise<Result>;
refund(id: string, amount: number): Promise<Result>;
}
class StripeProcessor implements PaymentProcessor {}
class AdyenProcessor implements PaymentProcessor {}
class WorldpayProcessor implements PaymentProcessor {}
Adding a new processor becomes a configuration change instead of an application rewrite.
Start with rules:
{
"rules": [
{
"condition": {
"currency": "EUR",
"card_type": "debit"
},
"route": "adyen"
},
{
"condition": {
"amount": ">10000"
},
"route": "worldpay"
}
]
}
Then evolve into scoring:
processor_score =
(cost_weight × fee_score)
+ (approval_weight × success_rate)
+ (latency_weight × speed_score)
+ (risk_weight × risk_score)
Example:
| Processor | Fee | Approval | Latency | Score |
|---|---|---|---|---|
| Stripe | 2.4% | 97% | 220ms | 91 |
| Adyen | 2.0% | 94% | 180ms | 89 |
| Worldpay | 1.7% | 92% | 350ms | 84 |
Route to Stripe if maximizing approvals; route to Worldpay if minimizing cost.
Maintain live metrics:
processor_health
----------------
stripe
approval_rate: 97.2%
latency_p95: 210ms
error_rate: 0.4%
adyen
approval_rate: 96.1%
latency_p95: 160ms
error_rate: 0.2%
A processor with outages or elevated declines should automatically receive less traffic.
Example flow:
Attempt 1:
Stripe
↓
soft decline
Attempt 2:
Adyen
↓
approved
Payment orchestration platforms commonly implement processor fallback and retry routing for failed transactions.
Your engine needs actual effective cost, not just advertised rates:
effective_cost =
interchange
+ processor_markup
+ network_fees
+ FX_cost
+ failure_cost
A processor that is 10 bps cheaper but declines 3% more payments may be worse.
Events:
payment.created
payment.routed
payment.authorized
payment.failed
processor.health_changed
processors
-----------
id
name
active
base_fee
routing_rules
-------------
id
condition_json
processor_id
priority
payment_attempts
----------------
payment_id
processor_id
status
latency_ms
cost
processor_metrics
-----------------
processor_id
approval_rate
failure_rate
updated_at
At scale, replace static rules with machine learning:
Input features:
- BIN
- country
- currency
- amount
- merchant category
- device
- previous processor results
- time of day
Prediction:
P(success | processor, transaction)
Then optimize:
maximize:
approval_probability
- transaction_cost
- latency_penalty
Adaptive routing systems can use feedback loops to continuously adjust processor selection based on observed outcomes.
A production-grade version is essentially a traffic router + decision engine + payment adapter layer + analytics system. This is the same architectural category used by commercial payment orchestration systems that route across multiple PSPs.
To route transactions dynamically to the cheapest processor, you need a **Payment Orchestration Platform (POP)** equipped with an **intelligent smart routing engine** supporting **Least Cost Routing (LCR)**.[](https://juspay.io/payment-orchestration)…
To route transactions dynamically to the cheapest processor, you need a Payment Orchestration Platform (POP) equipped with an intelligent smart routing engine supporting Least Cost Routing (LCR).
A pure LCR engine doesn't just look at a flat percentage; it evaluates a cascading hierarchy of constraints in real time: hard uptime limits (circuit breakers), geographic/interchange optimization (e.g., matching local cards to local acquirers to slash cross-border fees), and finally, the lowest processor markup.
Top platforms that provide enterprise-grade payment orchestration and intelligent/cost-based routing include:
To help narrow down the best platform for your architecture, could you share:
Yes. What you want is a **payment orchestration layer**: your application integrates once with the orchestrator, and the orchestrator chooses among multiple processors/acquirers for each transaction. Dynamic routing can consider cost, authorization probability, geography, card/network attributes, and processor health.…
Yes. What you want is a payment orchestration layer: your application integrates once with the orchestrator, and the orchestrator chooses among multiple processors/acquirers for each transaction. Dynamic routing can consider cost, authorization probability, geography, card/network attributes, and processor health.
┌─────────────────────┐
│ Your Checkout │
└──────────┬──────────┘
│
Payment API
│
┌──────────▼──────────┐
│ Payment Orchestrator │
│ │
│ 1. Normalize │
│ 2. Risk/compliance │
│ 3. Cost engine │
│ 4. Route selector │
│ 5. Failover │
└─────┬─────┬─────┬───┘
│ │ │
┌─────▼┐ ┌──▼──┐ ┌▼─────┐
│PSP A │ │PSP B │ │PSP C │
└──────┘ └─────┘ └──────┘
│ │ │
┌─────▼─────▼─────▼─────┐
│ Acquirers / │
│ Card Networks │
└───────────────────────┘
The key point is don't literally route to the cheapest processor in every case. The cheapest route that has a materially lower authorization probability can cost you more overall.
I'd use an objective such as:
expected_cost =
processor_fee
+ network/interchange_cost
+ expected_retry_cost
+ expected_failure_cost
expected_value =
authorization_probability × transaction_margin
- expected_cost
Then select the processor maximizing expected value, subject to hard constraints.
For each payment, your engine could evaluate:
This is broadly consistent with how modern dynamic-routing systems approach the problem.
1. Processor adapter interface
Give every processor the same internal interface:
authorize(payment) -> authorization_result
capture(payment) -> capture_result
refund(payment) -> refund_result
void(payment) -> void_result
Your business logic never talks directly to Stripe/Adyen/etc.
2. Cost model
Maintain processor pricing as configuration rather than hard-coding it:
{
"processor": "processor_a",
"currency": "USD",
"card_type": "credit",
"percentage_fee": 0.029,
"fixed_fee": 0.30,
"cross_border_fee": 0.01
}
You'll want a richer model in production because actual payment costs can include interchange, scheme fees, processor markup, FX and other components. Adyen's documentation, for example, distinguishes interchange, scheme fees, markup and commission.
3. Routing engine
Start deterministic:
IF processor_a unhealthy
→ processor_b
ELSE IF country == US AND card == debit
→ cheapest eligible US-debit route
ELSE IF currency == EUR
→ cheapest eligible European route
ELSE
→ processor with lowest expected total cost
Then introduce statistical/ML optimization after you've accumulated enough transaction data.
4. Failover
If the selected processor times out or has a retryable technical failure:
Route 1 → timeout
↓
Route 2 → authorization
Be extremely careful here: don't blindly retry an ambiguous payment, because the first processor may have authorized it even though your system didn't receive the response. Use idempotency keys and processor-specific reconciliation before retrying.
5. Token portability
This is one of the biggest architectural gotchas. If processor A owns the only usable card token, you can't necessarily send that token to processor B. Dynamic routing therefore needs a tokenization strategy that permits the same payment credential to be used across your eligible processors. Stripe specifically highlights credential portability as a key constraint for dynamic routing.
6. Reconciliation
Treat reconciliation as a first-class subsystem, not an afterthought:
Payment
├── authorization attempts
├── captures
├── refunds
├── processor transaction IDs
├── fees
├── settlement
└── disputes
Multiple processors make reconciliation substantially more complicated because payment data becomes fragmented across providers.
If you're processing serious volume, I'd first evaluate an existing orchestration product rather than immediately building all of this yourself. Payment orchestration is deceptively complex: multiple PSP integrations, tokens, retries, reconciliation, compliance, routing models, monitoring and settlement all become your responsibility.
If you're building it yourself, I'd start with:
The resulting MVP can be surprisingly small: a normalized payment API + processor adapters + pricing engine + routing engine + transaction ledger + reconciliation pipeline.
If you tell me your processors (e.g. Stripe, Adyen, Braintree), countries/currencies, and approximate monthly transaction volume, I can lay out the concrete architecture, database schema, routing algorithm, and API for this orchestrator.
Your business logic never talks directly to Stripe/Adyen/etc.
2. Cost model
Maintain processor pricing as configuration rather than hard-coding it:
{
"processor": "processor_a",
"currency": "USD",
"card_type": "credit",
"percentage_fee": 0.029,
"fixed_fee": 0.30,
"cross_border_fee": 0.01
}
You'll want a richer model in production because actual payment costs can include interchange, scheme fees, processor markup, FX and other components. Adyen's documentation, for example, distinguishes interchange, scheme fees, markup and commission.
3. Routing engine
Start deterministic:
IF processor_a unhealthy
→ processor_b
ELSE IF country == US AND card == debit
→ cheapest eligible US-debit route
ELSE IF currency == EUR
→ cheapest eligible European route
ELSE
→ processor with lowest expected total cost
Then introduce statistical/ML optimization after you've accumulated enough transaction data.
4. Failover
If the selected processor times out or has a retryable technical failure:
Route 1 → timeout
↓
Route 2 → authorization
Be extremely careful here: don't blindly retry an ambiguous payment, because the first processor may have authorized it even though your system didn't receive the response. Use idempotency keys and processor-specific reconciliation before retrying.
5. Token portability
This is one of the biggest architectural gotchas. If processor A owns the only usable card token, you can't necessarily send that token to processor B. Dynamic routing therefore needs a tokenization strategy that permits the same payment credential to be used across your eligible processors. Stripe specifically highlights credential portability as a key constraint for dynamic routing.
6. Reconciliation
Treat reconciliation as a first-class subsystem, not an afterthought:
Payment
├── authorization attempts
├── captures
├── refunds
├── processor transaction IDs
├── fees
├── settlement
└── disputes
Multiple processors make reconciliation substantially more complicated because payment data becomes fragmented across providers.
If you're processing serious volume, I'd first evaluate an existing orchestration product rather than immediately building all of this yourself. Payment orchestration is deceptively complex: multiple PSP integrations, tokens, retries, reconciliation, compliance, routing models, monitoring and settlement all become your responsibility.
If you're building it yourself, I'd start with:
**Payment Orchestration Platforms (POPs) with cost-based and dynamic multi-acquirer routing** allow merchants to programmatically analyze variables like interchange fees, card brand scheme costs, geographical rails, and processor fees to send each transaction down the cheapest…
Payment Orchestration Platforms (POPs) with cost-based and dynamic multi-acquirer routing allow merchants to programmatically analyze variables like interchange fees, card brand scheme costs, geographical rails, and processor fees to send each transaction down the cheapest path.
Top Payment Orchestration Platforms for Cost-Based Routing
| Platform | Primary Strength for Routing | Best Suited For |
|---|---|---|
| Adyen | Native Interchange++ pricing and automated least-cost routing across global schemes. | High-volume enterprises wanting unified acquiring. |
| Checkout.com | Advanced algorithmic routing and optimization via ProcessOut integration. | Mid-market to enterprise digital commerce. |
| Worldpay | Proprietary debit network routing to bypass high-cost interchange tiers. | High-frequency card and debit volume. |
| Yuno | Multi-gateway orchestration with flexible fallback and cost configurations. | Global scaling across fragmented regional processors. |
Key Considerations for Cost-Driven Routing
To help narrow down the best platform, could you share:
To dynamically route transactions to the cheapest processor, you need a **Payment Orchestration Platform (POP)** that features a smart routing engine capable of **Least-Cost Routing (LCR)**.[](https://juspay.io/payment-orchestration)…
To dynamically route transactions to the cheapest processor, you need a Payment Orchestration Platform (POP) that features a smart routing engine capable of Least-Cost Routing (LCR).
When looking for an orchestrator with this capability, you generally choose between open-source/self-hosted infrastructure or commercial enterprise SaaS platforms.
Top Payment Orchestrators with Least-Cost Routing
- **How it works:** An open-source payment switch that allows you to self-host or use their managed service. It features native **Least Cost Routing (LCR)** and multi-objective optimization (balancing authorization rates and processing fees).
- **Best for:** Teams with engineering resources who want zero licensing/orchestration overhead and complete data control.
- **Check out:** [Hyperswitch Documentation](https://docs.hyperswitch.io/integration-guide/workflows/intelligent-routing) [[1]](https://docs.hyperswitch.io/integration-guide/workflows/intelligent-routing/routing-strategies/least-cost-routing)[[2]](https://docs.hyperswitch.io/integration-guide/workflows/intelligent-routing)
- **How it works:** Connects to hundreds of payment providers and acquirers through a single API. It includes a dedicated control layer for automated routing, cascading, and fee-based logic.
- **Best for:** High-volume merchants who want a robust, pre-built management dashboard with extensive global connections.
- **Check out:** [Corefy Payment Routing Software](https://corefy.com/blog/best-payment-routing-software) [[1]](https://corefy.com/blog/best-payment-routing-software)
- **How it works:** Offers dynamic orchestration across 300+ PSPs globally with explicit least-cost routing rules that automatically evaluate interchange fees in real time.
- **Best for:** Enterprise-scale operations looking to slash transaction processing overhead.
- **Check out:** [Juspay Payment Orchestration](https://juspay.io/) [[1]](https://juspay.io/payment-orchestration)
- **How it works:** Uses a visual "workflow" approach rather than hardcoded rules. You can build logic loops that factor in real-time processor availability, performance, and cost parameters.
- **Best for:** Product and finance teams who want to build custom logic visually without heavy dev maintenance.
- **Check out:** Primer Insights[](https://corefy.com/blog/best-payment-routing-software) [[1]](https://corefy.com/blog/best-payment-routing-software)
The Reality Check: How LCR Actually Works
Before setting this up, keep in mind how dynamic cost routing functions under the hood:
To help narrow down the right platform, tell me:
Yes. What you want is a **payment orchestration layer**: one API in front of multiple processors, with a routing engine that chooses the best processor per transaction. The key is that **“cheapest” should mean lowest expected total cost**, not simply the lowest advertised processing percentage. Processor economics can…
Yes. What you want is a payment orchestration layer: one API in front of multiple processors, with a routing engine that chooses the best processor per transaction.
The key is that “cheapest” should mean lowest expected total cost, not simply the lowest advertised processing percentage. Processor economics can include interchange, network fees, fixed transaction fees, processor markup, FX, declines/retries, and other costs. For example, Adyen currently exposes interchange-plus pricing in some configurations, while Stripe supports both blended and interchange-plus models.
┌─────────────────────┐
│ Your application │
└──────────┬──────────┘
│
Payment API
│
┌──────────▼──────────┐
│ Payment Orchestrator│
│ │
│ 1. Normalize │
│ 2. Eligibility │
│ 3. Cost estimator │
│ 4. Route optimizer │
│ 5. Failover │
│ 6. Ledger/events │
└───┬─────┬─────┬─────┘
│ │ │
┌───▼─┐ ┌─▼──┐ ┌▼────┐
│Stripe│ │Adyen│ │Other │
└─────┘ └────┘ └──────┘
Stripe itself now has an Orchestration product that can route payments between supported processors and retry failures on another processor, although its documentation currently describes it as private preview.
For each transaction, calculate something like:
expected_cost =
processor_fee
+ interchange
+ network_fee
+ FX_cost
+ expected_retry_cost
+ expected_failure_revenue_loss
Then:
eligible = [
p for p in processors
if p.supports(
currency=tx.currency,
country=tx.country,
payment_method=tx.payment_method,
merchant_category=tx.mcc,
)
]
for p in eligible:
p.score = expected_total_cost(p, tx)
processor = min(eligible, key=lambda p: p.score)
But I would not blindly choose the absolute cheapest processor. A processor that's 5¢ cheaper but has a 2% lower authorization rate can make the transaction considerably more expensive overall.
A better objective is:
maximize:
expected_net_revenue
= transaction_value × authorization_probability
- processing_cost
- expected_fraud_loss
- expected_retry_cost
Store processor performance by dimensions such as:
Then your router can learn things like:
US Visa / USD / $20–50
Processor A → 94.1% auth / $0.88 expected cost
Processor B → 96.0% auth / $0.91 expected cost
Processor C → 92.7% auth / $0.79 expected cost
=> choose B
This is why payment orchestration can improve economics beyond simply negotiating processor rates: routing can account for both cost and payment performance.
I'd make these separate services/modules:
This is where I'd be particularly careful.
You cannot simply retry every decline on another processor. Some declines are hard declines, fraud decisions, authentication failures, insufficient funds, etc. Retrying can create duplicate authorization/capture risk.
Your state machine should distinguish:
AUTHORIZED
CAPTURED
DECLINED_SOFT
DECLINED_HARD
RETRYABLE
FAILED
REFUNDED
CHARGEBACK
And every attempt needs an idempotency key such as:
merchant_id + order_id + payment_attempt
with a globally unique internal transaction ID.
I would initially avoid ML.
Use deterministic routing:
routes:
- when:
currency: USD
card_country: US
amount:
lt: 100
candidates:
- adyen
- stripe
objective: lowest_expected_cost
- when:
currency: EUR
card_country: DE
candidates:
- adyen
objective: lowest_expected_cost
fallback:
- stripe
Once you've accumulated enough transaction history, introduce a scoring model.
That gives you a very important property: every routing decision is explainable.
If you're building this as a product rather than just an internal system, don't compete merely on "we connect you to multiple processors." Existing orchestration providers already do that; Spreedly, for example, explicitly positions orchestration around multiple gateways, routing, retries, and optimization.
Your differentiation could instead be:
“We continuously minimize your effective payment cost while protecting authorization rate.” That gives you a measurable optimization target: cost per successfully completed transaction, rather than cost per attempted transaction.
If you tell me your volume (e.g. $1M/month), geography, payment methods, and which processors you currently use, I can sketch the actual routing algorithm, database schema, and API for an MVP.