API мобильного приложения (1.0.0-draft)

Download OpenAPI specification:

API приложения-маркетплейса трёх торговых сетей: Пицца Лисицца, Godzilla, Donex. Собрано по POC (staging.proto.sites.iqd.by): каждый экран и каждое действие POC разобраны и привязаны к запросу. Таблица соответствия — в разделе «Покрытие POC».

Листинг каталога

Листинг каталога сети (разделы, карточки, цены в списке) — TODO, описывается отдельно. На него же ведут url секций satellites в карточке товара («добавить к заказу» и т. п.).

От листинга этому API нужно одно: uuid и type товара. С ними приложение открывает карточку и кладёт товар в корзину.

Основа

  • Базовый путь /api/v1. JSON, UTF-8, ключи в snake_case.
  • Поле без значения приходит как null, пустой список — как []. Ключи не пропадают.
  • Неизвестные поля клиент игнорирует: новые поля добавляются без смены версии.
  • Успех — {"data": …}. У постраничных списков рядом meta с page, per_page, total.
  • Ошибка — {"error": {"code", "message", "fields"}}. Ветвиться только по code; message — готовый русский текст, его можно показать.
  • Деньги — {"currency": "BYN", "amount": "26.90"}. Сумма всегда строка с двумя знаками. Суммы считает сервер, клиент их не пересчитывает.
  • Телефон — +375 и 9 цифр, без пробелов и скобок.
  • Дата и время — ISO 8601 со смещением (2026-11-12T18:30:00+03:00). Слоты времени — строки HH:MM по времени Минска.
  • Идентификаторы — uuid. В примерах вместо настоящих uuid стоят подписи в угловых скобках (<big_size_uuid>), чтобы было видно, что на что ссылается.
  • Торговая сеть в путях — её slug из GET /trade-networks (pzz, godzilla, donex); в приложение список сетей не зашивается.

Заголовки

Заголовок Значение Когда
Authorization: Bearer <токен> токен сессии все запросы, кроме POST /session
X-Client ios или android все запросы
X-Client-Version версия и сборка, 1.4.0+123 все запросы
Idempotency-Key uuid только создание заказа

Ошибки

code HTTP Когда
validation_failed 422 тело или параметры не прошли проверку; подробности в fields
session_expired 401 токен сессии неизвестен или истёк: создать сессию заново
unauthenticated 401 сессия жива, но действие требует входа
not_found 404 нет такого ресурса или он чужой
address_required 409 сети нужен объект, а адрес не выбран
address_out_of_zone 409 сеть не доставляет по выбранному адресу
pickup_point_required 409 выбран самовывоз, а точка не выбрана
item_invalid 422 состав позиции не сходится с товаром; причина в reason
item_unavailable 422 товар, вариант или добавка сейчас недоступны на объекте
cart_invalid 422 корзину нельзя оформить; причины в blockers
order_in_progress 409 заказ по этой корзине уже создаётся
order_not_cancellable 409 заказ уже нельзя отменить из приложения
account_has_active_orders 409 аккаунт нельзя удалить, пока есть незавершённый заказ
code_invalid 422 неверный код из SMS; attempts_left — сколько попыток осталось
code_expired 422 код истёк или попытки кончились: запросить новый
resend_too_early 429 новый код запрошен раньше, чем можно; retry_after_seconds
phone_taken 409 новый телефон уже принадлежит другому клиенту
too_many_requests 429 превышен лимит; retry_after_seconds и заголовок Retry-After
service_unavailable 503 сервер заказов недоступен; повторить через retry_after_seconds
internal_error 500 ошибка сервера

401, 429, 500 и 503 возможны у любого запроса и в описаниях запросов не повторяются. Автоматически повторять можно только GET. Создание заказа повторяется с тем же Idempotency-Key.

Как устроено состояние

Всё состояние посетителя живёт на сервере в сессии. Приложение хранит только токен.

