# Amount Classification

A checkout payment has two independent axes. `status` tracks on-chain / relayer progress (`pending → detected → sweeping → bridging → swept` / `failed`). `payment_outcome` is a separate column tracking the economic result of the deposit vs the requested amount. A session can be `swept` (USDC delivered) yet `underpaid` -- the two are kept separate so the status enum stays clean.

payment_outcome: `null` → `succeeded` `overpaid` `underpaid` `failed`

## Outcomes

| Outcome | Condition (checkout) | Extra fields |
| --- | --- | --- |
| `null` | In-flight -- not yet at a terminal state. | — |
| `succeeded` | `settled_amount == amount` | `settled_amount` |
| `overpaid` | `settled_amount > amount` | `settled_amount`, `overpaid_amount = settled - amount` |
| `underpaid` | `settled_amount < amount` | `settled_amount` |
| `failed` | Status reached `failed` (sweep retries exhausted, or bridge failed/expired). | `error_message` |

`amount` is the **expected net (after both fees)** settlement amount, so the comparison is a direct `>=` -- both on-chain fees (the platform fee and any app fee) are already baked into both the contract output and the `minOut` floor, so there is no fee math at classification time. Classification runs once at the terminal state. Anonymous P2P sessions have no `amount` and are simply `succeeded` when swept or `failed` otherwise.

> **Underpaid/overpaid still settle.** The contract's `minOut` is the swap slippage floor, *not* the checkout amount -- an underpayment is delivered and flagged off-chain rather than reverted on-chain. Handle `underpaid`/`overpaid` in your own reconciliation logic.
