Ⓜ️ Интеграция по схеме мегаптеки

Ⓜ️ Интеграция по схеме мегаптеки #


Интеграция по схеме мегаптеки подразумевает реализацию со стороны Партнёра HTTP REST интерфейса для взаимодействия с Ютекой, которая совместима для работы с сервисом мегаптеки. Если у Партнёра уже есть программное обеспечение для работы с мегаптекой, оно может быть переиспользовано для работы с Ютекой.

Ограничения #

Поскольку схема взаимодействия спроектирована и совместима с мегаптекой, основным ограничением является поддержка и доработка этой схемы под индивидуальные особенности Партнёра. Отклонение от схемы требует сложных доработок с сохранением обратной совместимости, а также займёт значительно больше времени, чем при использовании другой схемы взаимодействия, например, индивидуального REST-интерфейса Партнёра или FTP + RESt.

Также эта схема на данный момент не поддерживает интеграцию самовывоза на следующий день (доставка со склада, extendedPickup)

Из чего состоит интеграция #

Интеграция с Ютекой состоит из двух глобальных процессов:

  • синхронизация каталога (выгрузки)
  • обмен заказами
graph TD
A[Интеграция с Ютекой] --> B[Выгрузки]
A --> C[Обмен заказами]

Выгрузки #

Это процесс получения Ютекой информации от Партнёра о:

  • аптеках
  • аптечных сетях (не обязательно)
  • остатках

Обмен заказами #

Это процесс, при котором Ютека принимает запросы на:

  • получение списка заказов на обработку в инфраструктуре Ютеки для аптек Партнёра. Заказы, которые могут быть в списке:
    • новые (созданы на Ютеке, инфраструктура Партнёра не брала заказ в обработку)
    • отменённые (инфраструктура Партнёра уже взяла заказ в обработку, произошла отмена заказа со стороны пользователя или Ютеки)
  • обработку заказов Партнёром, полученных в списке
  • изменение статусов по заказам, находящиеся в обработке у Партнёра (сборка, частичная сборка, продажа, отмена со стороны аптеки)

Реализация интеграции по схеме мегаптеки с нуля #

Реализация выгрузок #

Общая схема выгрузок выглядит следующим образом:

graph TD
Partner[Партнёр] --> |POST/PUT| Pharmacies[Аптеки]
Partner --> |POST| PharmacyNetworks["Аптечные сети (не обязательно)"]
Partner --> |POST/PUT| Stocks[Остатки]
Pharmacies --> |/stores| Uteka[Ютека]
PharmacyNetworks --> |/stores/networks| Uteka
Stocks --> |/prices| Uteka

Аптечные сети (не обязательно) #

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

Нужна только для того, чтобы отсекать аптеки из выгрузки, если предварительно не была загружена для неё аптечная сеть.

Если требуется, то на этапе тестирования и запуска интеграции необходимо сообщить об этом в Ютеку, чтобы со стороны Ютеки включить дополнительные проверки по аптечным сетям.

Частота выгрузки сетей аптек Партнёром должна быть от 1 раза в 3 часа до 1 раза в сутки. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.

Для выгрузки данных по аптечным сетям нужно отправить запрос:

POST https://integration.uteka.dev/exch/v1/partnerID/stores/networks?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /stores/networks - эндпоинт для выгрузки аптечных сетей,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Тело запроса должно состоять из следующих полей:

Поле Тип Обязательное Описание
list []Object (см. пример ниже) ✔️ Массив объектов каждой аптечной сети
list[i].id string ✔️ ID аптечной сети
list[i].name string ✔️ Название аптечной сети

Не допускается использование названий полей, типов их содержания не по шаблону.

Добавление дополнительных полей для более специфичной логики обсуждается в индивидуальном порядке.

