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

# Sessions

**CheckoutSession** là thứ người mua tương tác: một object có giới hạn
thời gian, dùng một lần, sở hữu URL trang thanh toán dựng sẵn. Đây là
entity nhẹ nhất trong ba entity cốt lõi. Phần lớn logic của bạn làm
việc với [Orders](https://docs.infraio.xyz/vi/concepts/orders)
và **Payment Intents** (xem bên dưới).

## Mô hình dữ liệu ba entity

```
CheckoutSession  ←  1:1  →  Order  ←  1:N  →  PaymentIntent
   (người mua)              (catalog)         (mỗi lần thử trả)
```

| Entity | Mục đích | Vòng đời |
| --- | --- | --- |
| **CheckoutSession** | Hướng tới người mua — có `session_key`, `checkout_url`, TTL | Phút (mặc định 30) |
| **Order** | Trạng thái catalog của bạn — line items, tổng, refund | Bản ghi vĩnh viễn |
| **PaymentIntent** | Một lần thử trả trên một chain/asset | Giờ; được settle hoặc hết hạn |

Bạn tạo session và order cùng nhau (qua `POST /b2b/v1/checkout-sessions/quick`).
Mỗi khi người mua chọn một tài sản trên trang checkout, một PaymentIntent
mới được mở ứng với chain tương ứng. Nếu họ đổi tài sản giữa chừng,
intent trước đó chuyển sang `EXPIRED` và một intent mới bắt đầu.

## Định danh

Một session được nhận diện qua `session_key`:

```
cst_G-SO92J7HNWkwMHEHjD4oO1Z
```

URL-safe, khoảng 24 ký tự sau prefix. Trang thanh toán dựng sẵn là
`https://checkout.infraio.xyz/<session_key>` — coi session key là một
bearer credential cho đúng phiên checkout đó.

## Vòng đời — CheckoutSession

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

| Trạng thái | Ý nghĩa |
| --- | --- |
| `ACTIVE` | Session đã được tạo và mở. URL checkout có thể dùng. |
| `COMPLETED` | Một PaymentIntent trên session này đã được settle. Order hiện là `PAID` (hoặc `PARTIAL_PAID` nếu thiếu). |
| `EXPIRED` | `expires_at` đã qua mà chưa settle. Mọi Order đang mở đều bị hủy. |
| `CANCELED` | Hủy tường minh — hoặc người mua bấm "cancel" hoặc bạn gọi endpoint cancel. |

Ba trạng thái cuối loại trừ lẫn nhau và là final. Có thể tạo
CheckoutSession mới ứng với cùng Order nếu bạn muốn retry (ví dụ sau
underpayment).

## TTL

- **Mặc định:** 30 phút (cấu hình qua trường `expires_in` khi tạo, đơn
  vị giây).
- **Bounds:** Không có ngưỡng tối thiểu hay tối đa nào được enforce.
  Hãy chọn giá trị khớp với khoảng thời gian quyết định kỳ vọng của
  người mua: dưới 60 giây có thể khiến người mua hợp lệ bị timeout, và
  một session mở quá 7 ngày gần như chắc chắn đã bị bỏ.
- **Mặc định theo merchant:** Bạn có thể đặt default trong dashboard,
  nhưng chỉ `POST /b2b/v1/checkout-sessions` (hai bước) tôn trọng nó.
  `POST /b2b/v1/checkout-sessions/quick` dùng **30 phút** khi
  `expires_in` bị bỏ trống, bất kể cài đặt trong dashboard. Để dùng
  default khác với `/quick`, gửi `expires_in` trên mỗi lệnh gọi.
- **Áp dụng:** Một session có `expires_at` đã qua sẽ được coi như
  `EXPIRED` ngay cả khi state của nó chưa được cập nhật, nên đừng dựa
  vào giá trị state ngay tại thời điểm hết hạn.

## Underpayment

Nếu người mua gửi ít hơn số tiền của session, PaymentIntent vẫn được
settle với số tiền nhận được và Order chuyển sang `PARTIAL_PAID`.
CheckoutSession chuyển sang `COMPLETED` (một PaymentIntent đã settle)
nên không còn tái sử dụng được nữa.

Để chấp nhận phần thiếu như là thanh toán đủ, order có thể được resolve
sang `PAID`. Xem [Khái niệm → Orders](https://docs.infraio.xyz/vi/concepts/orders) (resolve không
có sẵn qua API; để giữ self-serve, hãy thu phần còn lại thay vì resolve).
Để thu phần còn lại, tạo **một** CheckoutSession
**mới** ứng với cùng Order với số tiền residual.

## Overpayment

Nếu người mua gửi nhiều hơn số tiền của session (hiếm, nhưng xảy ra với
chuyển khoản thủ công), InfraIO Pay sẽ phát hiện phần dư trong
vòng 24 giờ và thông báo cho bạn. Phần dư không được refund tự động.
Hãy refund qua refund API hoặc dashboard.

## Thanh toán sai tài sản

Địa chỉ nhận tiền riêng cho từng đơn được sinh cho một session, network và asset.
Nếu người mua gửi một tài sản khác đến địa chỉ đó, khoản thanh toán không
được khớp và PaymentIntent giữ trạng thái mở cho đến khi session hết hạn.
Support có thể giúp recover số tiền, nhưng không tự động. Hãy nói người
mua gửi đúng tài sản hiển thị trên trang checkout.

Trên TRON, Solana và TON không có địa chỉ nhận tiền riêng cho từng đơn, nên điều này chỉ áp dụng cho các network EVM: người mua trả thẳng vào ví của bạn. Xem [Network thanh toán thẳng vào ví](https://docs.infraio.xyz/vi/concepts/chains#network-thanh-toán-thẳng-vào-ví).

## Idempotency khi tạo

`POST /b2b/v1/checkout-sessions/quick` nhận trường `idempotency_key`
trong body của request (chú ý: **trường body, không phải HTTP header**).
Sinh một UUID nếu bạn không có key tự nhiên.

Key dedup cho **Order**, không phải CheckoutSession. Khi retry cùng
một key, `/quick` trả về *order ban đầu* (`order_id` ổn định) nhưng
phát hành một CheckoutSession **mới** — `session_key` và `checkout_url`
mới mỗi lần. Đó là chủ đích: một Order có thể đứng sau nhiều lần thử
checkout (xem [Orders](https://docs.infraio.xyz/vi/concepts/orders)), nên một `/quick` được
retry sẽ giao cho người mua một session sạch mà không nhân đôi order.

Hai hành vi cần lưu ý:

- **Body không được hash hay so sánh.** Tái sử dụng một key với body
  *khác* **không** trả về `409` — server lặng lẽ trả về order đã lưu
  với key đó và bỏ qua body mới. Vậy nên hãy coi `idempotency_key` là
  token dùng một lần cho một order logic duy nhất; đừng tái sử dụng
  qua các giỏ hàng khác nhau.
- **Chỉ Order được dedup, không phải session.** Nếu bạn cần lấy lại
  *cùng* checkout URL, hãy lưu `session_key` / `checkout_url` từ
  response đầu tiên — gọi `/quick` lại sẽ không trả về URL cũ. Để
  liệt kê mọi session đã được tạo cho một order, dùng
  `GET /b2b/v1/checkout-sessions/by-order/:order_id`.

## API endpoints

| Method | Path | Ghi chú |
| --- | --- | --- |
| `POST` | `/b2b/v1/checkout-sessions/quick` | Tạo order + session trong một lệnh |
| `POST` | `/b2b/v1/checkout-sessions` | Tạo session ứng với order đã có |
| `GET` | `/b2b/v1/checkout-sessions/by-order/:order_id` | Liệt kê mọi session của một order (lịch sử retry) |

Xem [Bắt đầu nhanh](https://docs.infraio.xyz/vi/get-started/quickstart) để biết body request
tạo đầy đủ và cách ký.

## Tiếp theo

- [Khái niệm → Orders](https://docs.infraio.xyz/vi/concepts/orders) — entity Order (cái bạn
  coi là nguồn xác thực cho fulfillment).
- [Khái niệm → Chains & tài sản](https://docs.infraio.xyz/vi/concepts/chains) — network được
  hỗ trợ và giả định finality theo từng chain.
- [Webhooks → Tổng quan](https://docs.infraio.xyz/vi/webhooks/overview) — event nào được phát
  ở mỗi transition trạng thái session.
