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

# 세션

**CheckoutSession**은 구매자가 상호작용하는 대상입니다 — 시간 제한이 있는 일회용
객체이며 호스팅 결제 페이지 URL을 소유합니다. 세 가지 핵심 엔티티 중 가장 가벼운
편이며, 대부분의 로직은 [Order](https://docs.infraio.xyz/ko/concepts/orders)와 **PaymentIntent**(아래 참조)가
담당합니다.

## 세 엔티티 데이터 모델

```
CheckoutSession  ←  1:1  →  Order  ←  1:N  →  PaymentIntent
   (구매자 영역)            (카탈로그)         (결제 시도별)
```

| 엔티티 | 용도 | 수명 |
| --- | --- | --- |
| **CheckoutSession** | 구매자 영역 — `session_key`, `checkout_url`, TTL 보유 | 분 단위(기본값 30) |
| **Order** | 가맹점의 카탈로그 상태 — 라인 아이템, 합계, 환불 | 영구 기록 |
| **PaymentIntent** | 하나의 체인/자산에서 한 번의 결제 시도 | 시간 단위. 정산되거나 만료 |

세션과 주문은 함께 생성됩니다(`POST /b2b/v1/checkout-sessions/quick` 사용).
구매자가 체크아웃 페이지에서 자산을 선택할 때마다 해당 체인에 대해 새로운
PaymentIntent가 열립니다. 체크아웃 진행 중에 자산을 변경하면 이전 인텐트는
`EXPIRED`로 이동하고 새 인텐트가 시작됩니다.

## 식별자

세션은 `session_key`로 식별됩니다.

```
cst_G-SO92J7HNWkwMHEHjD4oO1Z
```

URL-safe하며 프리픽스 뒤에 약 24자입니다. 호스팅 결제 페이지 페이지는
`https://checkout.infraio.xyz/<session_key>`이며 — 세션 키는 해당 단일 체크아웃에
대한 베어러 자격 증명으로 취급해야 합니다.

## 라이프사이클 — 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분(생성 시 `expires_in` 필드로 초 단위 설정 가능).
- **범위:** 강제되는 최소/최대값은 없습니다. 구매자의 예상 결정 시간에 맞는
  값을 선택하세요. 60초 미만은 정상적인 구매자도 시간 초과될 위험이 있고,
  7일 넘게 열려 있는 세션은 거의 확실히 포기된 것입니다.
- **가맹점별 기본값:** 대시보드에서 설정할 수 있지만,
  `POST /b2b/v1/checkout-sessions`(2단계)만 이를 따릅니다.
  `POST /b2b/v1/checkout-sessions/quick`은 `expires_in`이 생략된 경우
  대시보드 설정에 관계없이 **30분**을 사용합니다. `/quick`에서
  다른 기본값을 사용하려면 매번 `expires_in`을 전송하세요.
- **적용 방식:** `expires_at`이 지난 세션은 상태가 아직 업데이트되지 않았더라도
  `EXPIRED`로 취급되므로 만료 시점의 상태 값에 의존하지 마세요.

## 과소 결제

구매자가 세션 금액보다 적게 송금하면, PaymentIntent는 부분 금액에 대해
여전히 정산되고 Order는 `PARTIAL_PAID`로 전이합니다. CheckoutSession은
`COMPLETED`로 이동하므로(PaymentIntent 하나가 정산됨) 더 이상 재사용할 수
없습니다.

부족분을 전체 결제로 수용하려면 주문을 `PAID`로 해결할 수 있습니다 —
[개념 → Order](https://docs.infraio.xyz/ko/concepts/orders) 참조(resolve는 API로 제공되지 않으므로
셀프 서비스를 유지하려면 부족분을 대신 수집하세요). 부족분을
수집하려면 동일한 Order에 대해 잔여 금액으로 **새** CheckoutSession을
생성하세요.

## 초과 결제

구매자가 세션 금액보다 많이 송금하는 경우(수동 송금에서 드물게 발생),
InfraIO Pay가 24시간 이내에 초과 결제를 감지하고 알림을 보냅니다. 자동으로
환불되지 않습니다. 환불 API 또는 대시보드를 통해 환불을 발행하세요.

## 잘못된 자산 결제

입금 주소는 하나의 세션, 네트워크, 자산에 대해 생성됩니다. 구매자가 다른
자산을 해당 주소로 송금하면 결제가 매칭되지 않고 PaymentIntent는 세션이 만료될
때까지 열린 상태로 유지됩니다. 지원팀이 자금 복구를 도울 수 있지만 자동이
아닙니다. 구매자에게 체크아웃 페이지에 표시된 정확한 자산을 송금하도록
안내하세요.

TRON, Solana, TON에는 주문별 입금 주소가 없으므로 이 내용은 EVM 네트워크에만 해당합니다. 구매자가 가맹점의 지갑으로 직접 결제합니다. [지갑 직접 결제 네트워크](https://docs.infraio.xyz/ko/concepts/chains#지갑-직접-결제-네트워크)를 참조하세요.

## 생성 시 멱등성

`POST /b2b/v1/checkout-sessions/quick`는 요청 본문에 `idempotency_key` 필드를
허용합니다(주의: **HTTP 헤더가 아닌 본문 필드**). 자연스러운 키가 없다면 UUID를 생성하세요.

이 키는 CheckoutSession이 아닌 **Order**에 대해 중복을 제거합니다. 동일한
키로 재시도하면 `/quick`은 *원래 주문*(`order_id`는 안정적)을 반환하지만
**새로운 CheckoutSession**을 발급합니다 — 매번 새 `session_key`와
`checkout_url`이 생성됩니다. 이는 의도된 동작입니다. 하나의 Order가 여러
체크아웃 시도를 지원할 수 있으므로([Order](https://docs.infraio.xyz/ko/concepts/orders) 참조), 재시도된
`/quick`은 주문을 중복 생성하지 않고 구매자에게 깨끗한 세션을 제공합니다.

알아 두어야 할 두 가지 동작이 있습니다.

- **본문은 해시되거나 비교되지 않습니다.** 동일한 키를 *다른* 본문과 함께
  재사용해도 `409`를 반환하지 **않습니다** — 서버는 해당 키에 저장된 주문을
  조용히 반환하고 새 본문을 무시합니다. 따라서 `idempotency_key`는 단일
  논리적 주문에 대한 일회성 토큰으로 다루고, 서로 다른 장바구니에 절대
  재사용하지 마세요.
- **Order만 중복 제거되며, 세션은 그렇지 않습니다.** *동일한* 체크아웃
  URL을 다시 받으려면 첫 응답의 `session_key` / `checkout_url`을 영구
  저장하세요 — `/quick`을 다시 호출해도 이전 URL이 반환되지 않습니다.
  하나의 주문에 대해 발급된 모든 세션을 열거하려면
  `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` | 한 주문의 모든 세션 목록(재시도 이력) |

전체 생성 요청 본문과 서명은 [빠른 시작](https://docs.infraio.xyz/ko/get-started/quickstart)을 참조하세요.

## 다음 단계

- [개념 → Order](https://docs.infraio.xyz/ko/concepts/orders) — Order 엔티티(이행의 그라운드
  트루스로 다루는 엔티티)를 다룹니다.
- [개념 → 체인 및 자산](https://docs.infraio.xyz/ko/concepts/chains) — 지원되는 네트워크와 체인별
  파이널리티 가정을 다룹니다.
- [웹훅 → 개요](https://docs.infraio.xyz/ko/webhooks/overview) — 각 세션 상태 전이에서 발생하는
  이벤트를 다룹니다.