Что Сколько Где менять
Адрес доставки один на все сети PUT /address
Клиент (вход) один на все сети /auth/*
Тип обслуживания (доставка или самовывоз) один на все сети PUT /service; его же переключают выбор адреса и выбор точки
Точка самовывоза своя у каждой сети PUT /trade-networks/{tn}/pickup-point
Объект — точка, по меню которой считаются цены и наличие свой у каждой сети, определяет сервер не меняется напрямую
Корзина своя у каждой сети /trade-networks/{tn}/cart

Объект сети сервер выбирает сам: при доставке — по зоне выбранного адреса, при самовывозе — это выбранная точка сети. У разных объектов разные цены и наличие. В POC «Лисичка» стоит 26,90 на Немиге и 27,90 на Лобанке.

GET /session отдаёт всё это одним ответом. Запросы, которые меняют адрес, тип обслуживания или вход, возвращают обновлённую сессию. Если у сети сменился point.uuid, приложение перезагружает её листинг и корзину.

Нулевое меню

Пока у сети нет объекта (point: null), приложение показывает нулевое меню сети: общий ассортимент и общие цены. Объекта нет в трёх случаях:

  1. адрес доставки ещё не выбран;
  2. адрес выбран, но сеть туда не доставляет (delivery.available: false);
  3. выбран самовывоз, а точка этой сети не выбрана.

В нулевом меню карточка товара открывается и отдаёт те же данные, но положить товар в корзину нельзя: POST …/cart/items ответит 409 с кодом случая. Приложение этот ответ не ждёт — оно видит point: null и по нажатию «+» открывает выбор адреса, как в POC.

Запуск приложения

  1. Токена нет — POST /session, сохранить токен в защищённом хранилище. Токен есть — GET /session. На 401 session_expired создать сессию заново: адрес, корзины и вход потеряны.
  2. GET /trade-networks — вкладки сетей.
  3. Листинг открытой сети.
  4. Дальше — по действиям пользователя.

Покрытие POC

Экран или действие POC Запрос
Вкладки сетей в шапке, цвет, слоган, телефон колл-центра GET /trade-networks
Нижняя плашка: адрес, сумма корзины открытой сети GET /session
Меню сети, разделы, баннеры листинг каталога
Карточка товара: варианты, добавки, КБЖУ, состав набора GET /trade-networks/{tn}/products/{uuid}
«+» у простого товара, кнопка корзины в строке варианта POST …/cart/items
Чип добавки в строке, которая уже в корзине PATCH …/cart/items/{uuid} с additions
«− n +» на карточке, в строке варианта и в корзине PATCH …/cart/items/{uuid} с quantity
«Удалить из заказа» DELETE …/cart/items/{uuid}
Корзина: позиции, скидка, доставка, итог, «нет в наличии» GET …/cart
«добавить к заказу» в корзине и в карточке секция satellites: листинг по её url; добавление — POST …/cart/items
Экран «адрес»: улица, дом GET /address/search, GET /address/streets/{uuid}/buildings
«Ввести адрес моей геопозиции» GET /address/nearby
Тариф и предупреждения под выбранным домом GET /address/buildings/{uuid}/delivery
«ваши адреса» на экране адреса GET /account/addresses
«продолжить» на экране адреса PUT /address
Вкладка «самовывоз»: список точек, «продолжить» GET …/pickup-points, PUT …/pickup-point
«время доставки», «время самовывоза» GET …/time-slots
Форма заказа, ошибки под полями, кнопка «проверяем…» POST …/checkout/validate
«заказать» POST …/orders
«онлайн», «онлайн халва», «крок» блок payment заказа, GET /orders/{uuid}/payment
«Заказ оформлен!», «Заказ № …» GET /orders/{uuid}
«войти по номеру телефона» POST /auth/code, POST /auth/verify
Кабинет → заказы: актуальные и прошедшие, вкладки сетей GET /account/orders
Крестик «Отменить заказ» POST /orders/{uuid}/cancel
«Повторить заказ» POST /account/orders/{uuid}/repeat
Профиль: телефон, имя, почта, дата рождения GET и PATCH /account/profile, /account/phone/*
«Push-уведомления», «SMS-уведомления» PATCH /account/profile, PUT /devices/current
«Доступ к звонкам и SMS», «Доступ к геолокации» разрешения ОС, API не нужен
Адреса доставки: «сюда вы уже заказывали», крестик GET /account/addresses, DELETE /account/addresses/{uuid}
«выйти из профиля», «удалить аккаунт» POST /auth/logout, DELETE /account

Открытые вопросы

  1. Наличие в карточке товара. В структуре товара нет признака «недоступно» ни у варианта, ни у добавки. В POC он есть: зачёркнутый чип «пайк-борт с чеддером», «нет в наличии» у всей позиции. Предложение: is_available у варианта и у каждой добавки. До решения приложение узнаёт о недоступности из листинга и из ответа 422 item_unavailable.
  2. Недоступные позиции при оформлении. Сейчас описано как в POC: заказ нельзя оформить, пока их не удалят. Другой вариант — заказ создаётся из доступных позиций.
  3. Смена телефона. Если к первой версии не успеваем — поле телефона в профиле только для чтения.

Сессия

Сессия создаётся один раз и живёт 30 дней с последнего запроса. В ней адрес, корзины, выбор по каждой сети и вошедший клиент. Гость и вошедший клиент работают одинаково: вход нужен только для кабинета.

После входа по SMS сервер выдаёт новый токен, прежний перестаёт работать. Адрес и корзины при этом сохраняются.

Создать сессию

Первый запуск приложения или ответ 401 session_expired. Тело пустое, заголовок Authorization не нужен. Токен приходит в meta.session_token; приложение кладёт его в защищённое хранилище и никуда, кроме заголовка, не передаёт.

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Состояние сессии

Вошедший клиент, адрес, тип обслуживания и состояние каждой сети: объект, условия доставки, точка самовывоза, сводка корзины. Вызывается при запуске и при возврате приложения из фона. Сводки корзин нужны для суммы в нижней плашке; саму корзину отдаёт GET /trade-networks/{tn}/cart.

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Торговые сети

Список сетей маркетплейса. Справочник, меняется редко — можно держать в памяти 5 минут.

Список сетей

Активные сети в порядке показа. Сессия для запроса не нужна.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Товар

Карточка товара. Структура зависит от type; общие поля у всех типов одинаковые, остальное лежит в ключе с именем типа.

type Что это Пример в POC
simple один товар, один вариант Coca-Cola, соус
configurable несколько вариантов по измерениям, у варианта свои добавки и модификаторы пицца: «Большая», «Стандартная», «Тонкая», «Полпиццы»
group набор из нескольких товаров одной ценой «Пайк-сет»: три пайка
family несколько самостоятельных товаров на одной карточке «Чикенбургер»: бургер и бургер с картошкой

Цены и доступность зависят от объекта сети. Объект сервер берёт из сессии; если объекта нет — отдаёт данные нулевого меню.

Товар с вариантами

  1. dims — измерения и их значения. У пиццы одно измерение size; экран рисует по строке на каждое значение.
  2. variants — продаваемые варианты. Вариант находится по совпадению его dims с выбранными значениями. Комбинации без варианта не продаются.
  3. known_additions и known_modifiers — справочник всего, что бывает у товара. Что доступно конкретному варианту, перечислено в его suitable_additions и suitable_modifiers: там uuid записей верхнего уровня справочника — одиночных добавок и контейнеров.
  4. Одна и та же по смыслу добавка у разных вариантов — разные записи с разными uuid и ценами: «Двойной сыр» стоит 4,00 для стандартной и 5,00 для большой. Показывать name; inner_name — служебное название.

Добавки и модификаторы

Запись Смысл limit
addition платная добавка сколько штук можно взять на одну единицу товара
adtn_container группа добавок «выбери из» (борт пиццы) сколько добавок из группы можно выбрать; у вложенных limit: null
modifier бесплатное пожелание («без лука») всегда 1: включён или нет
mdfr_container группа модификаторов «выбери из» («разрезать на 4» или «на 6») сколько можно выбрать

default контейнера — uuid записи, выбранной изначально, или null, если изначально ничего не выбрано. В корзину передаются uuid самих добавок и модификаторов, не контейнеров.

Цена единицы = base_price варианта + сумма цен выбранных добавок с учётом количества. Это для показа до добавления; после добавления цену позиции отдаёт корзина. suppressed_price — прежняя, зачёркнутая цена; null, если скидки нет.

Состав, вес, КБЖУ

recipes — данные по конкретным сочетаниям. Запись подходит, если её properties.dims равны выбранным, а properties.additions содержит ровно выбранные добавки. Порядок поиска:

  1. точное совпадение варианта и добавок;
  2. тот же вариант без добавок;
  3. ничего не нашлось — блок состава и КБЖУ не показывать.

nutrition_facts — на 100 г: белки, жиры, углеводы в граммах, энергия в ккал. product_amount и product_measure — готовые строки для показа («830 г», «36 см»).

Набор и семейство

У group вложенные товары в items[].target показываются как состав набора («Что входит в набор»), отдельно их не купить. В корзину кладётся сам набор; добавки и модификаторы у набора свои, по тем же правилам.

У family каждый вложенный товар — полноценный товар своего типа со своим uuid, ценой и добавками. Экран рисует по строке на каждый; в корзину кладётся uuid вложенного товара, не семейства. У самого семейства нет ни цены, ни добавок, ни состава. Количество для карточки семейства в листинге отдаёт корзина в counters.

Дополнительные секции

satellites — секции под карточкой («добавить к заказу» и т. п.). В товаре секция только объявлена: для каждой приложение делает отдельный запрос по её url и рисует её независимо от карточки. url ведёт на листинг каталога, ответ — в формате листинга.

Поле Смысл
type как рисовать: satellite-roller — горизонтальная лента, satellite-showcase — витрина
url готовый адрес запроса; приложение его не собирает и не меняет
is_contrast секция на контрастном фоне

Карточка товара

Товар по uuid из листинга. Цены — объекта сети из сессии; без объекта — цены нулевого меню. Как читать структуру каждого типа — в описании раздела.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

product_uuid
required
string

Responses

Response samples

Content type
application/json
Example
{
  • "data": {
    }
}

Адрес

Адрес доставки один на все сети. Выбрали «Гикало, 18» в Лисицце — он же действует в Godzilla и Donex.

По одному адресу сети работают по-разному. PUT /address всегда отвечает 200, если дом существует, и возвращает сессию, где у каждой сети свой итог:

У сети Что значит Что показывает приложение
delivery.available: true, point заполнен сеть доставляет меню объекта
delivery.available: false, point: null сеть сюда не доставляет нулевое меню; текст из delivery.unavailable_reason; корзина сети заблокирована
delivery.asap_available: false доставляет, но не «сейчас» текст из delivery.preorder_notice; оформить можно только ко времени

Выбор адреса включает доставку для всех сетей: service_type сессии становится delivery.

То, что сеть не доставляет по адресу, — не ошибка запроса. Корзина такой сети отдаётся с 200 и причиной address_out_of_zone в blockers.

Поиск

В POC два поля: «Улица», затем «Дом». GET /address/search?kind=street — подсказки улиц, GET /address/streets/{uuid}/buildings — дома улицы. Тот же поиск без kind принимает строку целиком («лобанка 77») и возвращает и улицы, и дома.

Дом выбирается только из списка: свободного ввода нет. Запрос подсказок — от 2 символов, с задержкой не меньше 250 мс после последнего нажатия; предыдущий незавершённый запрос отменяется. Подсказки не кэшируются.

Квартира, подъезд, этаж

Это не часть адреса сессии: их вводят в форме заказа. Если адрес выбран из сохранённых (saved_address_uuid), сессия отдаёт их в address.details — ими заполняется форма.

Подсказки адреса

Улицы и дома по введённой строке. У дома сразу видно, какие сети туда доставляют. Сессия не нужна, ответ не кэшируется.

query Parameters
q
required
string [ 2 .. 100 ] characters
Example: q=лобанка 77

Строка поиска, от 2 символов. Сокращения («ул», «пр-т»), регистр и «ё» значения не имеют.

kind
string
Value: "street"

street — только улицы (поле «Улица» в POC). Без параметра — улицы и дома.

limit
integer [ 1 .. 20 ]
Default: 10

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Дома улицы

Дома выбранной улицы для поля «Дом». Порядок естественный (2, 2А, 10, 10 к1) — приложение его не меняет. В списке только дома, куда доставляет хотя бы одна сеть. Сессия не нужна.

path Parameters
street_uuid
required
string
query Parameters
q
string <= 15 characters

Начало номера дома. Без параметра — все дома улицы.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Дома рядом с геопозицией

«Ввести адрес моей геопозиции». Ближайшие дома к координатам, первый — самый близкий. Приложение подставляет его в поля и ждёт подтверждения пользователя: сам адрес выбирается только через PUT /address. Сессия не нужна.

query Parameters
lat
required
number
Example: lat=53.9045
lon
required
number
Example: lon=27.5615

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Условия доставки по дому

Что каждая сеть предлагает по этому дому, до того как адрес выбран. Сессию не меняет. Из этого ответа берутся строки под полем «Дом» в POC: «Платная доставка 5,00, бесплатно от 39,00 руб.», «Доставка по данному адресу осуществляется только до главного входа», «Лисицца не доставляет по данному адресу».

path Parameters
building_uuid
required
string

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Выбрать адрес доставки

Адрес выбирается для всех сетей сразу и включает доставку: service_type сессии становится delivery. Сервер заново определяет объект каждой сети и пересчитывает корзины.

Ответ 200 приходит и тогда, когда часть сетей или все сети по адресу не доставляют: это видно в trade_networks[].delivery.

После ответа приложение сравнивает point.uuid каждой сети с прежним и перезагружает листинг и корзину там, где он изменился.

Authorizations:
session
Request Body schema: application/json
required
building_uuid
string

Дом из подсказок или списка домов улицы.

saved_address_uuid
string

Адрес из GET /account/addresses. Нужен вход.

Responses

Request samples

Content type
application/json
Example
{
  • "building_uuid": "<kazimirovskaya_39_uuid>"
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Сбросить адрес

Тип обслуживания не меняется. При доставке все сети остаются без объекта и показывают нулевое меню; позиции корзин становятся недоступными, но не удаляются.

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Самовывоз и время

Тип обслуживания один на все сети: выбрали самовывоз — он действует в Лисицце, Godzilla и Donex. Точка самовывоза у каждой сети своя. Сеть, в которой точка ещё не выбрана, остаётся без объекта и показывает нулевое меню.

Действие Запрос Тип обслуживания
выбрали адрес доставки PUT /address становится delivery
выбрали точку самовывоза сети PUT /trade-networks/{tn}/pickup-point становится pickup
переключили тип, ничего не выбирая заново PUT /service какой передан

Переключение ничего не стирает: адрес и выбранные точки остаются в сессии. Вернулись на доставку — сети снова работают по адресу, вернулись на самовывоз — по своим точкам.

Смена типа обслуживания или точки меняет объект сети, а с ним цены и наличие. Корзина остаётся та же: сервер пересчитывает её по новому объекту, позиции, которых там нет, становятся inactive.

Точки самовывоза

Точки сети, где можно забрать заказ. Точку с is_accepting_orders: false показывают серой и выбрать не дают. Сессия не нужна.

path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Выбрать точку самовывоза

Выбирает точку самовывоза сети и включает самовывоз: service_type сессии становится pickup для всех сетей. Корзина сети пересчитывается по выбранной точке.

Точки других сетей не меняются. Сеть, где точка ещё не выбрана, остаётся без объекта и показывает нулевое меню.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Request Body schema: application/json
required
pickup_point_uuid
required
string

Точка из GET /trade-networks/{tn}/pickup-points.

Responses

Request samples

Content type
application/json
{
  • "pickup_point_uuid": "<lobanka_point_uuid>"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Доставка или самовывоз

Переключает тип обслуживания для всех сетей, ничего не выбирая заново. Адрес и точки самовывоза остаются те, что уже выбраны.

  • delivery — сети работают по адресу сессии; адреса нет — у всех сетей нет объекта.
  • pickup — каждая сеть работает по своей выбранной точке; сеть без точки остаётся без объекта.

Корзины пересчитываются по новым объектам.

Authorizations:
session
Request Body schema: application/json
required
service_type
required
string (ServiceType)
Enum: "delivery" "pickup"

delivery — доставка, pickup — самовывоз.

Responses

Request samples

Content type
application/json
{
  • "service_type": "delivery"
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Доступное время

Время, на которое можно оформить заказ при текущем объекте сети и текущем типе обслуживания. Из ответа строятся блоки «время доставки» и «время самовывоза».

Окно сдвигается со временем: перечитывать при открытии выбора времени. Время не из ответа сервер при оформлении отклонит.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Responses

Response samples

Content type
application/json
Example
{
  • "data": {
    }
}

Корзина

У каждой сети своя корзина. Она появляется при первом добавлении и живёт 24 часа с последнего изменения. Пустая корзина — обычный ответ 200 с uuid: null и пустым items.

Каждый запрос корзины возвращает её целиком и уже пересчитанной. Отдельного запроса «проверить корзину» нет: GET и есть проверка. Его вызывают при открытии корзины и при возврате приложения из фона.

Позиция

Позиция — это товар, вариант, набор добавок и набор модификаторов. У позиции свой uuid.

  • Тот же товар с тем же составом — одна позиция, растёт quantity.
  • Тот же товар с другим составом — другая позиция.
  • Состав относится ко всем единицам позиции. Изменить одну пиццу из трёх: уменьшить количество на 1 и добавить новую позицию с другим составом.

Счётчики на карточках

counters в корзине — готовое количество для карточек листинга: uuid товара карточки и сколько его единиц в корзине. У товара с вариантами это сумма по всем его позициям, у family — сумма по вложенным товарам. Приложение само ничего не складывает и состав семейства для этого не запрашивает.

Как собирается пицца

В POC пицца попадает в корзину нажатием кнопки в строке размера, а добавки переключаются уже после. Это три запроса:

POST  /trade-networks/pzz/cart/items
      {"product_uuid": "<pizza>", "dims": {"size": "<big>"}}
      → meta.item_uuid = "<line>"

PATCH /trade-networks/pzz/cart/items/<line>
      {"modifiers": ["<без лука>"]}

PATCH /trade-networks/pzz/cart/items/<line>
      {"additions": [{"uuid": "<двойной сыр для большой>", "quantity": 1}]}

Если состав выбран до нажатия, всё уходит одним POST с dims, additions и modifiers.

Правила PATCH:

  • Переданный ключ заменяет свою часть целиком. additions — это полный новый список добавок, а не «добавь ещё одну». Снять все добавки — "additions": []. Ключ не передан — часть не меняется.
  • quantity — итоговое количество, а не приращение. Серию нажатий «+» можно отправить одним запросом. 0 удаляет позицию.
  • Вариант (dims) у позиции не меняется. Другой размер — это другая позиция: в POC у каждого размера своя строка и свой счётчик.
  • Если после замены состав совпал с другой позицией, они сливаются. meta.item_uuid указывает на оставшуюся.

Что проверяет сервер при добавлении

Проверка Ошибка
у сети есть объект 409 address_required, address_out_of_zone или pickup_point_required
у configurable переданы все измерения и такой вариант есть 422 item_invalid, reason: variant_not_found
каждая добавка входит в suitable_additions варианта (сама или через контейнер) 422 item_invalid, reason: addition_not_suitable
количество добавки не больше её limit, выбранных из контейнера — не больше limit контейнера 422 item_invalid, reason: addition_limit_exceeded
то же для модификаторов reason: modifier_not_suitable, modifier_limit_exceeded
в корзину кладут не family 422 item_invalid, reason: not_sellable
товар, вариант и добавки есть на объекте и не на стопе 422 item_unavailable

При ошибке корзина не меняется.

Недоступные позиции

Позиция может стать недоступной уже в корзине: товар поставили на стоп, сменился объект, закончилось время действия меню. Такая позиция остаётся в корзине со status: inactive и причиной в inactive_reason. В суммы она не входит. Количество и состав у неё менять нельзя — только удалить. Пока в корзине есть такие позиции, оформить заказ нельзя: в blockers причина items_inactive. Когда товар снова доступен, позиция сама становится active.

Если у сети пропал объект (сменили адрес, сеть туда не доставляет), недоступными становятся все позиции, суммы обнуляются, в blockers — address_out_of_zone. Позиции не удаляются: вернули адрес — корзина ожила.

Что мешает оформить

can_checkout: false и список blockers. Приложение показывает message и не даёт нажать «заказать».

code Когда
cart_empty нет позиций
address_required доставка, адрес не выбран
address_out_of_zone сеть не доставляет по выбранному адресу
pickup_point_required самовывоз, точка не выбрана
items_inactive есть недоступные позиции
point_closed объект временно не принимает заказы

warnings оформлению не мешают, их просто показывают: price_changed — цена позиции изменилась с прошлого ответа.

Доставка и скидки

delivery.cost — стоимость доставки по адресу при текущей сумме. free_from — порог бесплатной доставки, left_to_free — сколько до него осталось. Заказ ниже порога оформить можно, доставка будет платной. Порог сравнивается с суммой до скидок.

discounts — применённые скидки с готовыми подписями («Скидка 10% на товары»). В POC скидка видна только в чеке после заказа; в API она есть в корзине всегда.

Корзина сети

Корзина, пересчитанная на момент запроса: цены, доступность позиций, доставка, скидки, причины, мешающие оформить. Ответ 200 приходит всегда, включая пустую корзину и случай, когда сеть не доставляет по адресу.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Responses

Response samples

Content type
application/json
Example
{
  • "data": {
    }
}

Добавить товар

Кладёт товар в корзину. Если позиция с таким же товаром, вариантом, добавками и модификаторами уже есть, её количество увеличивается на quantity; иначе создаётся новая позиция.

uuid позиции — в meta.item_uuid.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Request Body schema: application/json
required
product_uuid
required
string

simple, configurable или group. Для семейства — uuid вложенного товара.

object

Обязателен для configurable — значение каждого измерения. Для остальных типов не передаётся.

Array of objects (AdditionChoice)
Default: []

Выбранные добавки. Без ключа — без добавок.

modifiers
Array of strings
Default: []

uuid выбранных модификаторов.

quantity
integer [ 1 .. 99 ]
Default: 1

Responses

Request samples

Content type
application/json
Example
{
  • "product_uuid": "<cola_vanila_simple>"
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Очистить корзину

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Изменить позицию

Меняет количество и состав позиции. Переданный ключ заменяет свою часть целиком, непереданный не трогается. Вариант (dims) не меняется — другой размер добавляется отдельной позицией.

У позиции со status: inactive менять нельзя ничего: ответ 422 item_unavailable. Её можно только удалить.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

item_uuid
required
string

uuid позиции корзины.

Request Body schema: application/json
required
non-empty
quantity
integer [ 0 .. 99 ]

Итоговое количество. 0 удаляет позицию.

Array of objects (AdditionChoice)

Полный новый список добавок.

modifiers
Array of strings

Полный новый список модификаторов.

Responses

Request samples

Content type
application/json
Example
{
  • "quantity": 3
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Удалить позицию

Удаляет позицию целиком, в том числе недоступную. Повторное удаление — тоже 200.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

item_uuid
required
string

uuid позиции корзины.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Оформление

Корзина и форма заказа в POC — один экран. Черновик формы приложение хранит у себя, на сервер он уходит целиком двумя запросами с одинаковым телом:

  1. POST …/checkout/validate — проверка без создания заказа. В POC это момент, когда кнопка показывает «проверяем…». Вызывать при открытии формы и после смены способа оплаты, времени или типа обслуживания, но не на каждый символ.
  2. POST …/orders — создание заказа.

Способ оплаты может не сочетаться с выбранным временем — например, онлайн-оплата с предзаказом. Корзина этого не знает; проверка вернёт ошибку поля payment_method.

Вход для заказа не нужен. Отдельной галочки согласия нет: в POC согласие — это само нажатие кнопки, текст со ссылками на документы показывает приложение.

Улица и дом в форме не передаются — они в сессии. Сменить адрес с экрана заказа — тот же PUT /address.

Время

Что выбрано в POC Что передать
доставка «в течение 60 минут» preorder_at: null
доставка «заказ ко времени» preorder_at — день и слот из GET …/time-slots
самовывоз «срочный заказ», чип «25 мин» pickup_in_minutes: 25
самовывоз «заказ ко времени» preorder_at

Повтор после сбоя сети

Idempotency-Key создаётся в момент нажатия «заказать» и хранится до окончательного ответа. Если ответ не пришёл, запрос повторяется с тем же ключом и тем же телом: сервер вернёт уже созданный заказ, второго не будет. После успешного заказа корзина сети пуста.

Проверить форму заказа

Проверяет форму и корзину так же, как создание заказа, но заказ не создаёт. Ответ 200 приходит и при ошибках: valid: false, ошибки полей в fields, причины по корзине — в cart.blockers.

Незаполненные поля можно присылать как null: проверка вернёт по ним required, остальное проверит как есть.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

Request Body schema: application/json
required
customer_name
required
string or null
required
Phone (string) or null
null or AddressDetails (object)
comment
string or null <= 400 characters
preorder_at
string or null <date-time>
pickup_in_minutes
integer or null
required
PaymentMethod (string) or null
change_from
string or null^\d+\.\d{2}$

Сумма, с которой нужна сдача.

contactless_delivery
boolean
Default: false

Responses

Request samples

Content type
application/json
{
  • "customer_name": "string",
  • "phone": "+375291111111",
  • "address_details": { },
  • "comment": "string",
  • "preorder_at": "2019-08-24T14:15:22Z",
  • "pickup_in_minutes": 0,
  • "payment_method": "cash",
  • "change_from": "100.00",
  • "contactless_delivery": false
}

Response samples

Content type
application/json
Example
{
  • "data": {
    }
}

Создать заказ

Создаёт заказ из корзины сети и формы. Тело то же, что у проверки.

При оплате при получении (cash, card) заказ сразу уходит оператору, корзина очищается. При онлайн-оплате заказ создаётся в статусе created с блоком payment; корзина очищается после оплаты.

Authorizations:
session
path Parameters
trade_network
required
string (TradeNetworkSlug)
Examples: pzz

slug сети из GET /trade-networks. Неизвестная или выключенная сеть — 404 not_found.

header Parameters
Idempotency-Key
required
string

uuid, созданный при нажатии «заказать». Повтор с тем же ключом возвращает тот же заказ.

Request Body schema: application/json
required
customer_name
required
string or null
required
Phone (string) or null
null or AddressDetails (object)
comment
string or null <= 400 characters
preorder_at
string or null <date-time>
pickup_in_minutes
integer or null
required
PaymentMethod (string) or null
change_from
string or null^\d+\.\d{2}$

Сумма, с которой нужна сдача.

contactless_delivery
boolean
Default: false

Responses

Request samples

Content type
application/json
Example
{
  • "customer_name": "Тест",
  • "phone": "+375291111111",
  • "address_details": {
    },
  • "comment": null,
  • "preorder_at": null,
  • "pickup_in_minutes": null,
  • "payment_method": "cash",
  • "change_from": "100.00",
  • "contactless_delivery": false
}

Response samples

Content type
application/json
Example
{
  • "data": {
    }
}

Заказы и оплата

Заказ виден тому, кто его создал: по сессии — гостю, по клиенту — после входа с любого устройства.

Пока заказ не в конечном статусе, экран заказа опрашивает GET /orders/{uuid} не чаще раза в 15 секунд и только пока открыт. Push о смене статуса — сигнал перечитать заказ, а не источник данных.

Онлайн-оплата

Способы online (карта, Apple Pay, Google Pay), halva и krok идут через bePaid.

  1. POST …/orders возвращает заказ в статусе created с блоком payment.
  2. online и halva: открыть payment.redirect_url во встроенном браузере и закрыть его, когда он перейдёт на payment.return_url. krok: показать список банков из payment.krok.banks и открыть приложение банка по ссылке своей платформы.
  3. Опрашивать GET /orders/{uuid}/payment раз в 2–3 секунды, не дольше 2 минут.
  4. Итог оплаты — только ответ этого запроса. Тому, что показала платёжная страница или адрес возврата, верить нельзя.
status Что делать
awaiting ждать и опрашивать дальше
paid «Заказ оформлен!»; заказ ушёл оператору
failed «Оплата не прошла». Повторить POST …/orders с тем же Idempotency-Key — вернётся тот же заказ с новой попыткой оплаты
expired время на оплату вышло (15 минут), заказ отменён. Корзина на месте, оформить заново с новым ключом

Корзина очищается, когда заказ оплачен, а не когда создан.

«Бесконтактная доставка» доступна только при доставке с онлайн-оплатой.

Заказ

Заказ со статусом и составом. Доступен сессии, которая его создала, и клиенту, которому он принадлежит.

Authorizations:
session
path Parameters
order_uuid
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Состояние онлайн-оплаты

Единственный источник правды об оплате. Опрашивается после возврата с платёжной страницы или из приложения банка.

Authorizations:
session
path Parameters
order_uuid
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Отменить заказ

Крестик на карточке актуального заказа. Отменить можно, пока у заказа can_cancel: true. Подтверждение «Отменить заказ № …?» показывает приложение до запроса. Тело пустое.

Authorizations:
session
path Parameters
order_uuid
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Вход

Вход по телефону и коду из SMS. Код — 6 цифр, действует 5 минут, 5 попыток ввода. Новый код — не раньше чем через 60 секунд. Клиент один на все три сети.

Вход ничего не теряет: адрес и корзины гостя остаются. Выход тоже: клиент выходит, сессия с адресом и корзинами живёт дальше.

Отправить код

Отправляет SMS с кодом на номер. Ответ одинаков для нового и для уже известного клиента.

Authorizations:
session
Request Body schema: application/json
required
phone
required
string (Phone) ^\+375\d{9}$

Responses

Request samples

Content type
application/json
{
  • "phone": "+375291111111"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Войти по коду

Проверяет код и привязывает клиента к сессии. Нового клиента создаёт. В meta.session_token приходит новый токен — заменить сохранённый до любого следующего запроса.

Authorizations:
session
Request Body schema: application/json
required
phone
required
string (Phone) ^\+375\d{9}$
code
required
string^\d{6}$

Responses

Request samples

Content type
application/json
{
  • "phone": "+375291111111",
  • "code": "123456"
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Выйти

Отвязывает клиента от сессии. Токен, адрес и корзины остаются. Тело пустое.

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Кабинет

Кабинет один на все сети. Все запросы требуют входа, иначе 401 unauthenticated.

Заказы

GET /account/orders отдаёт заказы всех сетей; у каждого заказа есть trade_network. В POC сверху вкладки сетей, под ними блоки «актуальные заказы» и «прошедшие заказы». Вкладка — параметр trade_network, блок — параметр state. Пустой блок POC не показывает.

Повтор заказа

Позиции заказа добавляются в текущую корзину сети этого заказа, к тому, что там уже лежит. Цены — сегодняшние. Позиции, которых сейчас нет, добавляются как inactive: пользователь видит, чего не хватило, и удаляет их сам. Сети заказа нужен объект — иначе 409 address_required: приложение открывает выбор адреса и повторяет запрос.

Адреса

Адрес попадает в список сам после заказа с доставкой. Создать или изменить его запросом нельзя, можно только удалить. Выбрать сохранённый адрес для доставки — PUT /address с saved_address_uuid.

Уведомления

Два переключателя: push и sms. Это согласие клиента, оно хранится в профиле и действует на всех его устройствах. Чтобы push доходил до конкретного телефона, приложение регистрирует устройство через PUT /devices/current после того, как ОС выдала токен, и повторяет при его смене.

Профиль

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Изменить профиль

Меняет имя, почту, дату рождения и настройки уведомлений. Передаются только изменённые поля. null очищает имя, почту или дату рождения. Телефон здесь не меняется.

Authorizations:
session
Request Body schema: application/json
required
non-empty
name
string or null

2–63 символа, как имя в заказе.

email
string or null <email> <= 254 characters
birth_date
string or null <date>

Не в будущем.

object

Можно передать один переключатель.

Responses

Request samples

Content type
application/json
Example
{
  • "name": "Тест",
  • "email": "test@example.com",
  • "birth_date": "2000-01-01"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Смена телефона — отправить код

Отправляет код на новый номер. Правила кода те же, что при входе.

Authorizations:
session
Request Body schema: application/json
required
phone
required
string (Phone) ^\+375\d{9}$

Responses

Request samples

Content type
application/json
{
  • "phone": "+375447751232"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Смена телефона — подтвердить

После подтверждения клиент входит по новому номеру. История заказов и адреса остаются за ним.

Authorizations:
session
Request Body schema: application/json
required
phone
required
string (Phone) ^\+375\d{9}$
code
required
string^\d{6}$

Responses

Request samples

Content type
application/json
{
  • "phone": "+375291111111",
  • "code": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Заказы клиента

Заказы всех сетей, новые первыми. Заказы, которые ждут онлайн-оплаты (created), в список не попадают.

Экран кабинета делает два запроса на открытую вкладку сети: state=active и state=past.

Authorizations:
session
query Parameters
state
string
Enum: "active" "past"

active — актуальные (не в конечном статусе), past — доставленные, полученные и отменённые. Без параметра — все.

trade_network
string (TradeNetworkSlug)
Examples: trade_network=pzz

Только заказы этой сети.

page
integer >= 1
Default: 1
per_page
integer [ 1 .. 50 ]
Default: 20

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    }
}

Повторить заказ

Добавляет позиции заказа в корзину его сети. Приложение после ответа открывает эту сеть и её корзину. Тело пустое.

Authorizations:
session
path Parameters
order_uuid
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Адреса прошлых доставок

Адреса, по которым клиент уже заказывал, последний использованный первым. Общие для всех сетей.

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Удалить адрес

Убирает адрес из списка. На выбранный в сессии адрес и на прошлые заказы не влияет. Подтверждение показывает приложение. Повторное удаление — тоже 204.

Authorizations:
session
path Parameters
address_uuid
required
string

Responses

Удалить аккаунт

Удаляет профиль, сохранённые адреса и привязку истории заказов к клиенту; сессия становится гостевой. Восстановить нельзя. Подтверждение показывает приложение.

Запрос обязателен для публикации в App Store: приложение со входом должно давать удалить аккаунт.

Authorizations:
session

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "meta": {
    }
}

Зарегистрировать устройство для push

Привязывает push-токен устройства к сессии. Вызывается, когда ОС выдала или сменила токен. Вход не обязателен: гость тоже получает push о своём заказе. push_token: null отвязывает устройство — когда пользователь запретил уведомления в настройках ОС.

Authorizations:
session
Request Body schema: application/json
required
push_token
required
string or null

Токен APNs или FCM.

Responses

Request samples

Content type
application/json
{
  • "push_token": "fcm_e3b0c44298fc1c149afbf4c8996fb924"
}