# 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.

Source: https://www.coritan.com/docs/organizations/storefront/commerce-payment-routing/

*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](/docs/organizations/storefront/commerce-payment-pages/) 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](/docs/api/reference/organizations/commerce/commerce/). The dashboard does the same on the **Payment routing** card, as [Route payments](/docs/organizations/commerce/payments/#route-payments) describes.

## Before you begin

- 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](/docs/organizations/storefront/commerce-api/#add-regions) describes.
- The daily limits count from midnight in the organization's time zone: the **Timezone** under **Invoicing** in the [organization's settings](/docs/organizations/settings/#set-how-invoices-are-made). Without one, a day runs in UTC.

## Turn routing on

```bash
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](#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`.

## How a cart gets its route

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.

### What is checked each time

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.

### What the cart is offered

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

`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](/docs/organizations/storefront/commerce-api/#find-orders) describes.

## Pause the rollout

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

```bash
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`.

## Read the routing stats

```bash
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.

## Result

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.

## Troubleshooting

`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.

## Related

- [Take payments on your own payment pages](/docs/organizations/commerce/payments/)
- [Build a payment page for your store](/docs/organizations/storefront/commerce-payment-pages/)
- [Build a checkout with the Store API](/docs/organizations/storefront/store-api/)
- [Sell with the Commerce API](/docs/organizations/storefront/commerce-api/)

## API

- `GET /api/v1/orgs/{org_slug}/commerce/payment-routing/stats`: How the store's payment routing splits its carts (https://www.coritan.com/docs/api/reference/organizations/commerce/commerce/#op-get-api-v1-orgs-org-slug-commerce-payment-routing-stats)
