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

# Đơn hàng

Nếu [CheckoutSession](https://docs.infraio.xyz/vi/concepts/sessions) là thứ người mua thấy,
thì **Order** mới là thứ *bạn* quan tâm. Nó là bản ghi vĩnh viễn về:

- Người mua đang mua gì (line items)
- Bao nhiêu được nợ và bao nhiêu thực tế đã trả
- Refund đang chờ và đã được áp dụng
- Reference ngoài của bạn (`external_ref`) — thường là order ID của
  chính bạn, được lưu trên Order và trả về tại `GET /b2b/v1/orders/{id}`
  (không echo trong payload webhook — xem bên dưới)

Order không bắt buộc có line items: gửi `amount` thay cho `items` để thu một số tiền cố định (hoá đơn, tiền đặt cọc, link thanh toán tuỳ ý). Gửi một trong hai, không gửi cả hai.

CheckoutSession chết sau khi một trong các PaymentIntent của nó được
settle hoặc TTL hết hạn. Order sống mãi mãi.

## Khi nào Order được tạo

Khi bạn gọi `POST /b2b/v1/checkout-sessions/quick`, InfraIO Pay tạo
**cả** Order mới *và* CheckoutSession mới trong cùng một transaction.
Nếu bạn đã có Order và muốn retry checkout (ví dụ sau khi người mua từ
bỏ), dùng `POST /b2b/v1/checkout-sessions` để gắn một session mới vào
order đã có — giữ nguyên audit trail.

## Vòng đời

```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 --> [*]
```

| Trạng thái | Ý nghĩa |
| --- | --- |
| `PENDING` | Một CheckoutSession đang chạy và chưa được giải quyết. |
| `PAID` | Toàn bộ số tiền đã được settle. **Webhook `payment.settled` được phát ở đây.** An toàn để fulfillment. |
| `PARTIAL_PAID` | Tiền đã đến nhưng ít hơn tổng. Xem "Underpayment" bên dưới. |
| `CANCELED` | Session hết hạn hoặc merchant đã hủy. `metadata.canceled_reason` giải thích lý do (`payment_timeout`, `merchant_canceled`, …). |
| `REFUNDED` | Toàn bộ số tiền đã trả đều đã được hoàn lại. |
| `PARTIALLY_REFUNDED` | Có refund đã được thực thi nhưng phần còn lại vẫn được giữ là đã trả. |

> **Note:**
>
> **Trạng thái để chuyển logic fulfillment là `PAID`** — không phải
> `COMPLETED` của CheckoutSession. Webhook `payment.settled` là tín
> hiệu canonical.

## Trường `external_ref`

Khi tạo session bạn có thể đưa kèm `external_ref` (chuỗi bất kỳ tối
đa 255 ký tự — thường là order ID của chính bạn). Nó xuyên suốt cả
pipeline:

- Lưu trên Order
- Hiển thị trong merchant dashboard cho việc tra cứu support
- Trả về tại `GET /b2b/v1/orders/{id}` để webhook handler có thể lấy
  sau khi nhận `payment.settled`

> **Warning:**
>
> Payload webhook **không** bao gồm `external_ref`. Để map một webhook
> `payment.*` về record của riêng bạn, lấy `order_id` từ payload và
> fetch order.

## Underpayment

Nếu giao dịch chuyển on-chain của người mua clear ít hơn tổng order,
Order chuyển sang `PARTIAL_PAID`. Bạn có ba lựa chọn:

1. **Chấp nhận và resolve.** Chuyển order sang `PAID` và phát
   `order.resolved`. Lựa chọn này không có sẵn qua REST API, nên với
   luồng self-serve hãy dùng lựa chọn 2 (thu phần còn thiếu).
2. **Chờ phần còn thiếu.** Tạo một CheckoutSession mới ứng với cùng
   Order với `amount_due` = residual. Người mua trả phần chênh lệch;
   khi nó settle, Order chuyển sang `PAID`.
3. **Hủy và refund.** Hoàn lại số tiền partial và hủy Order. Người mua
   chịu mọi chain fee.

## Refunds

Refund là API surface riêng và là khái niệm riêng. Xem
[Khái niệm → Hoàn tiền](https://docs.infraio.xyz/vi/concepts/refunds).

## API endpoints

| Method | Path | Ghi chú |
| --- | --- | --- |
| `POST` | `/b2b/v1/orders` | Tạo Order mà không có session (hiếm) |
| `GET` | `/b2b/v1/orders/:order_id` | Đọc order đầy đủ với line items + lịch sử thanh toán |
| `PATCH` | `/b2b/v1/orders/:order_id/cancel` | Hủy một order chưa thanh toán |
| `PATCH` | `/b2b/v1/orders/:order_id/reopen` | Mở lại một order bị auto-cancel (`payment_timeout`) |

## Tiếp theo

- [Khái niệm → Sessions](https://docs.infraio.xyz/vi/concepts/sessions) — lớp vỏ hướng tới
  người mua bao quanh một Order.
- [Khái niệm → Hoàn tiền](https://docs.infraio.xyz/vi/concepts/refunds) — trạng thái refund
  và bước submit on-chain thủ công.
- [Webhooks → Tổng quan](https://docs.infraio.xyz/vi/webhooks/overview) — mọi event được phát
  trong vòng đời của Order.
