Единый чекаут

Draft → finalize flow, настраиваемые поля формы, доставка с расчётом на сервере, cash-on-delivery, embedded YooKassa, идемпотентность.

Единый чекаут

Чекаут в Framix — общий путь оплаты для всех каналов: товар можно купить с сайта, через AI-агента на сайте, через Telegram/VK. Адрес чекаут-страницы один: /checkout/[token]. На сайте используется та же модалка AppCheckoutModal.

Draft → finalize

Чтобы один поток работал и для сайта, и для агента (пользователь не заполняет адрес дважды), чекаут построен из двух шагов:

  1. Draft. Агент или сайт создают черновик заказа: позиции, сумма, привязка к группе/проекту/агенту/сессии. Никаких вызовов провайдера. Сток не блокируется. Клиент получает токен на /checkout/<token>.
  2. Finalize. Клиент на чекауте выбирает способ оплаты (Картой онлайн / При получении), доставку, заполняет контакты — и финализирует одним кликом. В этот момент:
    • сток лочится (SELECT FOR UPDATE по позициям);
    • тиры цен и скидки от суммы пересчитываются сервером (клиентским данным не доверяем);
    • создаётся payment в провайдере (для card-online);
    • возвращается confirmation_token (для embedded YooKassa) или confirmation_url (redirect).

Финализация идемпотентна: гонка вкладок, double-submit, retry-после-ошибки-сети вернут ту же платёжную ссылку. Никаких дублей.

Настраиваемые поля формы

/account/checkout-fields — единая конфигурация для всей группы. Применяется и к товарным заказам, и к оплачиваемым броням услуг.

Встроенные поля:

  • Email — обязателен всегда (54-ФЗ: нужен для чека). Убрать нельзя.
  • Имя и Телефон — можно скрыть или сделать обязательными.

Кастомные поля (до 20 штук):

  • Типы: text (однострочный), textarea (многострочный), select (список).
  • Готовые пресеты: Город, ИНН, Промокод, Адрес доставки и другие.
  • Порядок drag-and-drop.

Все значения сохраняются снапшотом в commerce_order.customer_fields / booking.customer_fields и отображаются владельцу в drawer'е заказа и карточках броней.

Способы оплаты

СпособКогдаЧто происходит
Картой онлайнВсегдаСоздаётся payment в провайдере, клиент платит. Embedded-виджет (YooKassa) или redirect (остальные).
При получении (cash-on-delivery)Если в корзине есть товар с requiresShipping=trueЗаказ переходит в awaiting_cod. Провайдеру ничего не отправляется. Бизнес доставляет товар и принимает оплату на месте.

Доставка

Если хоть один товар в корзине требует доставки (requiresShipping=true), на чекауте появляется блок выбора метода.

Методы доставки (настройка в кабинете)

/account/commerce → вкладка «Доставка» (AppShippingSettings):

Тип методаОписание
СамовывозОдна или несколько точек выдачи с адресом. Клиент выбирает точку.
Фиксированный тарифОдна цена для всех. Опция «бесплатно от суммы заказа X ₽».
ЗональныйГорода → своя цена и срок. Поддерживает зону-fallback («все остальные города»). «По городу бесплатно» — отдельный флаг.

Все методы редактируются в диалоге AppShippingMethodDialog. Внешние перевозчики (СДЭК, Boxberry) — будущая фаза, интерфейс адаптера уже готов.

Чекаут клиента

  • Автоопределение города по IP (сервис ipwho.is, best-effort).
  • Автокомплит городов из бандлованного списка городов РФ — без внешних ключей.
  • Клиент выбирает метод (с ценой и сроком) и, для самовывоза, точку.

Расчёт на сервере

finalize пересчитывает стоимость доставки по выбранному методу (quoteSingleMethod) — клиентскому deliveryCost сервер не доверяет. В заказ пишется снапшот: метод, точка самовывоза, срок доставки (commerce_order.shipping_method_*, pickup_point_id, delivery_eta_*).

API доставки: GET /api/shipping/{methods,quote,cities,detect-city}.

Cash-on-delivery flow

  1. Клиент выбирает «При получении» → finalize.
  2. Заказ переходит в awaiting_cod.
  3. Email клиенту: «Заказ оформлен, оплата при получении».
  4. Push владельцу: «Новый заказ под доставку».
  5. Бизнес доставляет, клиент платит на месте.
  6. Владелец вручную меняет статус на paid (или cancelled).
  7. Сток корректируется в момент paid.

Embedded YooKassa

После finalize, если provider = YooKassa, возвращается confirmation_token. На чекаут-странице рендерится YooMoneyCheckoutWidget — клиент платит, не уходя со страницы. Конверсия выше, чем с redirect, на 5–15%.

Для остальных провайдеров используется confirmation_url (классический redirect). В Telegram/VK — только redirect.

Embedded работает на host'е framix.app и на любых ваших custom-доменах — без дополнительной настройки.

Cron auto-expire

Брошенные draft'ы (клиент открыл чекаут, ушёл, не оплатил) переходят в expired после TTL (раз в час):

  • Сток больше не блокируется.
  • Статистика по конверсии не загрязняется.

pending_payment со ссылкой на провайдера не трогаем — поздний webhook должен иметь возможность долететь и перевести в paid.

Где именно живёт чекаут

URL / компонентНазначение
/checkout/[token]Публичная страница чекаута
AppCheckoutModalМодалка, используется в каталоге и на странице товара
AppCheckoutWidgetInline-виджет для чекаута в чате агента

Что хранится

Поле в commerce_orderЧто
statusdraftpending_payment / awaiting_codpaid / cancelled / expired / refunded
itemsСнапшот позиций: [{ productId, name, price, quantity }] — не FK, изменения цен товара не ломают заказ
amountСумма в копейках
discount_amountСкидка от суммы корзины в копейках
discount_percentПроцент примененной скидки
customer_fieldsЗначения кастомных полей формы (JSONB)
shipping_method_typeВыбранный метод доставки
pickup_point_idТочка самовывоза (если выбрана)
delivery_eta_min/maxСрок доставки в днях
paymentUrlconfirmation_url для redirect-режима
confirmationTokenДля embedded YooKassa
expiresAtTTL ссылки
paidAt, refundedAtМоменты смены статуса

Что видит клиент после оплаты

  • Email с подтверждением и кнопкой «Открыть мои заказы».
  • Кнопка ведёт в /my (вкладка «Заказы») — общий кабинет покупателя, вход по email + OTP.
  • В кабинете — статус заказа, кнопка повторной оплаты (для pending_payment), история броней.

На этой странице