订单
如果说 CheckoutSession 是买家看到的东西, 那么 Order 就是你关心的东西。它是以下事实的永久记录:
- 买了什么(明细项)
- 应付多少、实际收到多少
- 待处理与已执行的退款
- 你的外部引用(
external_ref)— 通常是你自己的订单 ID,存储在 Order 上并通过GET /b2b/v1/orders/{id}返回(不会在 Webhook 负载中回显 — 见下文)
CheckoutSession 在其某个 PaymentIntent 结算或 TTL 过期后就死掉了。 Order 永远存在。
何时创建 Order
当你调用 POST /b2b/v1/checkout-sessions/quick 时,payment-service
在一次事务里同时创建一个新 Order 和一个新 CheckoutSession。
如果你已经有 Order 并想重试结账(例如买家放弃后),改用
POST /b2b/v1/checkout-sessions 在现有订单上挂一个新会话 — 保留
审计轨迹。
生命周期
| 状态 | 含义 |
|---|---|
DRAFT | 保留给未来的草稿流程。目前没有任何代码路径会创建 DRAFT 订单 — 每个订单创建时都是 PENDING,所以你不会观察到这个状态。 |
PENDING | 一个 CheckoutSession 在运行且未决出。 |
PAID | 全额结算。Webhook payment.settled 在此触发。 可以放心履约。 |
PARTIAL_PAID | 资金到达但少于总额。见下面”欠付”。 |
CANCELED | 会话过期或商户取消。metadata.canceled_reason 解释原因(payment_timeout、merchant_canceled、……)。 |
REFUNDED | 所有已付款金额都已退款。 |
PARTIALLY_REFUNDED | 部分退款已执行但仍有余额保持已付。 |
你履约逻辑要切换的状态是 PAID — 不是 CheckoutSession 的
COMPLETED。payment.settled Webhook 才是规范信号。
external_ref 字段
创建会话时你可以包含 external_ref(任意字符串,最长 255 字符 —
通常是你自己的订单 ID)。它穿过整个管道:
- 存储在 Order 上
- 在商户仪表板可见,便于支持查询
- 在
GET /b2b/v1/orders/{id}返回,这样 Webhook 处理函数收到payment.settled后可以读取
Webhook 负载今天不直接回显 external_ref — 本文档早期草稿
曾声称 data.external_ref 流经每个事件,那是错的。要把一个
payment.* Webhook 映射回你 DB 中的行,请从负载里取 order_id,
然后反查订单。Webhook 负载上原生的 external_ref 字段在路线图上。
欠付
如果买家的链上转账清算金额少于订单总额,Order 进入 PARTIAL_PAID。
你有三种选择:
- 接受并解决。 把订单翻为
PAID,发出order.resolved。这不 是公共 REST 端点 — 解决今天是内部 / 运维动作,所以自助流程更倾向 选项 2(收齐剩款)。 - 等待剩款。 用
amount_due= 剩余值,对同一个 Order 创建一个 新的 CheckoutSession。买家付差额;结算后,Order 转为PAID。 - 取消并退款。 退还部分金额并取消 Order。任何链上手续费由买家承担。
产品对此没有给出推荐 — 不同商户想要不同策略。挑一个并固化进你的 管理工具。
退款
退款是独立的 API 表面和独立的概念页。 见 概念 → 退款。
API 端点
| 方法 | 路径 | 备注 |
|---|---|---|
POST | /b2b/v1/orders | 不带会话创建 Order(罕见) |
GET | /b2b/v1/orders/:order_id | 读取含明细项 + 付款历史的完整订单 |
PATCH | /b2b/v1/orders/:order_id/cancel | 取消未支付订单 |
PATCH | /b2b/v1/orders/:order_id/reopen | 重新打开一个自动取消(payment_timeout)的订单 |
下一步
- 概念 → 会话 — 包装 Order 的买家向外壳。
- 概念 → 退款 — 退款状态与手动链上提交步骤。
- Webhooks → 概览 — Order 生命周期内 发出的所有事件。