Skip to content
Coritan Docs

Route carts between ways to pay

Give a share of new carts other ways to pay, with daily limits, an allow list and a kill switch, and read how each route does.

View as Markdown

Payment routing splits your store's carts between two routes, each with its own ways to pay. Most carts take the default route, and a share you choose takes the rollout route, so that you can try a way to pay on real shoppers before you move everyone to it. You can keep your payment page on the default route, for example, and try card and PayPal on a tenth of carts.

Routing is the store's payment_routing setting, which PATCH /commerce/store changes. The Store API then offers each cart only its route's ways to pay, and GET https://api.coritan.com/api/v1/orgs/{org_slug}/commerce/payment-routing/stats reports how each route does. The API reference lists the stats under Commerce. The dashboard does the same on the Payment routing card, as Route payments describes.

  • Changing the setting takes an organization API key with the commerce.store:write scope, or a member's token with the Admin role or above. Reading the stats takes commerce.payments:read or commerce.finance:read, or the Billing role or above.
  • A route offers only what the cart's region offers, so list each way to pay you route in the regions' payment_providers, as Add regions describes.
  • The daily limits count from midnight in the organization's time zone: the Timezone under Invoicing in the organization's settings. Without one, a day runs in UTC.
Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/commerce/store" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"settings": {"payment_routing": {
    "enabled": true,
    "default_providers": ["external"],
    "rollout_providers": ["stripe", "paypal"],
    "percent": 10,
    "allow_emails": ["alex@example.com"],
    "max_order_total": {"USD": 50000},
    "daily_order_cap": 40,
    "daily_amount_cap": {"USD": 400000}
  }}}'
Key Takes
enabled true routes carts. false, the default, offers every cart every way to pay its region offers, as without the setting.
default_providers, rollout_providers The ways to pay on each route, from stripe, paypal, manual and external. Each list names at least one, and none is on both. They default to ["external"] and ["stripe", "paypal"].
percent The share of new carts given the rollout route, from 0 to 100 with up to two decimals. Defaults to 0.
allow_emails Up to 200 email addresses, or *@example.com for a whole domain, whose carts always take the rollout route. List your team's addresses to try the rollout route before shoppers do.
paused true stops the rollout at once, as Pause the rollout describes. Defaults to false.
max_order_total The largest total the rollout route takes in each currency, in minor units, such as {"USD": 50000}.
daily_order_cap How many rollout orders a day can place before new carts take the default route, up to 1,000,000. null, the default, sets no limit.
daily_amount_cap The same for what the day's rollout orders add up to in each currency, in minor units.
salt What the split is worked out with: 16 to 128 letters, digits, - and _. Leave it out, and we make one on the first save and keep it.

Send the whole setting each time: a key you leave out takes its default, except salt, which keeps its saved value. Sending another salt reshuffles the carts that have no route yet. GET /commerce/store answers the setting as saved, salt included, so you can change it and send it back.

Setting payment_routing to null removes it, turns routing off and drops the salt, so a later save makes a new one. A setting that is wrong answers 422 with invalid, and field names the key, such as settings.payment_routing.percent. A way to pay on both lists names settings.payment_routing.rollout_providers, and an amount names its currency, such as settings.payment_routing.max_order_total.USD.

A cart is given its route the first time the Store API lists its ways to pay or starts a payment for it, and it keeps that route, even when the shopper signs in.

The route follows the shopper rather than the visit. We work it out from the first of these that the cart has:

  1. Its signed-in customer.
  2. Its email address, trimmed and in lower case.
  3. The visitor_id in its metadata, of 8 to 64 letters, digits and -, such as an ID your storefront keeps in a cookie. Set it when you create the cart, so that a guest who has not given an email address yet keeps one route across carts.
  4. The cart itself.

That key, written as customer:<id>, email:<address>, visitor:<id> or cart:<cart id>, gives the cart a number from 0 to 9,999: the first 8 hex digits of the HMAC-SHA256 of <store id>:<key>, keyed with the salt, read as a number and taken modulo 10,000. The store's id is in GET /commerce/store. The cart takes the rollout route when its number is below the share times 100 and neither daily limit is reached, so a percent of 10 takes the numbers 0 to 999. Otherwise it takes the default route.

  • The same key gets the same number for as long as the salt stays, so a shopper's carts take the same route while the share stays the same.
  • Changing the share moves only the carts that have no route yet.
  • Our staff can set a ceiling for your store, and a higher percent counts as the ceiling. The stats answer it as rollout_max_percent, with the effective_percent that results.
  • The daily limits are checked when a cart is given its route. They count the orders of the cart's mode paid on the rollout route since midnight. Once they reach daily_order_cap, or their total in the cart's currency reaches its daily_amount_cap, new carts take the default route, and carts that already have the rollout route keep it.

Each time the Store API lists a cart's ways to pay or starts a payment, the first of these that applies decides the route:

  1. With paused: true, the cart is offered the default route's ways to pay.
  2. A cart whose email address, or whose signed-in shopper's address, is on allow_emails takes the rollout route, whatever the share, the limits or its total.
  3. A cart whose total is above its currency's max_order_total is offered the default route's ways to pay while its total stays above it.
  4. Otherwise, the cart takes the route it was given.

None of these changes the route the cart was given.

The cart is offered the ways to pay that its region offers and its route lists, in the usual order. A route never falls back on the other one: when none of its ways to pay can take the cart, such as a payment page that does not take the cart's currency, the cart is offered nothing. manual takes test carts only, so a route that holds nothing else offers live carts nothing.

