세션
CheckoutSession은 구매자가 상호작용하는 대상입니다 — TTL이 적용된 일회용 객체이며 호스팅 체크아웃 URL을 소유합니다. 세 가지 핵심 엔티티 중 가장 가벼운 편이며, 더 무거운 역할은 Order와 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-SO92J7HNWkwMHEHjD4oO1ZURL-safe하며 프리픽스 뒤에 약 24자입니다(18개의 무작위 바이트를 base64url
인코딩한 것이며, 패딩은 없음). 호스팅 체크아웃 페이지는
https://checkout.infraio.xyz/<session_key>이며 — 세션 키는 해당 단일 체크아웃에
대한 베어러 자격 증명으로 취급해야 합니다.
라이프사이클 — CheckoutSession
| 상태 | 의미 |
|---|---|
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을 명시적으로 전송하세요. - 적용 방식: 읽기 시 lazy 평가 + 주기적인 클린업 워커.
expires_at이 지난 세션은 상태 필드가 아직 업데이트되지 않았더라도EXPIRED로 취급되므로 만료 시점에 API로 상태를 읽는 것에 의존하지 마세요.
과소 결제
구매자가 세션 금액보다 적게 송금하면, PaymentIntent는 부분 금액에 대해
여전히 정산되고 Order는 PARTIAL_PAID로 전이합니다. CheckoutSession은
COMPLETED로 이동하므로(PaymentIntent 하나가 정산됨) 더 이상 재사용할 수
없습니다.
부족분을 전체 결제로 수용하려면 주문을 PAID로 해결할 수 있습니다 —
개념 → Order 참조(현재 공개 resolve API는 없습니다. 셀프
서비스 흐름에는 부족분을 대신 수집하는 방식이 권장됩니다). 부족분을
수집하려면 동일한 Order에 대해 잔여 금액으로 새 CheckoutSession을
생성하세요.
초과 결제
구매자가 세션 금액보다 많이 송금하는 경우(수동 송금에서 드물게 발생), 온체인 스캐너가 24시간 이내에 초과 결제를 감지하고 가맹점 알림을 발생시킵니다. 자동 환불은 없습니다 — 환불 API 또는 대시보드를 통해 수동으로 발행하세요.
잘못된 자산 결제
입금 주소는 (session, chain, asset) 튜플별로 생성됩니다. 구매자가 잘못된
자산을 해당 주소로 송금하면 온체인 매처가 이를 인식하지 못하고 PaymentIntent는
TTL 만료까지 열린 상태로 유지됩니다. 자금을 복구할 수는 있지만 이는 지원
플로우이며 자동이 아닙니다 — 구매자에게 체크아웃 페이지에 표시된 정확한 자산을
송금하도록 안내하세요.
생성 시 멱등성
POST /b2b/v1/checkout-sessions/quick는 요청 본문에 idempotency_key 필드를
허용합니다(주의: HTTP 헤더가 아닌 본문 필드). 클라이언트에 자연스러운
키가 없는 경우 UUID를 자동 생성하세요 — SDK는 기본적으로 이를 수행합니다.
이 키는 CheckoutSession이 아닌 Order에 대해 중복을 제거합니다. 동일한
키로 재시도하면 /quick은 원래 주문(order_id는 안정적)을 반환하지만
새로운 CheckoutSession을 발급합니다 — 매번 새 session_key와
checkout_url이 생성됩니다. 이는 의도된 동작입니다. 하나의 Order가 여러
체크아웃 시도를 지원할 수 있으므로(Order 참조), 재시도된
/quick은 주문을 중복 생성하지 않고 구매자에게 깨끗한 세션을 제공합니다.
Stripe 스타일의 멱등성 레이어와 두 가지 동작이 다르므로 주의가 필요합니다.
- 본문은 해시되거나 비교되지 않습니다. 동일한 키를 다른 본문과 함께
재사용해도
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 | 한 주문의 모든 세션 목록(재시도 이력) |
GET | /checkout/:session_key | 공개 — 구매자 브라우저가 호출 |
전체 생성 요청 본문과 서명은 빠른 시작을 참조하세요.
다음 단계
- 개념 → Order — Order 엔티티(이행의 그라운드 트루스로 다루는 엔티티)를 다룹니다.
- 개념 → 체인 및 자산 — 지원되는 네트워크와 체인별 파이널리티 가정을 다룹니다.
- 웹훅 → 개요 — 각 세션 상태 전이에서 발생하는 이벤트를 다룹니다.