# How Routes Work

A route decides which gateway profile ODUS attempts for a card payment, and where it fails over to after a decline. Unlike a [cascade](https://docs.odus.com/docs/guides/routing/cascade-overview.md), a route can read the payment: the card's brand, BIN, issuer and funding type, the amount and currency, who initiated it, and your own metadata. So you can send cards from one issuer to one gateway and everything else to another.

Routes apply to card payments only, and are managed through the Core API v2. To create one and point payments at it, see [Routing card payments with a route](https://docs.odus.com/docs/guides/routing/routing-card-payments.md).

## Versions

A route is a named container. Its decision tree lives in a **version**, and a version never changes once published. To change a route, publish a new version and activate it. To roll back, activate an earlier one.

Only the active version routes payments. Each payment records the version it was routed by, so activating another version later does not change how an earlier payment is explained.

## The decision tree

A tree is built from four kinds of node:

| Node        | What it does                                                                   |
| ----------- | ------------------------------------------------------------------------------ |
| `condition` | Tests the payment and follows the first branch that matches, or `else`.        |
| `split`     | Sends a fixed share of payments down each of its outputs, such as 20% and 80%. |
| `pool`      | Tries its children in turn, and fails over to the next child after a decline.  |
| `gateway`   | Attempts the payment at one gateway profile.                                   |

Nodes nest freely: a branch or a split's output can lead to a pool, and a pool can hold conditions.

```
{

  "type": "condition",

  "branches": [

    {

      "when": { "field": "card.issuerName", "op": "contains", "value": "Capital One" },

      "then": {

        "type": "pool",

        "strategy": "ordered",

        "children": [

          { "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" },

          { "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" }

        ]

      }

    }

  ],

  "else": { "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" }

}
```

Cards issued by Capital One go to the first gateway, and fail over to the second after a decline. Every other card goes to the second gateway directly.

## Conditions

A condition is a single comparison, `{ "field", "op", "value" }`, or `all`, `any` and `not` over other conditions.

| Field                       | Operators                                | Value                                          |
| --------------------------- | ---------------------------------------- | ---------------------------------------------- |
| `card.brand`                | `eq` `ne` `in` `nin` `exists`            | A card brand, such as `visa`                   |
| `card.bin`                  | `startsWith` `exists`                    | A prefix of 1–8 digits, or a list of them      |
| `card.issuerName`           | `eq` `ne` `in` `nin` `contains` `exists` | The issuing bank's name                        |
| `card.issuerCountry`        | `eq` `ne` `in` `nin` `exists`            | An ISO 3166-1 alpha-2 code, such as `US`       |
| `card.fundingType`          | `eq` `ne` `in` `nin` `exists`            | `credit`, `debit`, `prepaid` or `charge`       |
| `amount`                    | `eq` `ne` `gt` `gte` `lt` `lte`          | A number in minor units                        |
| `currency`                  | `eq` `ne` `in` `nin` `exists`            | A currency code, such as `usd`                 |
| `initiator`                 | `eq` `ne` `in` `nin` `exists`            | `customer` or `merchant`                       |
| `statementDescriptor`       | `eq` `ne` `in` `nin` `contains` `exists` | The payment's statement descriptor             |
| `statementDescriptorSuffix` | `eq` `ne` `in` `nin` `contains` `exists` | The payment's statement descriptor suffix      |
| `metadata.<key>`            | `eq` `ne` `in` `nin` `contains` `exists` | The value of `<key>` in the payment's metadata |

`in` and `nin` take a list, and `exists` takes no value. Metadata compares exactly; every other field ignores case.

`statementDescriptor` and `statementDescriptorSuffix` are the values the payment carries: the ones you sent when you created it, or else those of the first product in its cart. When the payment has no value for the field, a condition on it does not match, just as an [unknown card field](#when-card-data-is-unknown) does not.

## Branches

A `condition` tests its branches in order and follows the first one that matches. When none matches, it follows `else`. Without `else`, nothing is attempted from that node.

A matched branch is final

Once a branch matches, the condition never tries another of its branches or its `else` — not when the gateway in that branch declines, and not when it is inactive. What happens next depends on the node around the condition: inside a pool, the pool moves on to its next child; at the top of the tree, the route ends. To fail over within a branch, put a pool inside it, as the example above does.

## Splits

A `split` sends each payment down one of its `outputs`, in proportion to each output's `percent`. Use it to compare two setups on live traffic, or to move payments to a new gateway a share at a time:

```
{

  "type": "split",

  "outputs": [

    {

      "percent": 20,

      "then": { "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" }

    },

    {

      "percent": 80,

      "then": {

        "type": "pool",

        "strategy": "ordered",

        "children": [

          { "type": "gateway", "gatewayProfileId": "gwp_3RTHQUROb9Bt2dJwhLmTGEykKEe" },

          { "type": "gateway", "gatewayProfileId": "gwp_2mXGTxXIkqv4uSdbm8DJCzvHUIu" }

        ]

      }

    }

  ]

}
```

About 20% of payments go to the first gateway alone. The other 80% go to the second gateway, and fail over to the first after a decline.

A split has 2 to 20 outputs. Each `percent` is a whole number from 1 to 99, and the percentages of one split add up to 100.

The output is chosen from the payment, not drawn again each time:

* **Authorizing the same payment again sends it down the same output**, as long as the split and its percentages have not changed.
* **Each payment is chosen on its own.** A customer's next payment, such as a subscription renewal, may follow a different output.
* **The percentages hold across many payments.** Over a handful of payments, the shares can differ noticeably from them.
* **Two splits in one tree choose independently.**

The chosen output is final

A split never sends a payment down a second output — not when a gateway in the chosen output declines, and not when it is inactive. As with a matched branch, what happens next depends on the node around the split. To fail over within an output, put a pool inside it, as the example above does. To fail over across every gateway, use a `weighted` pool instead.

## Pools

A `pool` tries its children in an order set by its `strategy`:

| Strategy     | Order                                                                             |
| ------------ | --------------------------------------------------------------------------------- |
| `ordered`    | The order the children are written in.                                            |
| `roundRobin` | Written order, starting at a position that moves on from one payment to the next. |
| `weighted`   | A random order, in proportion to `weights`: one weight from 1 to 100 per child.   |

A `weighted` pool picks its whole order at once, so failover after a decline also follows the weights. To send a fixed share of payments down separate paths, with no failover between them, use a [split](#splits).

`maxAttempts` limits how many gateway attempts a pool makes, including those made by nodes inside it. When it is reached, the pool's remaining children are not tried.

## Skipped is not declined

A node that was never attempted is **skipped**. A gateway that was attempted and refused the payment **declined**. A pool moves on after either, but they are not the same:

|                                                   | Skipped                                                          | Declined                        |
| ------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------- |
| Examples                                          | An inactive gateway profile, a condition with no matching branch | The gateway refused the payment |
| Creates a transaction                             | No                                                               | Yes                             |
| Counts toward `maxAttempts` and the attempt limit | No                                                               | Yes                             |
| If nothing else is attempted, the route ends as   | `not_routed`                                                     | `declined`                      |

A gateway profile that is inactive when the payment is authorized is skipped, even though it was active when the version was published. If every gateway a payment can reach is skipped, nothing is attempted. The payment stays `open` and its `latestMessage` says so.

## When card data is unknown

ODUS reads the issuer name, issuer country and funding type from the card's BIN. They are not available for every card, and for some saved cards they are filled in later. A field ODUS does not have is **unknown**, and a comparison against an unknown field is neither true nor false.

An unknown field never matches

A branch whose condition is unknown is not taken, so the payment moves on to the next branch or `else`. This also holds for negative operators: `ne`, `nin` and `not` do not match an unknown field either. A card with no known issuer country is not matched by `{ "field": "card.issuerCountry", "op": "ne", "value": "US" }`, and nothing reports an error.

To decide what happens to those cards yourself, test for them with `exists`, which is always true or false:

```
{ "not": { "field": "card.issuerCountry", "op": "exists" } }
```

`all` is false as soon as one of its terms is false, and `any` is true as soon as one is true, whatever the others are. Only when the result depends on an unknown term is the whole condition unknown.

ODUS may also hold fewer digits of a card's BIN than your prefix has. When the digits it holds agree with your prefix, `startsWith` is unknown, because the card may or may not be in that range.

A metadata key the payment does not have is unknown too.

## Rules every route follows

These hold across the whole tree, and no node can turn them off:

* **One attempt per gateway profile.** A payment is attempted at each gateway profile at most once. A second node naming the same profile is skipped.
* **At most 11 attempts.** One authorization makes at most 11 gateway attempts in total.
* **No failover after an unknown outcome.** When a gateway gives no answer, it may have charged the card, so the route stops there.
* **A pending attempt ends the route.** When an attempt is left waiting on the customer or the gateway, such as for 3D Secure, the route ends at that gateway and does not move on.
* **No failover without time for it.** When too little time is left in the request for another attempt, the route stops instead of starting one. The first attempt is always made.

Automatic retries under a [retry policy](https://docs.odus.com/docs/guides/subscriptions/retry-policy.md) do not go through the route: each retry uses the gateway its retry step names.

## Limits

| Limit                                                            | Value |
| ---------------------------------------------------------------- | ----- |
| Nodes in one version                                             | 100   |
| Branches in one `condition`                                      | 20    |
| Children in one `pool`                                           | 20    |
| Outputs in one `split`                                           | 2–20  |
| `percent` on a split's output                                    | 1–99  |
| Terms in one branch's condition, `all`, `any` and `not` included | 50    |
| Values in one `in`, `nin` or `startsWith` list                   | 1,000 |
| `maxAttempts` on a pool                                          | 1–11  |