Read the route in the Store API

Section titled Read the route in the Store API

GET /store/carts/{cart_id}/payment-providers lists only the route's ways to pay, and its route says which route the cart is on now: default, rollout, or null while routing is off.

JSON
{
  "payment_providers": [
    {"id": "external", "display_name": "Credit / Debit Card", "test": false, "config": {"display_name": "Credit / Debit Card", "description": "Pay with your card, Apple Pay or Google Pay.", "brands": ["visa", "mastercard", "amex", "apple_pay", "google_pay"]}}
  ],
  "payment_required": true,
  "total": 5896,
  "currency_code": "USD",
  "route": "default"
}

Show the shopper only what the listing answers, and list it again when the cart changes, because a new total can take the cart above or below max_order_total. Starting a payment with a way to pay that the region offers but the route does not answers 409 with payment_provider_not_offered, with the cart's route and the ways to pay it offers in available. A way to pay that cannot take the cart at all answers 422 with invalid.

The payment session and the order keep the route. An order's payment_route is default or rollout, or null when routing was off or nothing was paid. GET /commerce/orders?payment_route=rollout lists the orders paid on the rollout route, and payment_route=none those paid with routing off, as Find orders describes.

Send the setting again with "paused": true, and the rest as it was:

Shell
curl -X PATCH "https://api.coritan.com/api/v1/orgs/acme/commerce/store" \
  -H "X-API-Key: $ORG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"settings": {"payment_routing": {
    "enabled": true,
    "paused": true,
    "default_providers": ["external"],
    "rollout_providers": ["stripe", "paypal"],
    "percent": 10,
    "allow_emails": ["alex@example.com"],
    "max_order_total": {"USD": 50000},
    "daily_order_cap": 40,
    "daily_amount_cap": {"USD": 400000}
  }}}'

While paused is true, every cart, those on the allow list included, is offered the default route's ways to pay. Each cart keeps the route it was given, so "paused": false puts it back. To end routing, send "enabled": false.

Shell
curl "https://api.coritan.com/api/v1/orgs/acme/commerce/payment-routing/stats?from=2026-09-28&to=2026-10-04&livemode=true" \
  -H "X-API-Key: $ORG_API_KEY"
Parameter Takes
from, to Days such as 2026-10-01 in the organization's time zone, both included. to defaults to today, and from to 6 days before to. The stats cover up to 92 days.
livemode true for live carts or false for test carts. Defaults to the store's own mode.
JSON
{
  "routing": {"enabled": true, "paused": false, "default_providers": ["external"], "rollout_providers": ["stripe", "paypal"], "percent": 10, "rollout_max_percent": null, "effective_percent": 10, "allow_emails": ["alex@example.com"], "max_order_total": {"USD": 50000}, "daily_order_cap": 40, "daily_amount_cap": {"USD": 400000}},
  "time_zone": "America/New_York",
  "livemode": true,
  "from": "2026-09-28",
  "to": "2026-10-04",
  "today": {"date": "2026-10-04", "rollout_orders": 12, "rollout_amounts": {"USD": 118800}, "daily_order_cap": 40, "daily_amount_cap": {"USD": 400000}, "order_cap_reached": false, "amount_cap_reached": {"USD": false}},
  "days": [
    {"date": "2026-09-28", "routes": {
      "default": {"carts_assigned": 310, "sessions_started": 120, "sessions_failed": 9, "orders": 96, "revenue": {"USD": 951200}},
      "rollout": {"carts_assigned": 33, "sessions_started": 14, "sessions_failed": 1, "orders": 11, "revenue": {"USD": 104500}}
    }}
  ]
}
  • routing is the setting as it acts now, without its salt, with our staff's ceiling as rollout_max_percent (null for none) and the effective_percent that results.
  • time_zone is the time zone the days run in.
  • today counts the rollout route's orders since midnight, and their totals per currency, against the daily limits, and says whether each limit is reached.
  • days holds each day from from to to, oldest first. For each route, carts_assigned counts the carts given the route that day, sessions_started the payments started on it and sessions_failed those that failed, and orders the orders paid on it, with their totals per currency in revenue.

A cart counts on the day it was given its route, a payment on the day it started and an order on the day it was placed.

Each new cart takes the default or the rollout route and keeps it. The Store API offers it only its route's ways to pay, each order records its payment_route, and the stats count each route's carts, payments and orders from the day they start.

422 with field set to settings.payment_routing.rollout_providers
A way to pay is on both lists. Each one is on one route at most.
409 with payment_provider_not_offered
The way to pay is not on the cart's route. Offer the shopper only what GET /store/carts/{cart_id}/payment-providers lists.
A cart is offered no way to pay
None of its route's ways to pay can take it: its region does not offer them, they do not take its currency, or the route holds only manual for a live cart. Add a way to pay to the route, or offer one in the region.
The rollout route takes fewer carts than the share
Our staff's ceiling may be lower, which effective_percent shows. A daily limit may be reached, which today shows, and a cart above max_order_total is offered the default route. Carts that had a route before you raised the share keep it.
Shoppers changed route
The salt changed, because you sent another one or set payment_routing to null and saved it again. Leave salt out to keep the saved one.
422 with field set to from or to
Send days as 2026-10-01, with to on or after from, and up to 92 days apart.

API operations on this page

MethodPathWhat it does
GET/api/v1/orgs/{org_slug}/commerce/payment-routing/statsHow the store's payment routing splits its carts