<!-- Source: https://docs.infraio.xyz/zh-CN/concepts/sessions -->
<!-- Last updated: 2026-10-04 -->

# 会话

**CheckoutSession** 是买家交互的对象 — 有时限、一次性,拥有托管收银台
URL。它是三大核心实体里最轻的一个。你的大部分逻辑与 [Orders](https://docs.infraio.xyz/zh-CN/concepts/orders)
和 **Payment Intents**(见下文)打交道。

## 三实体数据模型

```
CheckoutSession  ←  1:1  →  Order  ←  1:N  →  PaymentIntent
   (buyer)                  (catalog)         (each pay attempt)
```

| 实体 | 用途 | 生命周期 |
| --- | --- | --- |
| **CheckoutSession** | 买家向外 — 有 `session_key`、`checkout_url`、TTL | 数分钟(默认 30) |
| **Order** | 你的目录状态 — 明细项、总额、退款 | 永久记录 |
| **PaymentIntent** | 一条链/资产上的一次支付尝试 | 数小时;结算或过期 |

你通过 `POST /b2b/v1/checkout-sessions/quick` 同时创建一个会话和
一个订单。每次买家在结账页上挑一种资产时,都会针对对应的链打开一个
全新的 PaymentIntent。如果他们中途切换资产,之前的 intent 进入
`EXPIRED`,新 intent 开始。

## 标识符

会话由其 `session_key` 标识:

```
cst_G-SO92J7HNWkwMHEHjD4oO1Z
```

URL 安全,前缀之后约 24 个字符。
托管收银台是 `https://checkout.infraio.xyz/<session_key>` — 把 session_key
当作那一次结账的 bearer 凭据对待。

## 生命周期 — CheckoutSession

```mermaid
stateDiagram-v2
    [*] --> ACTIVE
    ACTIVE --> COMPLETED: a PaymentIntent settles
    ACTIVE --> EXPIRED:   expires_at elapsed
    ACTIVE --> CANCELED:  buyer or merchant cancels
    COMPLETED --> [*]
    EXPIRED --> [*]
    CANCELED --> [*]
```

| 状态 | 含义 |
| --- | --- |
| `ACTIVE` | 会话已创建并开放。结账 URL 可用。 |
| `COMPLETED` | 该会话上的某条 PaymentIntent 已结算。Order 现在是 `PAID`(欠付则 `PARTIAL_PAID`)。 |
| `EXPIRED` | `expires_at` 已过且未结算。任何打开的 Order 都会被取消。 |
| `CANCELED` | 显式取消 — 要么买家点了"取消",要么你调用了取消端点。 |

三个终态互斥且终局。如果你想重试(例如欠付后),可以对同一 Order
创建一个新的 CheckoutSession。

## TTL

- **默认:** 30 分钟(可通过 create 上的 `expires_in` 字段配置,单位秒)。
- **边界:** 不强制最小或最大值。请选择匹配你买家决策窗口的值:
  低于 60 秒可能让合法买家超时,而保持打开超过 7 天的会话几乎
  肯定已被放弃。
- **每商户默认:** 可在仪表板中设置,但只有
  `POST /b2b/v1/checkout-sessions`(两步)会遵守它。
  `POST /b2b/v1/checkout-sessions/quick` 在省略 `expires_in` 时使用
  **30 分钟**,不论仪表板如何设置。如果要在 `/quick` 上使用不同的
  默认值,请在每次调用时传 `expires_in`。
- **执行:** 一个 `expires_at` 已过的会话即便状态尚未更新,也会被视为
  `EXPIRED`,所以不要在到期的精确时刻依赖状态值。

## 欠付

如果买家发送的金额少于会话金额,PaymentIntent 仍然按部分金额结算,
Order 转为 `PARTIAL_PAID`。CheckoutSession 进入 `COMPLETED`
(一条 PaymentIntent 已结算),所以它不再可重用。

要把缺额视为全额支付,可以把订单解决为 `PAID`。见
[概念 → 订单](https://docs.infraio.xyz/zh-CN/concepts/orders)(无法通过 API 解决;
要保持自助,请改为收齐剩款)。要收齐剩款,对同一 Order 用剩余金额
创建**新**的 CheckoutSession。

## 超付

如果买家发送的金额超过会话金额(罕见,但人工转账时会发生),InfraIO Pay
会在 24 小时内检测到超付并通知你。它不会自动退款,请通过
退款 API 或仪表板发起退款。

## 错资产支付

收款地址是为某一个会话、网络和资产生成的。如果买家向它发送其他资产,
该付款不会被匹配,PaymentIntent 会一直保持打开,直到会话过期。
支持团队可以协助找回资金,但这不是自动的。请告诉买家发送结账页面
显示的精确资产。

TRON、Solana 和 TON 没有独立收款地址,因此这仅适用于 EVM 网络:买家直接向你的资金库钱包付款。见[直达钱包的网络](https://docs.infraio.xyz/zh-CN/concepts/chains#直达钱包的网络)。

## create 上的幂等

`POST /b2b/v1/checkout-sessions/quick` 接受请求 body 中的 `idempotency_key`
字段(注意:**body 字段,不是 HTTP 标头**)。
如果你没有天然 key,请生成一个 UUID。

这个 key 去重的是 **Order**,而不是 CheckoutSession。用同一个 key 重试时,
`/quick` 返回 *原始 order*(`order_id` 稳定),但每次都铸造一个**全新的
CheckoutSession** — 每次都是新的 `session_key` 和 `checkout_url`。这是
有意为之:一个 Order 可以背靠多次结账尝试(见
[订单](https://docs.infraio.xyz/zh-CN/concepts/orders)),所以重试的 `/quick` 会在不重复 order
的前提下给买家一个干净的会话。

有两点行为需要注意:

- **body 不会被哈希或比较。** 用 *不同* 的 body 复用一个 key **不会**
  返回 `409` — 服务端会静默返回该 key 名下已存的 order,并忽略新的
  body。所以请把 `idempotency_key` 当作单个逻辑 order 的一次性令牌;
  绝不要跨不同购物车回收使用。
- **只有 Order 被去重,会话不会。** 如果你需要拿回 *同一个* 结账 URL,
  请把第一次响应里的 `session_key` / `checkout_url` 持久化下来 — 再次
  调用 `/quick` 不会返回旧的。要枚举某个 order 名下铸造过的每一个会话,
  用 `GET /b2b/v1/checkout-sessions/by-order/:order_id`。

## API 端点

| 方法 | 路径 | 备注 |
| --- | --- | --- |
| `POST` | `/b2b/v1/checkout-sessions/quick` | 一次调用创建订单 + 会话 |
| `POST` | `/b2b/v1/checkout-sessions` | 针对已存在订单创建会话 |
| `GET` | `/b2b/v1/checkout-sessions/by-order/:order_id` | 列出某订单的所有会话(用于重试历史) |

完整 create 请求 body 与签名见 [快速入门](https://docs.infraio.xyz/zh-CN/get-started/quickstart)。

## 下一步

- [概念 → 订单](https://docs.infraio.xyz/zh-CN/concepts/orders) — 作为履约真相之源的
  Order 实体。
- [概念 → 链与资产](https://docs.infraio.xyz/zh-CN/concepts/chains) — 受支持的网络以及
  每条链的终局性假设。
- [Webhooks → 概览](https://docs.infraio.xyz/zh-CN/webhooks/overview) — 每个会话状态迁移
  时哪个事件触发。
