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

# 訂單

如果 [CheckoutSession](https://docs.infraio.xyz/zh-TW/concepts/sessions) 是買家看到的，
**Order** 就是*你*在意的。它是以下事項的永久紀錄:

- 買的是什麼(商品項目)
- 該付多少、實際付了多少
- 待處理與已套用的退款
- 你的外部參考(`external_ref`)— 通常是你自己的訂單 ID,儲存在
  Order 上並透過 `GET /b2b/v1/orders/{id}` 回傳(不會在 webhook
  酬載中回顯 — 見下文)

訂單不一定需要明細項目：傳送 `amount` 取代 `items` 即可只收取一個金額（發票、訂金、自訂金額的付款連結）。兩者只能擇一。

CheckoutSession 在其某個 PaymentIntent 結算或 TTL 過期後就會死亡。
Order 永遠存在。

## Order 何時被建立

當你呼叫 `POST /b2b/v1/checkout-sessions/quick` 時，InfraIO Pay
會在一個 transaction 中**同時**建立一個新 Order *與*一個新的
CheckoutSession。如果你已經有 Order 且想重試結帳(例如買家放棄
之後),請改用 `POST /b2b/v1/checkout-sessions` 將新 session 附到
既有訂單 — 保留稽核軌跡。

## 生命週期

```mermaid
stateDiagram-v2
    [*] --> PENDING: order created
    PENDING --> PAID:              full payment settled
    PENDING --> PARTIAL_PAID:      underpayment settled
    PENDING --> CANCELED:          session expired or canceled
    PARTIAL_PAID --> PAID:         merchant resolves OR remainder paid
    PAID --> PARTIALLY_REFUNDED:   partial refund executed
    PAID --> REFUNDED:             full refund executed
    PARTIALLY_REFUNDED --> REFUNDED: subsequent refund covers the rest
    PAID --> [*]
    REFUNDED --> [*]
    CANCELED --> [*]
```

| 狀態 | 意義 |
| --- | --- |
| `PENDING` | 一個 CheckoutSession 仍有效且未結算。 |
| `PAID` | 全額已結算。**webhook `payment.settled` 在這裡觸發。** 可以安全履約。 |
| `PARTIAL_PAID` | 資金已到但少於總額。請看下方「短付」。 |
| `CANCELED` | Session 過期或商家取消。`metadata.canceled_reason` 說明原因(`payment_timeout`、`merchant_canceled` 等)。 |
| `REFUNDED` | 所有已付金額都已退款。 |
| `PARTIALLY_REFUNDED` | 部分退款已執行，但仍有已付餘額。 |

> **Note:**
>
> **你應該以 `PAID` 作為履約邏輯的切換點** — 而非 CheckoutSession 的
> `COMPLETED`。`payment.settled` webhook 是規範訊號。

## `external_ref` 欄位

建立 session 時可以帶 `external_ref`(任意字串，最多 255 字 — 通常
是你自己的訂單 ID)。它會貫穿整個管線:

- 儲存於 Order 上
- 在商家儀表板可見，方便 support 查詢
- 在 `GET /b2b/v1/orders/{id}` 上回傳，讓 webhook handler 在收到
  `payment.settled` 後可以查到它

> **Warning:**
>
> Webhook 酬載**不包含** `external_ref`。要把 `payment.*` webhook
> 對應回你自己的紀錄，請從 payload 中取 `order_id` 再去查詢訂單。

## 短付

若買家的鏈上轉帳結算金額少於訂單總額，Order 會進入 `PARTIAL_PAID`。
你有三個選項:

1. **接受並解析。** 把訂單翻為 `PAID` 並發出 `order.resolved`。這無法
   透過 REST API 進行，所以對於自助流程請採用選項 2(收集差額)。
2. **等待差額。** 對同一個 Order 建立新的 CheckoutSession,`amount_due`
   為剩餘額。買家補上差額;結算後 Order 移到 `PAID`。
3. **取消並退款。** 退還已付的部分並取消 Order。買家需自行負擔鏈上
   手續費。

## 退款

退款是獨立的 API 介面與獨立的概念頁面。請見[概念 → 退款](https://docs.infraio.xyz/zh-TW/concepts/refunds)。

## API endpoints

| Method | Path | 備註 |
| --- | --- | --- |
| `POST` | `/b2b/v1/orders` | 不建立 session 直接建立 Order(罕見) |
| `GET` | `/b2b/v1/orders/:order_id` | 讀取完整訂單，含商品項目 + 付款歷史 |
| `PATCH` | `/b2b/v1/orders/:order_id/cancel` | 取消未付款訂單 |
| `PATCH` | `/b2b/v1/orders/:order_id/reopen` | 重新開啟自動取消(`payment_timeout`)的訂單 |

## 下一步

- [概念 → 工作階段](https://docs.infraio.xyz/zh-TW/concepts/sessions) — 包覆 Order 的買家
  面向外殼。
- [概念 → 退款](https://docs.infraio.xyz/zh-TW/concepts/refunds) — 退款狀態與手動鏈上提交
  步驟。
- [Webhooks → 總覽](https://docs.infraio.xyz/zh-TW/webhooks/overview) — Order 生命週期間
  觸發的所有事件。