Пример запроса
POST https://integration.uteka.dev/exch/v1/partnerID/stores/networks?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
    "list": [
        {
            "id": "0DECB304-CFDA-4C18-91F8-D22A09626D91",
            "name": "Аптечная сеть «Сеть»"
        }
    ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Аптеки #

Полная выгрузка аптек #

Выгрузка должна содержать все актуальные аптеки, которые будут доступны для заказов. Рекомендуется выгружать все поля, даже не обязательные, т.к. более полно заполненные аптеки пользователи будут выбирать с большей вероятностью.

Полная выгрузка подразумевает отправку POST запроса. Полная означает, что все аптеки, выгружаемые в Ютеку ранее, полностью заменятся аптеками из запроса.

Так же существуют частичные выгрузки аптек.

Активность аптеки управляется полем is_deleted, где true означает, что аптеку необходимо отключить, а false – включить.

Также за активность аптеки отвечает наличие этой аптеки в выгрузке, что будет эквивалентно полю is_deleted со значением false. Отсутствие аптеки в выгрузке, которая была ранее, эквивалентно полю is_deleted со значением true. (только для полных выгрузок)

Частота выгрузки аптек Партнёром должна быть от 1 раза в 3 часа до 1 раза в сутки. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.

Если работает выгрузка аптечных сетей, и со стороны Ютеки включены дополнительные проверки на их наличие, то необходимо сначала сделать выгрузку аптечных сетей, а уже затем выгружать аптеки. Т.е., ожидается 2 последовательных запроса.

Для выгрузки данных по аптекам нужно отправить запрос:

POST https://integration.uteka.dev/exch/v1/partnerID/stores?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /stores - эндпоинт для выгрузки аптек,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Тело запроса должно состоять из следующих полей:

Поле Тип Обязательное Описание
list []Object (см. пример ниже) ✔️ Массив объектов каждой аптеки
list[i].id string ✔️ ID аптеки
list[i].network_id string ✔️ ID аптечной сети из выгрузки аптечных сетей. Если аптечные сети не выгружаются, может быть заполнено любым непустым значением
list[i].brand string ✔️ Название бренда аптеки
list[i].region string ✔️ Название региона аптеки (субъект РФ)
list[i].city string ✔️ Название города аптеки
list[i].address string ✔️ Адрес аптеки в городе (без почтового индекса)
list[i].latitude float64 Широта
list[i].longitude float64 Долгота
list[i].phone string Телефон
list[i].email string Адрес электронной почты аптеки для уведомлений
list[i].schedule string (см. пример ниже) Часы работы в формате
list[i].ogrn string ОГРН аптеки
list[i].is_deleted bool Отметка об удалении

Не допускается использование названий полей, типов их содержания не по шаблону.

Добавление дополнительных полей для более специфичной логики обсуждается в индивидуальном порядке.

Пример POST запроса
POST https://integration.uteka.dev/exch/v1/partnerID/stores?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
    "list": [
        {
            "id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "network_id": "0DECB304-CFDA-4C18-91F8-D22A09626D91",
            "region": "Московская область",
            "city": "Зеленоград",
            "address": "Колотушкина, 10Б",
            "brand": "Аптека 007",
            "latitude": 57.12312,
            "longitude": 38.9879,
            "phone": "+7 900 123 45 67",
            "email": "email@exmaple.ru",
            "schedule": "{\"1\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"2\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"3\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"4\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"5\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"6\":{\"open\":\"10:00\",\"close\":\"17:30\"},\"7\":{\"open\":\"\",\"close\":\"\"}}",
            "ogrn": "1234567890",
            "is_deleted": false
        },
        {
            "id": "626D91D6-CFDA-4F92-A1DC-800269A30FE3",
            "network_id": "0DECB304-CFDA-4C18-91F8-D22A09626D91",
            "region": "Московская область",
            "city": "Зеленоград",
            "address": "Пушкина, 101",
            "brand": "Аптека 008",
            "latitude": 57.12317,
            "longitude": 38.9861,
            "phone": "+7 900 123 45 68",
            "email": "email@exmaple.ru",
            "schedule": "{\"1\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"2\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"3\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"4\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"5\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"6\":{\"open\":\"10:00\",\"close\":\"17:30\"},\"7\":{\"open\":\"\",\"close\":\"\"}}",
            "ogrn": "1234567890",
            "is_deleted": false
        }
    ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

График работы #

График работы ожидается в строковом типе, но в этой строке может быть завёрнут JSON-объект.

Для JSON-объекта:

  1. Дни недели пронумерованы от 1 до 7, где 1 - понедельник, 7 - воскресенье.
  2. open - время открытия аптеки, close - время закрытия. Относительно этих значений присылаются уведомления пользователям, и считаются регламенты (например, “будет собран до”).
  3. Если аптека не работает в какой-то день (например, в воскресенье), то день недели пропускается, либо значения open и close заполняются пустыми строками: "open":"","close":"".
  4. Если аптека круглосуточная, то open и close присылается со значением 00:00: "open":"00:00","close":"00:00"
  5. Разработчикам: нужно учитывать, что JSON формат относится только к одному полю из АПИ, а поле (колонка в выгрузке) должна быть строкой в соответствующем формате (JSON, CSV или XML, как договорились). Поэтому нужно не забывать про экранирование данных при подстановке в выгрузку.
Пример строкового workingHours с завёрнутым JSON-объектом
"workingHours": "{\"1\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"2\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"3\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"4\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"5\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"6\":{\"open\":\"10:00\",\"close\":\"17:30\"},\"7\":{\"open\":\"\",\"close\":\"\"}}"
  • Либо это может быть текст, содержащий сокращённые названия дней недели и их время работы.
  • Дни недели могут указываться интервалами от пн до вс.
  • Интервалы разделяются: ,
  • Если аптека не работает в какой-то день или интервал, то для него нужно указать ключевое слово выходной.
  • Если аптека работает круглосуточно, то для этого дня или интервала нужно указать ключевое слово круглосуточно
Пример строкового workingHours с текстовыми днями недели
"пн-пт 08:00-20:00, сб 10:00-17:30, вс выходной"

Для круглосуточной аптеки:

"пн-вс круглосуточно"

Частичная выгрузка аптек #

Частичная выгрузка подразумевает отправку PUT запроса. Частичная означает, что аптека может быть только добавлена (или обновлена, если ранее выгружалась) в Ютеку.

Может быть полезно, когда нет централизованного ПО партнёра, и каждая аптека отправляет данные только про себя.

Технически полностью аналогична полной выгрузке аптек, за исключением:

  • Отправка методом PUT вместо POST
  • Ожидается только одна аптека в списке в теле запроса (первая обработается, остальные будут проигнорированы).
Пример PUT запроса
PUT https://integration.uteka.dev/exch/v1/partnerID/stores?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
    "list": [
        {
            "id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "network_id": "0DECB304-CFDA-4C18-91F8-D22A09626D91",
            "region": "Московская область",
            "city": "Зеленоград",
            "address": "Колотушкина, 10Б",
            "brand": "Аптека 007",
            "latitude": 57.12312,
            "longitude": 38.9879,
            "phone": "+7 900 123 45 67",
            "email": "email@exmaple.ru",
            "schedule": "{\"1\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"2\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"3\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"4\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"5\":{\"open\":\"08:00\",\"close\":\"20:00\"},\"6\":{\"open\":\"10:00\",\"close\":\"17:30\"},\"7\":{\"open\":\"\",\"close\":\"\"}}",
            "ogrn": "1234567890",
            "is_deleted": false
        }
    ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Остатки #

Полная выгрузка остатков #

Модуль интеграции должен регулярно передавать информацию по остаткам товаров в аптеках.

Полная выгрузка подразумевает отправку POST запроса. Полная означает, что все остатки, выгружаемые в Ютеку ранее, полностью заменятся остатками из запроса.

Остатки передаются в рамках одной аптеки на запрос.

Так же существуют частичные выгрузки остатков.

Частота выгрузки аптек Партнёром должна быть от 1 раза в 15 минут до 1 раза в 30 минут. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.

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

Если полная выгрузка по одной и той же аптеке происходит чаще, чем 1 раз в 15 минут, запрос будет проигнорирован, в ответе вернётся код статуса 429 (см. Обработка ошибок).

Для выгрузки данных по остаткам нужно отправить запрос:

POST https://integration.uteka.dev/exch/v1/partnerID/prices?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /prices - эндпоинт для выгрузки остатков,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Тело запроса должно состоять из следующих полей:

Поле Тип Обязательное Описание
store_id string ✔️ ID аптеки, для которой выгружаются остатки
list []Object (см. пример ниже) ✔️ Массив объектов каждой аптеки
list[i].id string ✔️ ID товара
list[i].name string ✔️ Название товара
list[i].price float64 ✔️ Цена товара. Если 0 - удалит остаток из Ютеки
list[i].available_count int ✔️ Количество товара в наличии. Если 0 - удалит остаток из Ютеки
list[i].barcodes []string ✔️ (хотя бы 1, если не заполнен list[i].protek) Список штрихкодов
list[i].protek []string ✔️ (хотя бы 1, если не заполнен list[i].barcodes) Список кодов протек
list[i].katren []string Список кодов катрен
list[i].egk []string Список кодов егк
list[i].eas []string Список кодов eas
list[i].asna []string Список кодов asna
list[i].series string Партия товара. Записи уникальны по связке id, store_id. Указанная партия записывается к этой связке. Если связка дублируется, то данные будут записаны из последней по порядку связки
list[i].brand_name string Название производителя товара. Используется для более точных привязок
list[i].country string Страна-производитель товара. Используется для более точных привязок

Не допускается использование названий полей, типов их содержания не по шаблону.

Добавление дополнительных полей для более специфичной логики обсуждается в индивидуальном порядке.

Пример POST запроса
POST https://integration.uteka.dev/exch/v1/partnerID/prices?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
    "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
    "list": [
        {
            "id": "D19D2582-FF73-4822-847C-DF95E79AC265",
            "barcodes": ["4607068880111", "4607068880112", "4607068880113"],
            "protek": ["123", "124", "125"],
            "katren": ["456", "457", "458"],
            "egk": ["789", "780", "781"],
            "name": "Терафлю Лимон пакетики 10 шт.",
            "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "price": 99.5,
            "available_count": 22,
            "series": "206301707",
            "brand_name": "Глаксосмиткляйн",
            "country": "Турция"
        },
        {
            "id": "774D6582-FF73-4822-847C-DF95E79AC265",
            "barcodes": ["4607045191944"],
            "egk": ["98576"],
            "name": "ТераФлю Лар Ментол, тбл д/рассасыв №20",
            "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "price": 184.5,
            "available_count": 12,
            "series": "33017072",
            "brand_name": "Новартис",
            "country": "Россия"
        }
    ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Частичная выгрузка остатков #

Частичная выгрузка подразумевает отправку PUT запроса. Частичная означает, что остатки могут быть добавлены или обновлены, но те элементы, которые не были указаны в частичной выгрузке, останутся без изменений. Для того, чтобы товар пропал с остатков в аптеке, необходимо в частичной выгрузке указать у этого остатка "available_count": 0

Особенно полезно, когда ассортимент аптеки большой, и вместо полной замены остатков Партнёр может присылать только обновления по ассортименту.

Частота частичной выгрузки остатков Партнёром может быть от 1 раза в 5 минут до 1 раза в 30 минут. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.

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

Технически полностью аналогична полной выгрузке остатков, за исключением:

  • Отправка методом PUT вместо POST
Пример PUT запроса
PUT https://integration.uteka.dev/exch/v1/partnerID/prices?mc=partnerMC
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
    "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
    "list": [
        {
            "id": "D19D2582-FF73-4822-847C-DF95E79AC265",
            "barcodes": ["4607068880111", "4607068880112", "4607068880113"],
            "protek": ["123", "124", "125"],
            "katren": ["456", "457", "458"],
            "egk": ["789", "780", "781"],
            "name": "Терафлю Лимон пакетики 10 шт.",
            "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "price": 99.5,
            "available_count": 22,
            "series": "206301707",
            "brand_name": "Глаксосмиткляйн",
            "country": "Турция"
        },
        {
            "id": "774D6582-FF73-4822-847C-DF95E79AC265",
            "barcodes": ["4607045191944"],
            "egk": ["98576"],
            "name": "ТераФлю Лар Ментол, тбл д/рассасыв №20",
            "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
            "price": 184.5,
            "available_count": 0,
            "series": "33017072",
            "brand_name": "Новартис",
            "country": "Россия"
        }
    ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Реализация обмена заказами #

Общая схема обмена заказами выглядит следующим образом:

graph TD
Partner[Партнёр] --> |GET| Orders[Получение списка заказов]
Partner --> |POST| OrdersExported[Обработка заказов]
Partner --> |POST| OrderStatuses[Смена статуса заказо]
Orders --> |/orders| Uteka[Ютека]
OrdersExported --> |/orders/exported| Uteka
OrderStatuses --> |/orders| Uteka

Алгоритм взаимодействия #

  1. Модуль Партнёра в фоновом режиме с частотой 5-10 минут выполняет запрос на получение списка заказов по конкретной аптеке (либо по всем, см. получение списка заказов). В списке заказы могут быть:
    1. Если заказы найдены, то переходим на п.2
    2. Если заказов нет, то через 5-10 минут снова выполняется п.1
  2. Модуль Партнёра сохраняет данные полученных заказов в БД и отправляет в Ютеку информацию о том, что полученные заказы обработаны. Это нужно для обновления статуса заказана на стороне Ютеки.
    1. Если заказ отменён со стороны Ютеки (Ютекой или пользователем), то после отправки запроса на обработку дальнейших действий не требуется. При получении информации об обработки, Ютека посчитает, что модуль Партнёра принял отмены, и переведёт заказ в конечный статус. Дальнейшие запросы по этому заказу не будут иметь эффекта, в запрашиваемом списке они больше не появятся.
    2. Если заказ новый, переходим к п.3.
  3. Провизор приступает к сборке заказа
    1. Если заказ может быть собран полностью, то переходим к п.4.
    2. Если заказ не может быть собран полностью, то выполняется отправка актуального состава заказа и соответствующего статуса в Ютеку. Ожидаемый статус Ожидает подтверждения. Подробнее о статусах в справочнике статусов. Корзина в запросе должна соответствовать полному составу заказа, который на данный момент может собрать аптека:
      1. По каждому товару необходимо передать идентификатор позиции.
      2. Количество товара, которое возможно собрать в заказ
    3. Далее Модуль Партнёра ожидает действия по частично собранному заказу, выполняя запросы на получение списка заказов из п.1.
      1. Если покупатель согласится с изменениями, то при очередном запросе на получение списка заказов данный заказ будет иметь обновленный состав и соответствующий статус Подтвержден. В этом случае сборка заказа выполняется и переходим к п.4.
      2. Если пользователь откажется от заказа, то данная информация также будет получена при очередном регулярном запросе на получение списка заказов, только статус будет Отменен. В этом случае Модуль Партнёра должен будет отправить лишь подтверждение, что отмена принята аналогично п.2 (обработка заказа).
  4. После сборки заказа модуль интеграции отправляет запрос с соответствующим статусом заказа Готов к выдаче. Однако, даже если заказ собран, он так же может появиться в списке заказов как отменённый, поэтому эти заказы так же нужно ожидать аналогично п.1.
  5. После выкупа или невыкупа заказа модуль Партнёра отправляет запрос в Ютеку с соответствующим статусом заказа Выдан. Процесс обработки заказа на этом успешно завершается.

Схематично алгоритм выглядит следующим образом:

flowchart TD
Start([Начало]) --> Creating[Пользователь создаёт заказ в Ютеке]
Creating --> Get[Модуль Партнёра получает список заказов Ютеки в фоновом режиме GET /orders]
Get --> Ack[Модуль Партнёра сохраняет информацию о полученных заказах и отправляет запросы с подтверждением этих заказов POST /orders/exported]
Ack --> Assembling[Сборка заказа]
Assembling --> CanAssembly{Аптека может собрать заказ?}
CanAssembly --> |Да| Cart{Все позиции заказа в наличии?}
CanAssembly -->|Нет| CancelledByPharmacy[Модуль Партнёра отправляет отмену заказа POST /orders]
CancelledByPharmacy --> Canceling
Cart -->|Да| Ready[Уведомление о готовности заказа POST /orders]
Cart -->|Нет| CartDiff[Отправка информации о недостающих позициях POST /orders]
CartDiff --> WaitingConfirmation[Ожидание ответа пользователя GET /orders]
WaitingConfirmation --> Confirmation{Пользователь согласился с новым составом заказа?}
Confirmation -->|Да| Assembling
Confirmation -->|Нет| CancelledByClient[Заказ отменяется]

Ready --> ReservationEndsAt[Запуск таймера на срок хранения заказа]
ReservationEndsAt --> Waiting[Ожидание пользователя и параллельно мониторинг появления отмены по этому заказу GET /orders]
Waiting --> IsCancelled{Заказ был в списке с отменённым статусом?}
IsCancelled -->|Нет| IsCameInTime{Пользователь пришёл за заказом вовремя?}
IsCancelled -->|Да| CancelledByClient[Модуль Партнёра отправляет запросы с подтверждением отмен этих заказов POST /orders/exported]
CancelledByClient --> Canceling
IsCameInTime -->|Да| Completion[Оплата и выдача заказа]
IsCameInTime -->|Нет| CancelledByDeadline[Модуль Партнёра отправляет отмену заказа POST /orders]
CancelledByDeadline --> Canceling[Заказ отменяется]

Completion --> P([Конец])
Canceling --> P

Список заказов #

В фоновом режиме с частотой 5-10 минут модуль Партнёра выполняет запрос на получение списка заказов по конкретной аптеке. Допускается возможность включить получение списка заказов сразу по всем аптекам (обсуждается на этапе интеграции).

Лимит выдачи: в ответе вернётся не больше 100 заказов, которые нужно обработать.

После обработки заказа, он пропадёт из выдачи. Под обработкой подразумевается, что модуль Партнёра получил информацию по заказам, сохранил её и отправил в Ютеку запрос на обработку этого заказа.

Для получения списка заказов на обработку нужно отправить запрос:

GET https://integration.uteka.dev/exch/v1/partnerID/orders?mc=partnerID&store_id=1234
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /orders - эндпоинт по заказам,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • store_id=1234 - аптека, по которой нужно получить список заказов. Не обязателен, если на этапе интеграции договорились о возможности получения заказов на обработку по всем аптекам целиком,
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Ответ будет состоять из следующих полей:

Поле Тип Обязательное Описание
list []Object (см. пример ниже) ✔️ Массив объектов каждого заказа
list[i].id int ✔️ ID заказа на Ютеке
list[i].external_id string ID заказа Партнёра (на тот случай, если используется своя собственная нумерация. Недоступен для статуса новый, т.к. для таких заказов Ютека ещё не знает про то, какой номер заказа будет присвоен в модуле Партнёра)
list[i].store_id string ✔️ ID аптеки
list[i].store string ✔️ Название аптеки
list[i].status_id int ✔️ Статус заказа. Может принимать следующие значения: Новый, Отменен, Подтвержден (изменение состава заказа подтверждено покупателем). Подробнее о статусах в справочнике статусов
list[i].fio string ✔️ ФИО покупателя
list[i].phone string ✔️ Телефон покупателя
list[i].email string Электронный адрес покупателя
list[i].region string Регион
list[i].city string Город
list[i].message string Сообщение к заказу
list[i].positions int ✔️ Кол-во уникальных позиций в корзине
list[i].amount float64 ✔️ Сумма заказа без учета скидки
list[i].discount float64 ✔️ Величина скидки
list[i].total_amount float64 ✔️ Сумма заказа с учетом скидки
list[i].creation_date int ✔️ Дата создания заказа в формате Unix-timestamp
list[i].items []Object (см. пример ниже) ✔️ Корзина заказа. Массив объектов каждой позиции в корзине
list[i].items[j].id int ✔️ Порядковый номер в корзине
list[i].items[j].item_id string ✔️ ID товара
list[i].items[j].item_name string Название товара (поле всегда будет пустое, т.к. есть ID товара)
list[i].items[j].price float64 ✔️ Цена товара, по которой покупатель совершил заказ (list[i].items[j].price * list[i].items[j].quantity)
list[i].items[j].quantity int ✔️ Количество товара
list[i].items[j].amount float64 ✔️ Сумма товара, по которой покупатель совершил заказ
list[i].items[j].series string Номер партии товара. Непустой, если выгружался в остатках
Пример ответа JSON
HTTP/2 200 OK
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
  "list": [
    {
      "id": 561841,
      "external_id": "",
      "region": "Московская область",
      "city": "Зеленоград",
      "store": "Колотушкина, 10Б",
      "store_id": "5F4774D6-1A09-4F92-A1DC-800269A30FE3",
      "message": "",
      "fio": "Иванов Иван Иванович",
      "phone": "79001234567",
      "email": "user@example.ru",
      "status_id": 1,
      "positions": 2,
      "amount": 483,
      "discount": 0,
      "total_amount": 483,
      "creation_date": 1603089774,
      "items": [
        {
          "id": 1,
          "item_id": "D19D2582-FF73-4822-847C-DF95E79AC265",
          "item_name": "",
          "price": 99.5,
          "quantity": 3,
          "amount": 298.5,
          "series": "206301707"
        },
        {
          "id": 1,
          "item_id": "774D6582-FF73-4822-847C-DF95E79AC265",
          "item_name": "",
          "price": 184.5,
          "quantity": 1,
          "amount": 184.5,
          "series": "33017072"
        }
      ]
    }
  ]
}

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Обработка заказов #

Под обработкой подразумевается, что модуль Партнёра получил информацию по заказам, сохранил её и отправил запрос в Ютеку, сообщающий о том, что заказ - принят. После этого запроса в случае успешного ответа заказ перестанет приходить в списке заказов до тех пор, пока заказ снова не изменится со стороны Ютеки (в случае отмены или подтверждения изменённой корзины пользователем).

Для обработки заказов нужно отправить запрос:

POST https://integration.uteka.dev/exch/v1/partnerID/orders/exported?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /orders/exported - эндпоинт для обработки заказов,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Тело запроса должно состоять из следующих полей:

Поле Тип Обязательное Описание
id []int ✔️ Массив IDs заказов в Ютеке

Не допускается использование названий полей, типов их содержания не по шаблону.

Добавление дополнительных полей для более специфичной логики обсуждается в индивидуальном порядке.

Пример запроса
POST https://integration.uteka.dev/exch/v1/partnerID/orders/exported?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
  "id": [561841, 71512]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Обновление статусов #

После того, как произошло обновление заказа со стороны Партнёра, например, аптека собрала/частично собрала/выдала/отменила заказ, это изменение необходимо отправить в Ютеку.

Для обновления статусов нужно отправить запрос:

POST https://integration.uteka.dev/exch/v1/partnerID/orders?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

, где:

  • https://integration.uteka.dev/exch/v1 - адрес для обмена с Ютекой,
  • /partnerID - специальный ID Партнёра в Ютеке (выдаётся со стороны Ютеки),
  • /orders - эндпоинт по заказам,
  • mc=partnerMC - специальный merchantCode Партнёра, обычно совпадает с partnerID (выдаётся со стороны Ютеки),
  • auth_token - заголовок с авторизационным токеном (токен выдаётся со стороны Ютеки),
  • X-Request-ID - специальный заголовок для трассировки запросов. При его наличии, в ответе на вернётся точно такой же заголовок с таким же значением. Если со стороны Партнёра этот заголовок не пришёл, Ютека сгенерирует и отправит его со своей стороны самостоятельно в ответе на запрос.

Тело запроса должно состоять из следующих полей:

Поле Тип Обязательное Описание
list []Object (см. пример ниже) ✔️ Массив объектов каждого заказа
list[i].id int ✔️ ID заказа в Ютеке
list[i].external_id string ID заказа в модуле Партнёра (на тот случай, если используется своя собственная нумерация)
list[i].status_id int ✔️ Статус заказа. Может принимать следующие значения: Комплектуется, Готов к выдаче, Выдан, Отменен, Ожидает подтверждения (изменение состава заказа). Подробнее о статусах в справочнике статусов
list[i].cancel_reason string Причина отмены
list[i].reservation_end_date int Дата окончания резерва в формате Unix-timestamp. Обрабатывается только для статуса Готов к выдаче
list[i].items []Object (см. пример ниже) ✔️Для статуса Ожидает подтверждения Актуальный состав (целиком) заказа, который аптека сможет собрать. Обрабатывается только для статуса Ожидает подтверждения
list[i].items[j].id string ✔️ ID товара
list[i].items[j].quantity int ✔️ Количество товара

Не допускается использование названий полей, типов их содержания не по шаблону.

Добавление дополнительных полей для более специфичной логики обсуждается в индивидуальном порядке.

Если был передан статус Ожидает подтверждения и Ютека зафиксировала изменения в корзине, модуль Партнёра должен дождаться одно из 2х действий:

  • Подтверждение нового состава заказа пользователем
  • Отмена заказа пользователем

Информацию модуль Партнёра получает путём фоновых запросов списка заказов.

Пример сборки заказа
POST https://integration.uteka.dev/exch/v1/partnerID/orders?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
  "list": [
    {
      "id": 561841,
      "external_id": "WL-9102",
      "status_id": 5,
      "cancel_reason": "",
      "reservation_end_date": 1603089774
    }
  ]
}
Пример запроса с изменённым составом
POST https://integration.uteka.dev/exch/v1/partnerID/orders?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
  "list": [
    {
      "id": 561841,
      "external_id": "WL-9102",
      "status_id": 7,
      "cancel_reason": "",
      "reservation_end_date": 1603089774,
      "items": [
        {
          "id": "D19D2582-FF73-4822-847C-DF95E79AC265",
          "quantity": 1
        },
        {
          "id": "774D6582-FF73-4822-847C-DF95E79AC265",
          "quantity": 1
        }
      ]
    }
  ]
}
Пример выдачи заказа
POST https://integration.uteka.dev/exch/v1/partnerID/orders?mc=partnerID
auth_token: token
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP

{
  "list": [
    {
      "id": 561841,
      "external_id": "WL-9102",
      "status_id": 2
    }
  ]
}

При успешном запросе вернётся ответ с пустым телом и статусом 200.

При ошибке будет отправлен соответствующий статус (см. Обработка ошибок)

Обработка ошибок #

Модуль Партнёра должен уметь обрабатывать ошибки, которые могут быть получены от Ютеки. Коды статусов ошибок, которые может вернуть Ютека:

  • 400. Означает, что Модуль Партнёра передал некорректные данные в запрос. Рекомендуется проверить корректность передаваемых данных в запросе Модулем Партнёра и исправить их.
  • 403. Означает, что Модулю Партнёра не разрешён доступ на запрашиваемый им адрес с такими переданными данными авторизации. Рекомендуется проверить правильность введённого токена авторизации и merchant code.
  • 404. Означает, что Модуль Партнёра отправил запрос на несуществующий адрес. Рекомендуется проверить правильность адреса, на который передаются запросы модулем Партнёра, и исправить его.
  • 429. Означает, что Модуль Партнёра отправляет запросы слишком часто, и текущий запрос был проигнорирован. Рекомендуется проверить частоту отправляемых запросов и снизить её.
  • 500. Означает, что произошла внутренняя ошибка сервиса Ютека. Рекомендуется предусмотреть возможность повторного запроса через некоторый промежуток времени и сообщить об ошибке сотрудникам Ютеки.
  • 502. Означает, что на момент запроса сервис Ютека недоступен. Рекомендуется предусмотреть возможность повторного запроса через некоторый промежуток времени.
  • 504. Означает, что сервис Ютека не успел обработать запрос. Рекомендуется проверить частоту отправляемых запросов и снизить её, если зафиксированы дубли и/или лишние запросы, сообщить об ошибке сотрудникам Ютеки.

Справочник статусов #

ID статусов всегда должен быть целочисленного типа (int) как для запросов в Ютеку, так и в теле ответа Ютеки.

ID Название Описание
1 Новый Автоматически присваивается при создании заказа в Ютеке
2 Выдан Присваивается в момент продажи товаров заказа
3 Отменен Используется в следующих случаях:
- Отмена пользователем (пропала потребность в заказе, и пользователь отменил через сайт или мобильное приложение).
- Отмена аптекой (истек срок брони, либо нет возможности собрать ни один товар из заказа, либо пользователь отказался выкупать заказ в аптеке).
- Отмена Ютекой (по просьбе пользователя, аптеки или Партнёра).
5 Готов к выдаче Присваивается в тот момент, когда заказ собран и готов к выдаче, пользователь может идти забирать заказ
6 Комплектуется Необязательный, присваивается в тот момент, когда информация о заказе попала в аптеку
7 Ожидает подтверждения Присваивается в случае, когда нет возможности собрать полный заказ, только часть заказа (в этом случае клиенту уходят уведомления, с предложением либо принять изменения, либо отменить заказ). Может использовать только модулем Партнёра. Ютека не может отправлять этот статус в модуль Партнёра
8 Подтвержден Присваивается, когда пользователь подтвердил предложенные изменения в составе заказа. Может использовать только Ютекой. Партнёр не может отправлять этот статус в Ютеку