Skip to Content
КонцепцииСессии

Сессии

CheckoutSession — это то, с чем взаимодействует покупатель — объект с TTL и одноразовостью, владеющий URL размещённого checkout. Самая тонкая из трёх ключевых сущностей; основная работа происходит в Заказах и Payment Intent’ах (см. ниже).

Модель данных из трёх сущностей

CheckoutSession ← 1:1 → Order ← 1:N → PaymentIntent (покупатель) (каталог) (каждая попытка оплаты)
СущностьНазначениеВремя жизни
CheckoutSessionОбращённая к покупателю — есть session_key, checkout_url, TTLМинуты (по умолчанию 30)
OrderСостояние вашего каталога — позиции, суммы, возвратыПостоянная запись
PaymentIntentОдна попытка оплаты на одной сети/активеЧасы; рассчитывается или истекает

Вы создаёте сессию и заказ вместе (через POST /b2b/v1/checkout-sessions/quick). Каждый раз, когда покупатель выбирает актив на странице checkout, открывается свежий PaymentIntent для соответствующей сети. Если он переключает активы посреди checkout, предыдущий intent переходит в EXPIRED, и стартует новый.

Идентификатор

Сессия идентифицируется своим session_key:

cst_G-SO92J7HNWkwMHEHjD4oO1Z

URL-безопасный, ~24 символа после префикса (18 случайных байт, закодированных base64url без padding’а). Размещённая страница checkout — это https://checkout.infraio.xyz/<session_key> — рассматривайте session_key как bearer-credential для именно этого checkout.

Жизненный цикл — CheckoutSession

СостояниеЗначит
ACTIVEСессия создана и открыта. URL checkout работает.
COMPLETEDPaymentIntent на этой сессии рассчитан. Order теперь PAID (или PARTIAL_PAID при недоплате).
EXPIREDexpires_at прошло без расчёта. Cleanup-worker перевёл состояние и отменил все открытые Order’ы.
CANCELEDЯвная отмена — либо покупатель нажал «cancel», либо вы вызвали endpoint отмены.

Три терминальных состояния взаимоисключающие и финальные. Новая CheckoutSession против того же Order может быть создана, если вы хотите повторить (например, после недоплаты).

TTL

  • По умолчанию: 30 минут (настраивается через поле expires_in при создании, в секундах).
  • Границы: На сервере жёсткого min/max не закреплено. Используйте разумные значения — менее 60 секунд рискует выйти за тайминги для легитимных покупателей; более 7 дней удерживает ёмкость на токене, который почти наверняка заброшен. Выбирайте число, соответствующее ожидаемому окну принятия решения покупателем.
  • Дефолт per-merchant: Настраивается через панель, но соблюдается только в legacy-пути POST /b2b/v1/checkout-sessions (двухшаговый). Путь POST /b2b/v1/checkout-sessions/quick всегда откатывается на 30 минут, если expires_in опущен, независимо от настройки per-merchant. Если на быстром пути нужен другой дефолт, отправляйте expires_in явно при каждом вызове.
  • Проверка: Ленивая на чтении + периодический cleanup-worker. Сессия, у которой expires_at прошло, трактуется как EXPIRED, даже если поле состояния ещё не записано, поэтому не полагайтесь на чтение состояния через API в точный момент истечения.

Недоплата

Если покупатель отправляет меньше суммы сессии, PaymentIntent всё равно рассчитывается на частичную сумму, а Order переходит в PARTIAL_PAID. CheckoutSession переходит в COMPLETED (один PaymentIntent рассчитан), поэтому она больше не пригодна к повторному использованию.

Чтобы принять недоплату как полную оплату, заказ можно разрешить в PAID — см. Концепции → Заказы (публичного resolve API сегодня нет; для self-serve потока вместо этого собирайте остаток). Чтобы собрать остаток, создайте новую CheckoutSession против того же Order на остаточную сумму.

Переплата

Если покупатель отправляет больше суммы сессии (редко, но случается при ручных переводах), on-chain сканер фиксирует переплату в течение 24 часов и отправляет оповещение мерчанту. Автоматического возврата нет — выпустите его вручную через API возвратов или панель.

Платежи неверным активом

Депозитный адрес генерируется per кортежу (session, chain, asset). Если покупатель отправит неверный актив на адрес, on-chain matcher не распознает его, и PaymentIntent останется открытым до истечения TTL. Мы можем восстановить средства, но это поток через поддержку, не автоматика — инструктируйте покупателей отправлять ровно тот актив, который показан на странице checkout.

Идемпотентность при создании

POST /b2b/v1/checkout-sessions/quick принимает поле idempotency_key в теле запроса (заметьте: поле тела, а не HTTP-заголовок). Сгенерируйте для него UUID автоматически, если у вашего клиента нет естественного ключа — SDK делает это по умолчанию.

Ключ дедуплицирует Order, а не CheckoutSession. При повторе с тем же ключом /quick возвращает оригинальный заказ (order_id стабилен), но создаёт свежую CheckoutSession — новый session_key и checkout_url каждый раз. Это намеренно: один Order может стоять за несколькими попытками checkout (см. Заказы), поэтому повторный /quick отдаёт покупателю чистую сессию без дублирования заказа.

Два поведения отличаются от Stripe-стиля idempotency — не попадитесь:

  • Тело не хэшируется и не сравнивается. Повторное использование ключа с другим телом не возвращает 409 — сервер молча возвращает заказ, уже сохранённый под этим ключом, и игнорирует новое тело. Поэтому рассматривайте idempotency_key как одноразовый токен для одного логического заказа; никогда не переиспользуйте его между разными корзинами.
  • Дедуплицируется только Order, не сессия. Если вам нужен тот же URL checkout, сохраните session_key / checkout_url из первого ответа — повторный вызов /quick не вернёт старый. Чтобы перечислить каждую сессию, выпущенную против заказа, используйте GET /b2b/v1/checkout-sessions/by-order/:order_id.

Endpoint’ы 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Публичный — то, что вызывает браузер покупателя

См. Быстрый старт для полного тела запроса создания и подписания.

Что дальше