🏥 Комплексная интеграция с Ютекой с нуля #
Данный документ описывает полную интеграцию аптечной сети (Партнёра) с Ютекой, включая все поддерживаемые типы заказов:
| Тип заказа | Описание | Источник остатков |
|---|---|---|
pickup |
Самовывоз из аптеки. Товар уже в аптеке, покупатель бронирует и забирает | Остатки аптек |
extendedPickup |
Самовывоз на следующий день. Товар на складе, доставляется в аптеку, покупатель забирает по прибытии | Остатки складов |
delivery |
Доставка на дом. Товар в аптеке, Партнёр организует доставку курьером | Остатки аптек |
courier |
Доставка курьером Ютеки. Товар в аптеке, курьер Ютеки забирает заказ из аптеки и доставляет покупателю | Остатки аптек |
Какие типы заказов необходимо поддержать Партнёру #
Не каждый Партнёр обязан поддерживать все типы заказов. Минимально необходимый тип — pickup. Остальные типы
подключаются по мере готовности инфраструктуры Партнёра.
| Тип заказа | Требования к инфраструктуре Партнёра |
|---|---|
pickup |
Аптеки с остатками |
extendedPickup |
Склады + аптеки с привязкой к складам + остатки на складах + даты доставки |
delivery |
Аптеки с остатками + собственная курьерская служба + эндпоинт расчёта доставки |
courier |
Аптеки с остатками (курьерскую службу предоставляет Ютека) |
Из чего состоит интеграция #
Интеграция с Ютекой состоит из двух глобальных процессов:
- синхронизация каталога (выгрузки)
- обмен заказами
graph TD A[Интеграция с Ютекой] --> B[Выгрузки] A --> C[Обмен заказами] B --> B1[Склады] B --> B2[Аптеки] B --> B3[Товары] B --> B4[Остатки аптек] B --> B5[Остатки складов] C --> C1[Создание заказа] C --> C2[Получение статусов] C --> C3[Отмена заказа] C --> C4[Расчёт доставки]
Выгрузки #
Это процесс получения Ютекой информации от Партнёра о:
- складах
- аптеках
- товарах
- остатках (аптек и/или складов)
Обмен заказами #
Это процесс, при котором Ютека:
- отправляет новые заказы в инфраструктуру Партнёра
- получает статусы из инфраструктуры Партнёра
- отправляет статусы в инфраструктуру Партнёра
- запрашивает расчёт доставки (для типа
delivery)
Зависимость выгрузок от типов заказов #
Не все выгрузки необходимы для каждого типа заказов:
| Выгрузка | pickup |
extendedPickup |
delivery |
courier |
|---|---|---|---|---|
| Склады | — | ✔️ | — | — |
| Аптеки (базовые поля) | ✔️ | ✔️ | ✔️ | ✔️ |
Аптеки (warehouseId) |
— | ✔️ | — | — |
Аптеки (deliveryDates) |
— | ✔️ | — | — |
| Товары | ✔️ | ✔️ | ✔️ | ✔️ |
| Остатки аптек | ✔️ | — | ✔️ | ✔️ |
| Остатки складов | — | ✔️ | — | — |
| Расчёт доставки (эндпоинт) | — | — | ✔️ | — |
Примеры реализации комплексной интеграции с нуля #
Выгрузки через протокол FTP #
graph TD Partner[Партнёр] --> Warehouses[Склады] Partner[Партнёр] --> Pharmacies[Аптеки] Partner[Партнёр] --> Products[Товары] Partner[Партнёр] --> Stocks[Остатки] Warehouses[Склады] --> FTP Pharmacies[Аптеки] --> FTP Products[Товары] --> FTP Stocks[Остатки] --> FTP FTP --> Uteka[Ютека] Uteka[Ютека] --> FTP
На данный момент мы поддерживаем следующие форматы файлов: JSON (приоритетнее), CSV, XML.
Файлы должны быть в кодировке UTF-8 или Windows-1251 без BOM.
Для CSV формата рекомендуемый разделитель должен быть: ;, но можно ,, если технически можете сформировать корректный csv. Формат файлов должен быть одинаковый для всех файлов в выгрузках.
Т.е. не должно быть такого, что аптеки находятся в json, товары - в xml, а остатки - в csv.
Для всех процессов выбирается только 1 формат.
В случае, если у Партнера нет своего FTP сервера, Ютека может предоставить доступ на свой FTP сервер.
Аптеки FTP #
Необходимо для: всех типов заказов
Частота выгрузки аптек должна быть от 1 раза в 3 часа до 1 раза в сутки. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.
Для выгрузки аптек в корень FTP выгружается файл pharmacies.json
(или csv/xml в зависимости от выбранного формата).
Файл должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание | Комментарий |
|---|---|---|---|---|
id или pharmacyId |
string | ✔️ | ID аптеки | |
title |
string | ✔️ | Название аптеки | |
warehouseId |
string | extendedPickup ✔️ | ID склада из выгрузок складов | Обязательно для extendedPickup. Одна аптека может принимать остатки только с одного склада. |
region |
string | Регион местонахождения аптеки | Не обязателен, если указан в поле address |
|
city |
string | ️ | Город местонахождения аптеки | Не обязателен, если указан в поле address |
address |
string | ✔️ | Адрес местонахождения аптеки полностью (Область, населённый пункт, улица, дом). Без почтового индекса. | С точностью до дома. Если возможно, без дополнительных комментариев, вида пом.75843б, за магазином канцтоваров. Регион и город не обязательно указывать тут, если они передаются в полях region и city |
phone |
string | ✔️ | Телефон в международном формате (+71234567890) |
Если это городской номер телефона, то с кодом города (например для Новосибирска это +7(383)000‒00‒00) |
workingHours |
Object или string (см. пример ниже) | ✔️ | Часы работы в формате | |
deliveryDates |
Object (см. пример ниже) | extendedPickup ✔️ | Даты доставки остатков со склада warehouseId |
Обязательно для extendedPickup. Ежедневное обновление на ближайшие 2-3 дня |
location |
string | ✔️ | GPS координаты Latitude,Longitude |
Например, 59.939032,30.315826. При подстановке в гугл/яндекс/другие карты должна показываться точка на аптеку. По координатам проверяется адрес |
email |
string | Email аптеки для связи | Мы можем присылать email оповещения в аптеку при поступлении новых или отмене заказов |
Возможно добавление дополнительных полей для более специфичной логики, например, ограничение доставки некоторых остатков в некоторые аптеки (например, по признаку наличия холодильника в аптеке для вакцин) или по другим условиям.
Обсуждается в индивидуальном порядке.
График работы #
График работы ожидается в виде JSON объекта, разбитый по дням недели, либо в строковом типе.
Для JSON-объекта:
- Дни недели пронумерованы от 1 до 7, где 1 - понедельник, 7 - воскресенье.
open- время открытия аптеки,close- время закрытия. Относительно этих значений присылаются уведомления пользователям, и считаются регламенты (например, “будет собран до”).- Если аптека не работает в какой-то день (например, в воскресенье), то день недели пропускается, либо значения
openиcloseзаполняются пустыми строками:"open":"","close":"". - Если аптека круглосуточная, то
openиcloseприсылается со значением 00:00:"open":"00:00","close":"00:00" - Разработчикам: нужно учитывать, что JSON формат относится только к одному полю из АПИ, а поле (колонка в выгрузке) должна быть строкой в соответствующем формате (JSON, CSV или XML, как договорились). Поэтому нужно не забывать про экранирование данных при подстановке в выгрузку.
Пример JSON объекта workingHours
"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": "" }
}
Для строкового типа:
Это может быть тот же самый json-объект, но завёрнутый в строку.
Пример строкового 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, вс выходной"
Для круглосуточной аптеки:
"пн-вс круглосуточно"
Даты доставки (deliveryDates) #
Необходимо для:
extendedPickup
Даты доставки описывают, когда остатки со склада warehouseId будут доставлены в аптеку. Объект со следующими полями:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
deliveryDate |
string | ✔️ | Ближайшая дата доставки остатков в аптеку |
deliveryDateExpires |
string | Крайняя дата и время оформления заказа для попадания в ближайшую поставку. Опционально — на случай, если deliveryDate неактуальна в течение всего дня |
|
nextDeliveryDate |
string | Следующая дата доставки после ближайшей |
Формат дат: YYYY-MM-DDTHH:mm:ss
Рекомендуется обновлять даты доставки ежедневно.
Пример deliveryDates — все поля
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00",
"deliveryDateExpires": "2024-04-26T20:30:00",
"nextDeliveryDate": "2024-04-28T00:00:00"
}
Здесь: ближайшая доставка — 27 апреля. Чтобы попасть в эту доставку, заказ нужно оформить до 26 апреля 20:30. Следующая доставка — 28 апреля.
Пример deliveryDates — только обязательное поле
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00"
}
Если deliveryDateExpires не указан, подразумевается, что deliveryDate актуальна в течение всего дня.
Примеры файлов аптек целиком:
Пример JSON с часами работы в виде объекта
[
{
"pharmacyId": "228",
"title": "Аптечный пункт №1",
"address": "Краснодарский край, г Тихорецк, ул Октябрьская, д 101",
"phone": "8-800-228-22-88",
"warehouseId": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00",
"deliveryDateExpires": "2024-04-26T20:30:00",
"nextDeliveryDate": "2024-04-29T00:00:00"
},
"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": "" }
},
"location": "45.868670,40.144246",
"email": "test@test.com"
},
{
"pharmacyId": "100",
"title": "Аптека на Ленина",
"address": "г Москва, ул Ленина, д 15",
"phone": "+74951234567",
"workingHours": {
"1": { "open": "08:00", "close": "21:00" },
"2": { "open": "08:00", "close": "21:00" },
"3": { "open": "08:00", "close": "21:00" },
"4": { "open": "08:00", "close": "21:00" },
"5": { "open": "08:00", "close": "21:00" },
"6": { "open": "09:00", "close": "18:00" },
"7": { "open": "10:00", "close": "16:00" }
},
"location": "55.753215,37.622504",
"email": "apteka100@partner.ru"
}
]
Аптека 228 — с привязкой к складу и датами доставки (для extendedPickup).
Аптека 100 — без привязки к складу (для pickup, delivery, courier).
Пример JSON с часами работы в виде строки
[
{
"pharmacyId": "228",
"title": "Аптечный пункт №1",
"address": "Краснодарский край, г Тихорецк, ул Октябрьская, д 101",
"phone": "8-800-228-22-88",
"warehouseId": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00",
"deliveryDateExpires": "2024-04-26T20:30:00",
"nextDeliveryDate": "2024-04-29T00:00:00"
},
"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\":\"\"}}",
"location": "45.868670,40.144246",
"email": "test@test.com"
},
{
"pharmacyId": "100",
"title": "Аптека на Ленина",
"address": "г Москва, ул Ленина, д 15",
"phone": "+74951234567",
"workingHours": "пн-пт 08:00-21:00, сб 09:00-18:00, вс 10:00-16:00",
"location": "55.753215,37.622504",
"email": "apteka100@partner.ru"
}
]
Пример CSV
pharmacyId;title;address;phone;warehouseId;deliveryDates;workingHours;location;email
228;Аптечный пункт №1;Краснодарский край, г Тихорецк, ул Октябрьская, д 101;8-800-228-22-88;20247701-bf4b-11ed-812f-00e0ed9e2e92;"{""deliveryDate"": ""2024-04-27T00:00:00"", ""deliveryDateExpires"": ""2024-04-26T20:30:00"", ""nextDeliveryDate"": ""2024-04-29T00:00:00""}";"{""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"":""""}}";45.868670,40.144246;test@test.com
100;Аптека на Ленина;г Москва, ул Ленина, д 15;+74951234567;;;"пн-пт 08:00-21:00, сб 09:00-18:00, вс 10:00-16:00";55.753215,37.622504;apteka100@partner.ru
Пример XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<pharmacyId>228</pharmacyId>
<title>Аптечный пункт №1</title>
<address>Краснодарский край, г Тихорецк, ул Октябрьская, д 101</address>
<phone>8-800-228-22-88</phone>
<warehouseId>20247701-bf4b-11ed-812f-00e0ed9e2e92</warehouseId>
<deliveryDates>
<deliveryDate>2024-04-27T00:00:00</deliveryDate>
<deliveryDateExpires>2024-04-26T20:30:00</deliveryDateExpires>
<nextDeliveryDate>2024-04-29T00:00:00</nextDeliveryDate>
</deliveryDates>
<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>
<location>45.868670,40.144246</location>
<email>test@test.com</email>
</item>
<item>
<pharmacyId>100</pharmacyId>
<title>Аптека на Ленина</title>
<address>г Москва, ул Ленина, д 15</address>
<phone>+74951234567</phone>
<workingHours>
{"1":{"open":"08:00","close":"21:00"},"2":{"open":"08:00","close":"21:00"},"3":{"open":"08:00","close":"21:00"},"4":{"open":"08:00","close":"21:00"},"5":{"open":"08:00","close":"21:00"},"6":{"open":"09:00","close":"18:00"},"7":{"open":"10:00","close":"16:00"}}
</workingHours>
<location>55.753215,37.622504</location>
<email>apteka100@partner.ru</email>
</item>
</items>
Склады FTP #
Необходимо для:
extendedPickup
Частота выгрузки складов должна быть до 1 раза в сутки. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.
Для выгрузки складов в корень FTP выгружается файл region.json
(или csv/xml в зависимости от выбранного формата).
Файл должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание | Комментарий |
|---|---|---|---|---|
id |
string | ✔️ | ID склада | Нужен для привязки к аптекам, куда будут доставляться остатки. Может содержать название региона. |
title |
string | ✔️ | Название склада | Краткое описание. Может содержать название региона. |
Важно! На один регион может быть несколько складов, один склад может быть на несколько регионов.
Пример JSON
[
{
"id": "msc",
"title": "Москва"
},
{
"id": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"title": "Краснодарский край \"Парк А\""
}
]
Пример CSV
id;title
msc;Москва
20247701-bf4b-11ed-812f-00e0ed9e2e92;Краснодарский край "Парк А"
Пример XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<id>msc</id>
<title>Москва</title>
</item>
<item>
<id>20247701-bf4b-11ed-812f-00e0ed9e2e92</id>
<title>Краснодарский край "Парк А"</title>
</item>
</items>
Товары FTP #
Необходимо для: всех типов заказов
Частота выгрузки товаров должна быть от 1 раза в 3 часа до 1 раза в сутки. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.
Для выгрузки товаров в корень FTP выгружается файл products.json
(или csv/xml в зависимости от выбранного формата).
Файл должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
id или productId |
string | ✔️ | ID товара |
barcode или barcodes |
string | ✔️ | Штрихкод. Если несколько, перечислять через ,, например: 4250369505166,2250369505188 |
title |
string | ✔️ | Название |
vendor |
string | ✔️ | Производитель |
country |
string | ✔️ | Страна |
egk |
string | код egk значительно ускорит привязку товаров к нашему каталогу. Если несколько, то перечислять через ,, например: 161421,161369 |
|
rls |
string | код rls значительно ускорит привязку товаров к нашему каталогу. Если несколько, то перечислять через ,, например: 82531,82592 |
|
katren |
string | код katren значительно ускорит привязку товаров к нашему каталогу. Если несколько, то перечислять через ,, например: 55079077,55079091 |
|
protek |
string | код protek значительно ускорит привязку товаров к нашему каталогу. Если несколько, то перечислять через ,, например: 1-218661,218661,70218661 |
Пример JSON
[
{
"productId": "228",
"barcode": "4250369505166",
"title": "Лекарственные средства АСПИРИН ЭКСПРЕСС табл. шип. 500мг N12",
"vendor": "БАЙЕР",
"country": "ГЕРМАНИЯ",
"katren": "55079077",
"protek": "1-218661,218661,70218661",
"egk": "100986"
},
{
"productId": "229",
"barcode": "4630000891184",
"title": "Травы, чаи прочие ГАЛЕГИ ТРАВА (Козлятник) 50г N1",
"vendor": "Камелия-ЛТ",
"country": "РФ",
"katren": "400707176"
}
]
Пример CSV
productId;barcode;title;vendor;country;rls;katren;protek;egk
228;4250369505166;Лекарственные средства АСПИРИН ЭКСПРЕСС табл. шип. 500мг N12;БАЙЕР;ГЕРМАНИЯ;;55079077;1-218661,218661,70218661;100986
229;4630000891184;Травы, чаи прочие ГАЛЕГИ ТРАВА (Козлятник) 50г N1;Камелия-ЛТ;РФ;;400707176;;
Пример XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<productId>228</productId>
<barcode>4250369505166</barcode>
<title>Лекарственные средства АСПИРИН ЭКСПРЕСС табл. шип. 500мг N12</title>
<vendor>БАЙЕР</vendor>
<country>ГЕРМАНИЯ</country>
<katren>55079077</katren>
<protek>1-218661,218661,70218661</protek>
<egk>100986</egk>
</item>
<item>
<productId>229</productId>
<barcode>4630000891184</barcode>
<title>Травы, чаи прочие ГАЛЕГИ ТРАВА (Козлятник) 50г N1</title>
<vendor>Камелия-ЛТ</vendor>
<country>РФ</country>
<katren>400707176</katren>
</item>
</items>
Остатки FTP #
Частота выгрузки остатков должна быть не чаще 1 раза в 15 минут, не реже 1 раза в час. Нестандартная частота выгрузок обсуждается в индивидуальном порядке.
Для выгрузки остатков в директорию /stocks FTP выгружается файл stocks_<pharmacyId>.json (или csv/xml в зависимости от
выбранного формата), где суффикс названия соответствует ID аптеки.
Для остатков складов (extendedPickup) — аналогично, файл stocks_<warehouseId>.json, где суффикс — ID склада.
Файл должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
productId |
string | ✔️ | ID товара из выгрузок товаров |
warehouseId |
string | ID склада из выгрузок складов. Обязателен для остатков склада (extendedPickup). Не обязательный, т.к. мы можем достать его из названия файла |
|
price |
float64 или string или int (если стоимость в копейках) | ✔️ | Цена |
quantity |
int или string | ✔️ | Количество |
partNumber или consignment |
string | Номер партии | |
expirationDate |
string | Срок годности в формате: 2006-01-02T15:04:05 или 2006-01-02 |
|
maxQuantity |
int или string | Максимально доступное количество этой позиции на единицу заказа | |
isDelivery |
bool | Доступен ли данный остаток для доставки (только для типа delivery). По умолчанию true |
Остатки с quantity = 0 должны передаваться — Ютека убирает их на своей стороне.
Возможно добавление дополнительных полей для более специфичной логики, например, ограничение доставки некоторых остатков в некоторые аптеки (например, по признаку наличия холодильника в аптеке для вакцин) или по другим условиям.
Пример JSON
[
{
"product_id": "1234",
"price": 51.0,
"quantity": 914,
"partNumber": "20250201228228",
"expirationDate": "2040-01-02T00:00:00",
"maxQuantity": 10
},
{
"product_id": "1235",
"price": 78.0,
"quantity": 1324,
"partNumber": "20250201228114",
"expirationDate": "2045-06-02T00:00:00"
},
{
"product_id": "1236",
"price": 58.0,
"quantity": 730,
"partNumber": "20250201228553",
"expirationDate": "2042-09-01T00:00:00"
}
]
Пример CSV
product_id;price;quantity;partNumber;expirationDate;maxQuantity
1234;51.0;914;20250201228228;2040-01-02T00:00:00;10
1235;78.0;1324;20250201228114;2045-06-02T00:00:00;
1236;58.0;730;20250201228553;2042-09-01T00:00:00;
Пример XML
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<product_id>1234</product_id>
<price>51.0</price>
<quantity>914</quantity>
<partNumber>20250201228228</partNumber>
<expirationDate>2040-01-02T00:00:00</expirationDate>
<maxQuantity>10</maxQuantity>
</item>
<item>
<product_id>1235</product_id>
<price>78.0</price>
<quantity>1324</quantity>
<partNumber>20250201228114</partNumber>
<expirationDate>2045-06-02T00:00:00</expirationDate>
</item>
<item>
<product_id>1236</product_id>
<price>58.0</price>
<quantity>730</quantity>
<partNumber>20250201228553</partNumber>
<expirationDate>2042-09-01T00:00:00</expirationDate>
</item>
</items>
Обмен заказами через протокол FTP (менее предпочтительный ❌) #
graph TD Partner[Партнёр] -->|Статус заказа| FTP Uteka[Ютека] -->|Новый заказ| FTP Uteka -->|Отмена заказа| FTP
Этот способ обмена заказами устарел и поддерживается минимально. Для малейших отклонений от схемы и различной кастомизации понадобятся отдельные доработки и тестирование кастомизированной схемы обмена заказами. Мы не рекомендуем его использовать в обмене заказами.
Важно! В данном способе обмена заказами невозможна реализация тайм-слотов доставки, поэтому тип заказа delivery
не поддерживается через FTP. Для заказов с доставкой курьером Партнёра необходимо использовать
обмен заказами через HTTP REST, где реализован эндпоинт
расчёта доставки.
Вместо этого способа, лучше использовать процесс обмена заказами через HTTP REST интерфейса для обработки заказов.
Это примечание не относится к процессу выгрузок через протокол FTP
Раскрыть
Ютека будет загружать свои запросы в директорию /orders/outgoing. А аптечная сеть должна выгружать статусы по заказам
в директорию /orders/incoming.
Передача запроса считается успешной, когда файл будет удалён с ftp-сервера получателем.
ID заказов — внутренние ID Ютеки.
Создание заказа FTP #
После того, как пользователь создал заказ, Ютека передаёт его в интеграцию через FTP в виде файла.
На данный момент поддерживается обмен файлами в формате .json, и .xml. Формат файлов должен быть одинаковый для всех
процессов.
Имя файла соответствует номеру заказа + расширение (.json, .xml) в кодировке UTF-8 без BOM.
Например, файл с запросом на создание заказа с ID=1234 будет лежать в папке /orders/outgoing и называться
1234.json, полный путь: /orders/outgoing/1234.json
Файл будет состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
orderId |
int | ✔️ | ID заказа Ютеки |
warehouse |
string | extendedPickup ✔️ | ID склада из выгрузок складов, из которого нужно отправить препараты в аптеку |
pharmacy |
string | ✔️ | ID аптеки из выгрузок аптек, куда нужно отправить препараты из склада |
amount |
float64 | ✔️ | Полная стоимость корзины в рублях (сумма всех item.price), округлённая до копеек |
name |
string | ✔️ | Имя покупателя |
phone |
string | ✔️ | Телефон покупателя в международном формате без 7, например, 9181231234 |
items |
[]Object | ✔️ | Корзина |
items.productId |
string | ✔️ | ID товара из выгрузок товаров. Если есть номера партий, в это поле можем подставлять его |
items.quantity |
int | ✔️ | Количество текущего ID товара в корзине |
items.price |
float64 | ✔️ | Цена за единицу текущего ID товара |
Кастомизация полей, их содержания и типов обговаривается с Партнёром в индивидуальном порядке.
Пример JSON
{
"orderId": 1234,
"pharmacy": "228",
"warehouse": "msc",
"items": [
{
"productId": "1234",
"quantity": 2,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 78
}
],
"amount": 180,
"name": "Иванов Иван Иванович",
"phone": "9181231234"
}
Пример XML
<order>
<OrderID>1234</OrderID>
<Pharmacy>228</Pharmacy>
<Warehouse>msc</Warehouse>
<Items>
<Item>
<ProductID>1234</ProductID>
<Quantity>2</Quantity>
<Price>51</Price>
</Item>
<Item>
<ProductID>1235</ProductID>
<Quantity>1</Quantity>
<Price>78</Price>
</Item>
</Items>
<Amount>180</Amount>
<Name>Иванов Иван Иванович</Name>
<Phone>9181231234</Phone>
</order>
Получение статусов по заказам от Партнёра FTP #
Для получения статусов, Партнёр должен выгрузить файлы со статусами по каждому заказу в директорию /orders/incoming.
Имя файла соответствует номеру заказа + расширение (.json, .xml) в кодировке UTF-8 без BOM.
Например, файл со статусом заказа 1234 будет лежать в папке /orders/incoming и называться
status_1234.json, полный путь: orders/incoming/status_1234.json
Файл должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
orderId |
string | ✔️ | ID заказа Ютеки |
status |
string | ✔️ | Статус заказа |
comment |
string | Причина отмены (например, “нет на складе” или “отказ покупателя”) |
Возможные статусы заказов:
| Статус | Описание |
|---|---|
approved |
Подтверждён. Интеграция взяла заказ в обработку. Фармацевт в аптеке увидел заказ или заказ дошёл до аптеки и сохранился со стороны аптечной сети. |
ready |
Готов к выдаче. Интеграция доставила корзину в аптеку, собрала заказ и готова выдать покупателю. Покупатель может идти в аптеку |
completed |
Продан. Покупатель пришёл в аптеку и выкупил корзину |
cancelled |
Отменён аптекой. Аптека не смогла на каком-то из этапов собрать заказ и отменила его |
Заказ подтверждён JSON
{
"orderId": "1234",
"status": "approved"
}
Заказ подтверждён XML
<Status>
<OrderID>1234</OrderID>
<Status>approved</Status>
</Status>
Заказ готов к выдаче JSON
{
"orderId": "1234",
"status": "ready"
}
Заказ готов к выдаче XML
<Status>
<OrderID>1234</OrderID>
<Status>ready</Status>
</Status>
Заказ продан JSON
{
"orderId": "1234",
"status": "completed"
}
Заказ продан XML
<Status>
<OrderID>1234</OrderID>
<Status>completed</Status>
</Status>
Заказ отменён JSON
{
"orderId": "1234",
"status": "cancelled",
"comment": "Нет на складе"
}
Заказ отменён XML
<Status>
<OrderID>1234</OrderID>
<Status>cancelled</Status>
<Comment>Нет на складе</Comment>
</Status>
Ограничения обмена заказами через FTP #
- Частичная сборка корзины — не предусмотрена в данном способе обмена заказами.
- Тип заказа
delivery— не поддерживается, т.к. невозможна реализация тайм-слотов доставки. Дляdeliveryнеобходим эндпоинт расчёта доставки, который доступен только в REST-варианте.
Для поддержки этих возможностей лучше рассмотреть использование процесса обмена заказами через HTTP REST интерфейса для обработки заказов.
Отмена заказа FTP #
Ютека будет выгружать отмены заказов так же в директорию /orders/outgoing.
ID заказов — внутренние ID Ютеки.
Имя файла соответствует status_<id>.json (или .xml).
Например, файл с запросом на отмену заказа с номером 1234 будет лежать в папке /orders/outgoing и называться
status_1234.json, полный путь: /orders/outgoing/status_1234.json
Файл будет состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
orderId |
string | ✔️ | ID заказа Ютеки |
status |
string | ✔️ | Статус заказа |
Заказ отменён со стороны Ютеки JSON
{
"orderId": "1234",
"status": "cancelled"
}
Заказ отменён со стороны Ютеки XML
<Status>
<OrderID>1234</OrderID>
<Status>cancelled</Status>
</Status>
Подтверждение выполнения заказа FTP (courier) #
Необходимо для:
courier(опционально)
Если Партнёру нужно от Ютеки явное подтверждение о том, что заказ доставлен покупателю, для таких случаев Ютека может отправить Партнёру запрос с подтверждением выполнения заказа после того, как курьер вручит заказ.
Механизм аналогичен отмене заказа FTP — Ютека выгружает файл status_<id>.json (или .xml)
в директорию /orders/outgoing, но вместо статуса отмены передаётся статус выполнения.
Партнёру необходимо предоставить Ютеке код статуса, который означает «заказ выполнен» в его системе.
Заказ выполнен (подтверждение) JSON
{
"orderId": "1234",
"status": "<код статуса выполнения, предоставленный Партнёром>"
}
Заказ выполнен (подтверждение) XML
<Status>
<OrderID>1234</OrderID>
<Status><код статуса выполнения, предоставленный Партнёром></Status>
</Status>
Выгрузки через HTTP REST интерфейс #
graph TD Uteka[Ютека] -->|GET /warehouses| Partner[REST API Партнёра] Uteka -->|GET /pharmacies| Partner Uteka -->|GET /products| Partner Uteka -->|GET /stocks| Partner
Этот раздел описывает процесс интеграции Ютеки и Партнера через реализацию на стороне Партнера HTTP REST интерфейса для процесса выгрузок. Партнёру нужно реализовать на своей стороне HTTP REST интерфейс для получения Ютекой информации о:
Общие требования к REST-эндпоинтам #
Для всех эндпоинтов действуют следующие правила:
X-Request-ID— специальный заголовок для трассировки запросов. При его наличии, в ответе на запрос должен быть точно такой же заголовок с таким же значением. Если со стороны Ютеки этот заголовок не пришёл, Партнёру нужно сгенерировать и отправить его со своей стороны самостоятельно.- Допускается возможность авторизации разными способами, например,
Basic,Bearerи др. - Допускается наличие у партнёра отдельного авторизационного эндпоинта, который будет возвращать временный токен доступа, а Ютека будет обновлять его по истечению срока его жизни.
- Допускается использование комбинированных подходов, дополнительных заголовков и др.
- Допускается реализация пагинации, но обсуждается в индивидуальном порядке с Партнёром.
- Допускается использование названий полей, типов их содержания и не по шаблону (обговаривается с Партнёром в индивидуальном порядке).
Аптеки REST #
Необходимо для: всех типов заказов
Частота обновления аптек в системе Партнёра должна быть от 1 раза в 3 часа до 1 раза в сутки.
Для выгрузки аптек со стороны Партнёра должен быть реализован эндпоинт, принимающий GET-запросы, и возвращающий
список аптек с content-type: application/json (или xml, на усмотрение Партнёра).
Ютека будет отправлять запросы, например:
GET https://partner-host.ru/pharmacies
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание | Комментарий |
|---|---|---|---|---|
id или pharmacyId |
string | ✔️ | ID аптеки | |
title |
string | ✔️ | Название аптеки | |
warehouseId |
string | extendedPickup ✔️ | ID склада из выгрузок складов | Обязательно для extendedPickup |
region |
string | Регион местонахождения аптеки | Не обязателен, если указан в поле address |
|
city |
string | ️ | Город местонахождения аптеки | Не обязателен, если указан в поле address |
address |
string | ✔️ | Адрес местонахождения аптеки полностью (Область, населённый пункт, улица, дом). Без почтового индекса. | С точностью до дома. Если возможно, без дополнительных комментариев |
phone |
string | ✔️ | Телефон в международном формате (+71234567890) |
Если это городской номер телефона, то с кодом города |
workingHours |
Object или string (см. пример ниже) | ✔️ | Часы работы в формате | |
deliveryDates |
Object (см. пример ниже) | extendedPickup ✔️ | Даты доставки остатков со склада warehouseId |
Обязательно для extendedPickup |
location |
string | ✔️ | GPS координаты Latitude,Longitude |
Например, 59.939032,30.315826. При подстановке в карты должна показываться точка на аптеку |
email |
string | Email аптеки для связи | Мы можем присылать email оповещения в аптеку при поступлении новых или отмене заказов |
Возможно добавление дополнительных полей для более специфичной логики, например, ограничение доставки некоторых остатков в некоторые аптеки (например, по признаку наличия холодильника в аптеке для вакцин) или по другим условиям.
Обсуждается в индивидуальном порядке.
Пример ответа JSON с часами работы в виде объекта
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
[
{
"pharmacyId": "228",
"title": "Аптечный пункт №1",
"address": "Краснодарский край, г Тихорецк, ул Октябрьская, д 101",
"phone": "8-800-228-22-88",
"warehouseId": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00",
"deliveryDateExpires": "2024-04-26T20:30:00",
"nextDeliveryDate": "2024-04-29T00:00:00"
},
"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": "" }
},
"location": "45.868670,40.144246",
"email": "test@test.com"
},
{
"pharmacyId": "100",
"title": "Аптека на Ленина",
"address": "г Москва, ул Ленина, д 15",
"phone": "+74951234567",
"workingHours": {
"1": { "open": "08:00", "close": "21:00" },
"2": { "open": "08:00", "close": "21:00" },
"3": { "open": "08:00", "close": "21:00" },
"4": { "open": "08:00", "close": "21:00" },
"5": { "open": "08:00", "close": "21:00" },
"6": { "open": "09:00", "close": "18:00" },
"7": { "open": "10:00", "close": "16:00" }
},
"location": "55.753215,37.622504",
"email": "apteka100@partner.ru"
}
]
Аптека 228 — с привязкой к складу и датами доставки (для extendedPickup).
Аптека 100 — без привязки к складу (для pickup, delivery, courier).
Пример ответа JSON с часами работы в виде строки
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
[
{
"pharmacyId": "228",
"title": "Аптечный пункт №1",
"address": "Краснодарский край, г Тихорецк, ул Октябрьская, д 101",
"phone": "8-800-228-22-88",
"warehouseId": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"deliveryDates": {
"deliveryDate": "2024-04-27T00:00:00",
"deliveryDateExpires": "2024-04-26T20:30:00",
"nextDeliveryDate": "2024-04-29T00:00:00"
},
"workingHours": "пн-пт 08:00-20:00, сб 10:00-17:30, вс выходной",
"location": "45.868670,40.144246",
"email": "test@test.com"
},
{
"pharmacyId": "100",
"title": "Аптека на Ленина",
"address": "г Москва, ул Ленина, д 15",
"phone": "+74951234567",
"workingHours": "пн-пт 08:00-21:00, сб 09:00-18:00, вс 10:00-16:00",
"location": "55.753215,37.622504",
"email": "apteka100@partner.ru"
}
]
Пример ответа XML
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<pharmacyId>228</pharmacyId>
<title>Аптечный пункт №1</title>
<address>Краснодарский край, г Тихорецк, ул Октябрьская, д 101</address>
<phone>8-800-228-22-88</phone>
<warehouseId>20247701-bf4b-11ed-812f-00e0ed9e2e92</warehouseId>
<deliveryDates>
<deliveryDate>2024-04-27T00:00:00</deliveryDate>
<deliveryDateExpires>2024-04-26T20:30:00</deliveryDateExpires>
<nextDeliveryDate>2024-04-29T00:00:00</nextDeliveryDate>
</deliveryDates>
<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>
<location>45.868670,40.144246</location>
<email>test@test.com</email>
</item>
<item>
<pharmacyId>100</pharmacyId>
<title>Аптека на Ленина</title>
<address>г Москва, ул Ленина, д 15</address>
<phone>+74951234567</phone>
<workingHours>
{"1":{"open":"08:00","close":"21:00"},"2":{"open":"08:00","close":"21:00"},"3":{"open":"08:00","close":"21:00"},"4":{"open":"08:00","close":"21:00"},"5":{"open":"08:00","close":"21:00"},"6":{"open":"09:00","close":"18:00"},"7":{"open":"10:00","close":"16:00"}}
</workingHours>
<location>55.753215,37.622504</location>
<email>apteka100@partner.ru</email>
</item>
</items>
Склады REST #
Необходимо для:
extendedPickup
Частота обновления складов в системе Партнёра должна быть до 1 раза в сутки.
Для выгрузки складов со стороны Партнёра должен быть реализован эндпоинт, принимающий GET-запросы, и возвращающий
список складов с content-type: application/json (или xml, на усмотрение Партнёра).
Ютека будет отправлять запросы, например:
GET https://partner-host.ru/warehouses
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание | Комментарий |
|---|---|---|---|---|
id |
string | ✔️ | ID склада | Нужен для привязки к аптекам, куда будут доставляться остатки. Может содержать название региона. |
title |
string | ✔️ | Название склада | Краткое описание. Может содержать название региона. |
Пример ответа JSON
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
[
{
"id": "msc",
"title": "Москва"
},
{
"id": "20247701-bf4b-11ed-812f-00e0ed9e2e92",
"title": "Краснодарский край \"Парк А\""
}
]
Пример ответа XML
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<id>msc</id>
<title>Москва</title>
</item>
<item>
<id>20247701-bf4b-11ed-812f-00e0ed9e2e92</id>
<title>Краснодарский край "Парк А"</title>
</item>
</items>
Товары REST #
Необходимо для: всех типов заказов
Частота обновления товаров в системе Партнёра должна быть от 1 раза в 3 часа до 1 раза в сутки.
Для выгрузки товаров со стороны Партнёра должен быть реализован эндпоинт, принимающий GET-запросы, и возвращающий
список товаров с content-type: application/json (или xml, на усмотрение Партнёра).
Ютека будет отправлять запросы, например:
GET https://partner-host.ru/products
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
id или productId |
string | ✔️ | ID товара |
barcode или barcodes |
string | ✔️ | Штрихкод. Если несколько, перечислять через ,, например: 4250369505166,2250369505188 |
title |
string | ✔️ | Название |
vendor |
string | ✔️ | Производитель |
country |
string | ✔️ | Страна |
egk |
string | код egk значительно ускорит привязку товаров к нашему каталогу |
|
rls |
string | код rls значительно ускорит привязку товаров к нашему каталогу |
|
katren |
string | код katren значительно ускорит привязку товаров к нашему каталогу |
|
protek |
string | код protek значительно ускорит привязку товаров к нашему каталогу |
Пример ответа JSON
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
[
{
"productId": "228",
"barcode": "4250369505166",
"title": "Лекарственные средства АСПИРИН ЭКСПРЕСС табл. шип. 500мг N12",
"vendor": "БАЙЕР",
"country": "ГЕРМАНИЯ",
"katren": "55079077",
"protek": "1-218661,218661,70218661",
"egk": "100986"
},
{
"productId": "229",
"barcode": "4630000891184",
"title": "Травы, чаи прочие ГАЛЕГИ ТРАВА (Козлятник) 50г N1",
"vendor": "Камелия-ЛТ",
"country": "РФ",
"katren": "400707176"
}
]
Пример ответа XML
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<productId>228</productId>
<barcode>4250369505166</barcode>
<title>Лекарственные средства АСПИРИН ЭКСПРЕСС табл. шип. 500мг N12</title>
<vendor>БАЙЕР</vendor>
<country>ГЕРМАНИЯ</country>
<katren>55079077</katren>
<protek>1-218661,218661,70218661</protek>
<egk>100986</egk>
</item>
<item>
<productId>229</productId>
<barcode>4630000891184</barcode>
<title>Травы, чаи прочие ГАЛЕГИ ТРАВА (Козлятник) 50г N1</title>
<vendor>Камелия-ЛТ</vendor>
<country>РФ</country>
<katren>400707176</katren>
</item>
</items>
Остатки REST #
Частота обновления остатков в системе Партнёра должна быть не реже, чем 1 раза в 15 минут.
Для выгрузки остатков со стороны Партнёра должен быть реализован эндпоинт, принимающий GET-запросы, и возвращающий
список остатков с content-type: application/json (или xml, на усмотрение Партнёра).
Ютека будет отправлять запросы, например:
GET https://partner-host.ru/stocks?warehouseId={warehouseId}
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
, где:
partner-host.ru- хост партнёраstocks- адрес ресурса с информацией по остаткам{warehouseId}- ID склада, по которому Ютека получит остатки. Для остатков аптек —pharmacyId={pharmacyId}X-Request-ID- специальный заголовок для трассировки запросов. При его наличии, в ответе на запрос должен быть точно такой же заголовок с таким же значением. Если со стороны Ютеки этот заголовок не пришёл, Партнёру нужно сгенерировать и отправить его со своей стороны самостоятельно.
Ютека будет получать остатки по каждому складу/аптеке, т.е. кол-во запросов на остатки = кол-ву складов/аптек Партнёра.
Ютека будет отправлять запросы на получение остатков одновременно по нескольким складам/аптекам, но с лимитом на кол-во одновременных запросов (по умолчанию 10 одновременных запросов, увеличение/уменьшение обсуждается индивидуально).
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
productId |
string | ✔️ | ID товара из выгрузок товаров |
warehouseId |
string | ID склада из выгрузок складов. Обязателен для остатков склада (extendedPickup). Не обязательный, т.к. мы можем достать его из запроса |
|
price |
float64 или string или int (если стоимость в копейках) | ✔️ | Цена |
quantity |
int или string | ✔️ | Количество |
partNumber или consignment |
string | Номер партии | |
expirationDate |
string | Срок годности в формате: 2006-01-02T15:04:05 или 2006-01-02 |
|
maxQuantity |
int или string | Максимально доступное количество этой позиции на единицу заказа | |
isDelivery |
bool | Доступен ли данный остаток для доставки (только для типа delivery). По умолчанию true |
Допускается использование названий полей, типов их содержания и не по шаблону (обговаривается с Партнёром в индивидуальном порядке).
Возможно добавление дополнительных полей для более специфичной логики, например, ограничение доставки некоторых остатков в некоторые аптеки (например, требуется ли холодильник в аптеке для текущего остатка) или по другим условиям.
Пример ответа JSON
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
[
{
"product_id": "1234",
"price": 51.0,
"quantity": 914,
"partNumber": "20250201228228",
"expirationDate": "2040-01-02T00:00:00",
"maxQuantity": 10
},
{
"product_id": "1235",
"price": 78.0,
"quantity": 1324,
"partNumber": "20250201228114",
"expirationDate": "2045-06-02T00:00:00"
},
{
"product_id": "1236",
"price": 58.0,
"quantity": 730,
"partNumber": "20250201228553",
"expirationDate": "2042-09-01T00:00:00"
}
]
Пример ответа XML
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<product_id>1234</product_id>
<price>51.0</price>
<quantity>914</quantity>
<partNumber>20250201228228</partNumber>
<expirationDate>2040-01-02T00:00:00</expirationDate>
<maxQuantity>10</maxQuantity>
</item>
<item>
<product_id>1235</product_id>
<price>78.0</price>
<quantity>1324</quantity>
<partNumber>20250201228114</partNumber>
<expirationDate>2045-06-02T00:00:00</expirationDate>
</item>
<item>
<product_id>1236</product_id>
<price>58.0</price>
<quantity>730</quantity>
<partNumber>20250201228553</partNumber>
<expirationDate>2042-09-01T00:00:00</expirationDate>
</item>
</items>
Обмен заказами через протокол REST (рекомендуемый ✅) #
graph TD Uteka[Ютека] -->|POST /orders/create| Partner[REST API Партнёра] Uteka -->|GET или POST /orders/status| Partner Uteka -->|POST или DELETE /orders/cancel| Partner Uteka -->|POST /delivery/calculate| Partner
Ютека будет отправлять запросы с новыми заказами в инфраструктуру Партнёра используя HTTP REST протокол.
Далее Ютека будет опрашивать API партнёра по каждому незавершённому заказу для получения обновлений их статусов.
Также, Ютека может отправлять отмены по этим заказам, когда отмена инициирована, например, пользователем, либо истекло
время сборки заказа.
Для заказов типа delivery Ютека дополнительно запрашивает расчёт вариантов доставки.
Партнёру нужно реализовать на своей стороне HTTP REST интерфейс для отправки Ютекой:
- Новых заказов
- Запросов на получение статусов по заказам
- Отмену заказов
- Расчёт доставки (для
delivery)
Типы заказов и их флоу #
Прежде чем описать каждый эндпоинт, важно понять жизненный цикл заказа. Все четыре типа заказов проходят одинаковую цепочку статусов — отличаются только смысл каждого шага и дополнительные поля.
Общая диаграмма статусов #
graph LR A[approved] --> B[ready] B --> C[completed] A -.->|отмена| X[cancelled] B -.->|отмена| X
| Статус | Описание |
|---|---|
approved |
Заказ подтверждён, взят в обработку |
ready |
Готов к выдаче / передан курьеру |
completed |
Заказ завершён (выкуплен / доставлен) |
cancelled |
Отменён Партнёром |
Могут быть дополнительные промежуточные статусы Партнёра, текст статусов может быть разным, главное — наличие 4-х разных статусов, соответствующих по смыслу описаниям в таблице выше.
Что означают статусы для каждого типа заказа #
| Статус | pickup |
extendedPickup |
delivery |
courier |
|---|---|---|---|---|
approved |
Аптека увидела заказ | Товар готовится к отправке со склада | Формируется отправление | Аптека начинает сборку |
ready |
Заказ собран, ожидает покупателя | Товар доставлен в аптеку, ожидает покупателя | Заказ передан курьеру / в пути | Заказ собран, ожидает курьера Ютеки |
completed |
Покупатель забрал заказ | Покупатель забрал заказ | Покупатель получил заказ | Курьер забрал заказ из аптеки |
Отличия по типам заказов #
pickup — самовывоз из аптеки. Покупатель бронирует товар в аптеке и приходит забрать его лично. Самый простой тип, не требует дополнительных полей или эндпоинтов.
extendedPickup — самовывоз на следующий день. Товар на складе, отправляется в аптеку. В ответах на статусы Партнёр может передавать дополнительные поля:
deliveryDate— актуальная дата доставки в аптеку (если изменилась)shippingStartedAt— дата/время отправки товара со склада
delivery — доставка курьером Партнёра. Перед созданием заказа Ютека запрашивает у Партнёра расчёт доставки, выбранный покупателем тайм-слот передаётся в запросе на создание заказа. После передачи заказа курьеру отмена может быть невозможна (на усмотрение Партнёра).
courier — доставка курьером Ютеки. При создании заказа Ютека может передать поле courierCode — код, который курьер Ютеки называет при получении заказа из аптеки. Ютека формирует этот код самостоятельно при взаимодействии с курьерской службой. Партнёру необходимо сохранить этот код и предъявить его курьеру при выдаче заказа. После того, как курьер вручит заказ покупателю и заказ будет считаться завершённым, Ютека может отправить Партнёру подтверждение выполнения заказа, чтобы Партнёр мог списать позиции из заказа в своей системе.
Дополнительные поля в ответе на статус по типам заказов #
| Поле | Описание | pickup |
extendedPickup |
delivery |
courier |
|---|---|---|---|---|---|
deliveryDate |
Дата доставки товара в аптеку | — | ✔️ | — | — |
shippingStartedAt |
Дата/время отправки со склада | — | ✔️ (опционально) | — | — |
reservationEndsAt |
Дата окончания резервирования | ✔️ (опционально) | ✔️ (опционально) | — | — |
items |
Актуальная корзина (для частичной сборки) | ✔️ (опционально) | ✔️ (опционально) | ✔️ (опционально) | ✔️ (опционально) |
Создание заказа REST #
После того, как пользователь создал заказ, Ютека передаёт его в интеграцию используя HTTP REST интерфейс.
Для отправки заказов Ютекой со стороны Партнёра должен быть реализован эндпоинт, принимающий POST-запросы, и
возвращающий ID заказа в инфраструктуре партнёра с content-type: application/json (или xml, на усмотрение Партнёра).
Ютека будет отправлять запросы, которые будут состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
orderId или utekaOrderId |
string | ✔️ | ID заказа Ютеки |
orderType |
string | ✔️ | Тип заказа: pickup, extendedPickup, delivery, courier |
warehouseId |
string | extendedPickup ✔️ | ID склада из выгрузок складов, из которого нужно отправить препараты в аптеку |
pharmacyId |
string | ✔️ | ID аптеки из выгрузок аптек |
amount |
float64 | ✔️ | Полная стоимость корзины в рублях (сумма всех item.price * item.quantity), округлённая до копеек |
name |
string | ✔️ | Имя покупателя |
phone |
string | ✔️ | Телефон покупателя в международном формате без 7, например, 9181231234 |
items |
[]Object | ✔️ | Корзина |
items.productId |
string | ✔️ | ID товара из выгрузок товаров |
items.consignment или items.partNumber |
string | ️ | Номер партии товара при наличии из выгрузки остатков |
items.quantity |
int | ✔️ | Количество текущего ID товара в корзине |
items.price |
float64 | ✔️ | Цена за единицу текущего ID товара из выгрузки остатков |
deliveryAddress |
string | delivery ✔️ | Адрес доставки (только для delivery) |
deliverySlotId |
string | delivery ✔️ | ID выбранного тайм-слота из расчёта доставки (только для delivery) |
deliveryParams |
string | Параметры доставки, возвращённые в расчёте доставки (только для delivery) |
|
courierCode |
string | Код курьера для получения заказа из аптеки (только для courier). Формируется Ютекой при взаимодействии с курьерской службой |
Допускается использование названий полей, типов их содержания и не по шаблону (обговаривается с Партнёром в индивидуальном порядке).
Пример JSON — pickup
POST https://partner-host.ru/orders/create
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"utekaOrderId": "1234",
"orderType": "pickup",
"pharmacyId": "100",
"items": [
{
"productId": "1234",
"quantity": 2,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 78
}
],
"amount": 180,
"name": "Иванов Иван Иванович",
"phone": "9181231234"
}
Пример JSON — extendedPickup
POST https://partner-host.ru/orders/create
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"utekaOrderId": "1235",
"orderType": "extendedPickup",
"pharmacyId": "228",
"warehouseId": "msc",
"items": [
{
"productId": "1234",
"quantity": 1,
"price": 51
}
],
"amount": 51,
"name": "Петров Пётр Петрович",
"phone": "9261234567"
}
Пример JSON — delivery
POST https://partner-host.ru/orders/create
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"utekaOrderId": "1236",
"orderType": "delivery",
"pharmacyId": "100",
"items": [
{
"productId": "1234",
"quantity": 2,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 78
}
],
"amount": 180,
"name": "Сидоров Сидор Сидорович",
"phone": "9031234567",
"deliveryAddress": "г Москва, ул Пушкина, д 10, кв 5",
"deliverySlotId": "slot-100-1",
"deliveryParams": "{\"deliveryTimeSlotId\":42}"
}
Пример JSON — courier
POST https://partner-host.ru/orders/create
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"utekaOrderId": "1237",
"orderType": "courier",
"pharmacyId": "100",
"items": [
{
"productId": "1235",
"quantity": 3,
"price": 78
}
],
"amount": 234,
"name": "Козлова Анна Сергеевна",
"phone": "9161234567",
"courierCode": "A7B3"
}
Пример XML — extendedPickup
POST https://partner-host.ru/orders/create
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<order>
<utekaOrderId>1235</utekaOrderId>
<orderType>extendedPickup</orderType>
<pharmacyId>228</pharmacyId>
<warehouseId>msc</warehouseId>
<items>
<item>
<productId>1234</productId>
<quantity>1</quantity>
<price>51</price>
</item>
</items>
<amount>51</amount>
<name>Петров Пётр Петрович</name>
<phone>9261234567</phone>
</order>
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
partnerOrderId |
string | ✔️ | ID заказа в инфраструктуре Партнёра. Можно использовать ID Ютеки (обсуждается перед реализацией) |
utekaOrderId |
string | ️ | ID заказа Ютеки |
status |
string | Статус заказа. Если не указан, считаем, что заказ взят в обработку | |
deliveryDate |
string | Дата доставки корзины в аптеку (для extendedPickup), если она не совпадает с датой из выгрузки аптек |
Пример ответа JSON
HTTP/2.0 201 Created
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "0001-4321",
"utekaOrderId": "1234",
"status": "approved"
}
Пример ответа XML
HTTP/2.0 201 Created
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<orderStatus>
<partnerOrderId>0001-4321</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>approved</status>
</orderStatus>
Получение статусов по заказам от Партнёра REST #
После того, как Ютека передала заказ в инфраструктуру партнёра, Ютека будет отправлять запросы на получение статусов по заказам.
Для отправки статусов по заказам со стороны Партнёра должен быть реализован эндпоинт, принимающий GET или POST-запросы,
и возвращающий объект заказа в инфраструктуре партнёра с content-type: application/json (или xml, на усмотрение
Партнёра).
Ютека будет отправлять запросы, которые будут состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
partnerOrderId |
string | ✔️ | ID заказа в инфраструктуре Партнёра. Можно использовать ID Ютеки (обсуждается перед реализацией) |
utekaOrderId |
string | ️ | ID заказа Ютеки |
Пример запроса JSON
POST-запрос:
POST https://partner-host.ru/orders/status
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"partnerOrderId": "228",
"utekaOrderId": "1234"
}
Или GET-запрос:
GET https://partner-host.ru/orders/status?partnerOrderId=228
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Пример запроса XML
POST https://partner-host.ru/orders/status
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<orderStatus>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
</orderStatus>
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
partnerOrderId |
string | ✔️ | ID заказа в инфраструктуре Партнёра |
utekaOrderId |
string | ️ | ID заказа Ютеки |
status |
string | ✔️ | Статус заказа |
deliveryDate |
string | Дата доставки корзины в аптеку (для extendedPickup) |
|
shippingStartedAt |
string | Дата/время отправки товара со склада (для extendedPickup) |
|
reservationEndsAt |
string | Дата/время окончания резервирования заказа | |
items |
[]Object | Актуальная корзина при частичной сборке заказа | |
items.productId |
string | ✔️(если заполнен items) |
ID товара из выгрузок товаров |
items.consignment или items.partNumber |
string | ️ | Номер партии товара при наличии |
items.quantity |
int | ✔️(если заполнен items) |
Количество текущего ID товара в корзине |
items.price |
float64 | ✔️(если заполнен items) |
Цена за единицу текущего ID товара |
Возможные статусы заказов:
| Статус | Описание |
|---|---|
approved |
Подтверждён. Интеграция взяла заказ в обработку. Фармацевт в аптеке увидел заказ или заказ дошёл до аптеки и сохранился со стороны аптечной сети. |
ready |
Готов к выдаче. Интеграция доставила корзину в аптеку, собрала заказ и готова выдать покупателю. Покупатель может идти в аптеку |
completed |
Продан. Покупатель пришёл в аптеку и выкупил корзину |
cancelled |
Отменён аптекой. Аптека не смогла на каком-то из этапов собрать заказ и отменила его |
Могут быть дополнительные статусы, текст статусов может быть разным, главное, наличие 4-х разных статусов, соответствующих по смыслу к описаниям в таблице выше.
Заказ подтверждён JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"status": "approved"
}
Заказ подтверждён XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>approved</status>
</status>
extendedPickup — approved с датой доставки JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "0001-4322",
"utekaOrderId": "1235",
"status": "approved",
"deliveryDate": "2024-04-27T00:00:00",
"shippingStartedAt": "2024-04-26T14:30:00"
}
Заказ отменён JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"status": "cancelled",
"comment": "Нет на складе"
}
Заказ отменён XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>cancelled</status>
<comment>Нет на складе</comment>
</status>
Batch получение статусов по заказам от Партнёра REST #
Это необязательный вариант реализации обмена статусами по заказам, когда Ютека в одном запросе передаёт сразу несколько IDs заказов, по которым ожидает получить статусы.
По сути, отличие от реализации обмена статусами по заказам состоит только в том, что Ютека будет передавать массив объектов заказов вместо одного заказа, по которым нужно получить статусы, а инфраструктура Партнёра будет возвращать в ответе массив объектов заказов, вместо одного заказа, с актуальными статусами.
Этот процесс так же может поддерживать и механизм частичных сборок по аналогии с обычной реализацией обмена статусами по заказам
Пример запроса JSON
POST-запрос:
POST https://partner-host.ru/orders/status
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"orderIds": [
{
"partnerOrderId": "0001-4321",
"utekaOrderId": "1234"
},
{
"partnerOrderId": "0001-4322",
"utekaOrderId": "1235"
},
{
"partnerOrderId": "0001-4323",
"utekaOrderId": "1236"
}
]
}
Или GET-запрос:
GET https://partner-host.ru/orders/status?partnerOrderId=0001-4321&partnerOrderId=0001-4322&partnerOrderId=0001-4323
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Пример запроса XML
POST https://partner-host.ru/orders/status
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<orderIds>
<orderId>
<partnerOrderId>0001-4321</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
</orderId>
<orderId>
<partnerOrderId>0001-4322</partnerOrderId>
<utekaOrderId>1235</utekaOrderId>
</orderId>
<orderId>
<partnerOrderId>0001-4323</partnerOrderId>
<utekaOrderId>1236</utekaOrderId>
</orderId>
</orderIds>
Пример ответа JSON — разные типы заказов
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"orderIds": [
{
"partnerOrderId": "0001-4321",
"utekaOrderId": "1234",
"status": "approved"
},
{
"partnerOrderId": "0001-4322",
"utekaOrderId": "1235",
"status": "approved",
"deliveryDate": "2024-04-27T00:00:00"
},
{
"partnerOrderId": "0001-4323",
"utekaOrderId": "1236",
"status": "completed"
}
]
}
Пример ответа XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<orderStatuses>
<order>
<partnerOrderId>0001-4321</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>approved</status>
</order>
<order>
<partnerOrderId>0001-4322</partnerOrderId>
<utekaOrderId>1235</utekaOrderId>
<status>approved</status>
<deliveryDate>2024-04-27T00:00:00</deliveryDate>
</order>
<order>
<partnerOrderId>0001-4323</partnerOrderId>
<utekaOrderId>1236</utekaOrderId>
<status>completed</status>
</order>
</orderStatuses>
Частичная сборка корзины заказа REST #
Это необязательный механизм, при котором после подтверждения заказа его состав может быть изменён. Поддерживается для всех типов заказов.
Например, инфраструктура Партнёра взяла заказ в обработку, но при сборке на складе или уже в аптеке собрать заказ целиком не получилось. В таком случае вместо мгновенной отмены, со стороны Ютеки предусмотрен механизм, при котором Ютека отправляет покупателю уведомление об изменении состава заказа, заказ переходит в состояние ожидание действия от пользователя. Далее может быть 2 ситуации:
- пользователь согласился с изменениями. В этом случае, состав корзины меняется на новый и заказ идёт дальше по флоу обработки и выдачи. Можем отправлять подтверждение в инфраструктуру Партнёра (обговаривается индивидуально), но это необязательное действие.
- пользователь отклонил изменения. В этом случае, со стороны Ютеки будет отправлена отмена по этому заказу в инфраструктуру Партнёра.
graph TD status[Мониторинг статуса на Ютеке] -->|POST /orders/status| Partner[REST API Партнёра] validateCart -->|было изменение| userFlow[Ожидание ответа пользователя] validateCart -->|не было изменений| status Partner -->|HTTP 200 + items| validateCart[Проверка состава на Ютеке] userFlow -->|Не принял изменения POST /orders/cancel| Partner userFlow -->|Принял изменения| status
Для поддержки этого механизма при опросе Ютекой статусов по заказам в ответе должен приходить массив объектов каждого элемента корзины.
Под изменениями в корзине понимается:
- уменьшение количества каких-нибудь позиций в корзине
- увеличение стоимости каких-нибудь позиций в корзине
- если корзина целиком недоступна, отмена заказа будет сразу
При этом, если количество позиций в корзине увеличилось или стали дешевле, изменения в корзине не будут зафиксированы.
Вместе (либо вместо) с productId дополнительно (но не обязательно) можно использовать номера партий partNumber для
более точного сравнения корзин. Допускается использование названий полей, типов их содержания и не по шаблону (
обговаривается с Партнёром в индивидуальном порядке).
Примеры ответов ниже.
Корзина доступна целиком JSON
В этом случае корзина будет совпадать с исходной. Ютека будет дальше продолжать получать статусы по заказу.
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"items": [
{
"productId": "1234",
"quantity": 2,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 78
}
],
"status": "approved"
}
Корзина доступна целиком XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<items>
<item>
<productId>1234</productId>
<quantity>2</quantity>
<price>51</price>
</item>
<item>
<productId>1235</productId>
<quantity>1</quantity>
<price>78</price>
</item>
</items>
<status>approved</status>
</status>
В корзине не хватает одной из позиций JSON
В этом случае корзина будет отличаться от исходной количеством товара 1234, 2 -> 1. Ютека отправит пользователю
уведомление и будет ждать его ответа. В зависимости от ответа пришлёт либо отмену, либо продолжит опрашивать статусы.
При продолжении опроса корзины из ответов будут сравниваться уже с новой корзиной.
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"items": [
{
"productId": "1234",
"quantity": 1,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 78
}
],
"status": "approved"
}
В корзине не хватает одной из позиций XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<items>
<item>
<productId>1234</productId>
<quantity>1</quantity>
<price>51</price>
</item>
<item>
<productId>1235</productId>
<quantity>1</quantity>
<price>78</price>
</item>
</items>
<status>approved</status>
</status>
В корзине изменилась цена на одну из позиций JSON
В этом случае корзина будет отличаться от исходной ценой товара 1235, 78 -> 105. Ютека отправит пользователю
уведомление и будет ждать его ответа.
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"items": [
{
"productId": "1234",
"quantity": 2,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 105
}
],
"status": "approved"
}
В корзине изменилась цена на одну из позиций XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<items>
<item>
<productId>1234</productId>
<quantity>2</quantity>
<price>51</price>
</item>
<item>
<productId>1235</productId>
<quantity>1</quantity>
<price>105</price>
</item>
</items>
<status>approved</status>
</status>
Изменилась цена и не хватает позиции одновременно JSON
В этом случае корзина будет отличаться от исходной количеством товара 1234, 2 -> 1 и ценой товара 1235,
78 -> 105.
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"items": [
{
"productId": "1234",
"quantity": 1,
"price": 51
},
{
"productId": "1235",
"quantity": 1,
"price": 105
}
],
"status": "approved"
}
Изменилась цена и не хватает позиции одновременно XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<items>
<item>
<productId>1234</productId>
<quantity>1</quantity>
<price>51</price>
</item>
<item>
<productId>1235</productId>
<quantity>1</quantity>
<price>105</price>
</item>
</items>
<status>approved</status>
</status>
Отмена заказа REST #
После того, как Ютека передала заказ в инфраструктуру партнёра, Ютека может отправлять запросы на отмену этих заказов. Отмена может инициироваться пользователем, либо другими процессами, например, когда у заказа истёк дедлайн сборки.
Для отправки отмен по заказам со стороны Партнёра должен быть реализован эндпоинт, принимающий DELETE или
POST-запросы, и возвращающий результат отмены в инфраструктуре партнёра с content-type: application/json (или xml, на усмотрение
Партнёра).
Ютека будет отправлять запросы, которые будут состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
partnerOrderId |
string | ✔️ | ID заказа в инфраструктуре Партнёра. Можно использовать ID Ютеки (обсуждается перед реализацией) |
utekaOrderId |
string | ️ | ID заказа Ютеки |
status |
string | ️ | Передача статуса отмены явно, если это требуется |
Пример JSON
POST-запрос:
POST https://partner-host.ru/orders/cancel
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"partnerOrderId": "228",
"utekaOrderId": "1234"
}
Или DELETE-запрос:
DELETE https://partner-host.ru/orders/cancel?partnerOrderId=228
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Пример XML
POST https://partner-host.ru/orders/cancel
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<orderStatus>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
</orderStatus>
Ответ должен состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
partnerOrderId |
string | ID заказа в инфраструктуре Партнёра. Можно использовать ID Ютеки (обсуждается перед реализацией) | |
utekaOrderId |
string | ️ | ID заказа Ютеки |
status |
string | ️ | Передача статуса отмены явно, если это требуется |
Тело ответа при этом не обязательно. Будет достаточно 2xx статуса в ответе на запрос отмены. Но можно заполнить поля
с IDs и статусом, чтобы на стороне Ютеки сделать дополнительные проверки.
Заказ отменён со стороны Ютеки JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"status": "cancelled"
}
Заказ отменён со стороны Ютеки XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<status>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>cancelled</status>
</status>
Ограничения отмены по типам заказов #
| Тип заказа | Ограничения отмены |
|---|---|
pickup |
Отмена возможна в любом нетерминальном статусе |
extendedPickup |
Отмена возможна в любом нетерминальном статусе |
delivery |
После передачи курьеру отмена может быть невозможна (на усмотрение Партнёра) |
courier |
Отмена возможна в любом нетерминальном статусе |
Batch отмена заказов REST #
Это необязательный вариант реализации отмен заказов, когда Ютека в одном запросе передаёт сразу несколько IDs заказов, которые нужно отменить в инфраструктуре Партнёра.
По сути, отличие от реализации отмен заказов состоит только в том, что Ютека будет передавать массив объектов заказов вместо одного заказа, которые нужно отменить, а инфраструктура Партнёра сможет обрабатывать такие запросы.
Пример запроса JSON
POST-запрос:
POST https://partner-host.ru/orders/cancel
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"orderIds": [
{
"partnerOrderId": "228",
"utekaOrderId": "1234"
},
{
"partnerOrderId": "339",
"utekaOrderId": "1235"
},
{
"partnerOrderId": "440",
"utekaOrderId": "1236"
}
]
}
Или DELETE-запрос:
DELETE https://partner-host.ru/orders/cancel?partnerOrderId=228,339,440
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
Пример запроса XML
POST https://partner-host.ru/orders/cancel
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<orderIds>
<orderId>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
</orderId>
<orderId>
<partnerOrderId>339</partnerOrderId>
<utekaOrderId>1235</utekaOrderId>
</orderId>
<orderId>
<partnerOrderId>440</partnerOrderId>
<utekaOrderId>1236</utekaOrderId>
</orderId>
</orderIds>
Пример ответа JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
{
"orderIds": [
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"status": "cancelled"
},
{
"partnerOrderId": "339",
"utekaOrderId": "1235",
"status": "cancelled"
},
{
"partnerOrderId": "440",
"utekaOrderId": "1236",
"status": "cancelled"
}
]
}
Пример ответа XML
Content-Type: application/xml; charset=utf-8
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
<?xml version="1.0" encoding="UTF-8"?>
<orderStatuses>
<order>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status>cancelled</status>
</order>
<order>
<partnerOrderId>339</partnerOrderId>
<utekaOrderId>1235</utekaOrderId>
<status>cancelled</status>
</order>
<order>
<partnerOrderId>440</partnerOrderId>
<utekaOrderId>1236</utekaOrderId>
<status>cancelled</status>
</order>
</orderStatuses>
Подтверждение выполнения заказа REST (courier) #
Необходимо для:
courier(опционально)
Если Партнёру нужно от Ютеки явное подтверждение о том, что заказ доставлен покупателю, для таких случаев Ютека может отправить Партнёру запрос с подтверждением выполнения заказа после того, как курьер вручит заказ.
Конкретный способ передачи подтверждения обсуждается с Партнёром в индивидуальном порядке. Как правило, используется
тот же эндпоинт, что и для отмены заказа (например, changeStatus), но вместо статуса отмены
передаётся статус выполнения.
Партнёру необходимо предоставить Ютеке числовой или строковый код статуса, который означает «заказ выполнен» в его системе.
Пример: подтверждение через changeStatus JSON
POST https://partner-host.ru/orders/changeStatus
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"partnerOrderId": "228",
"utekaOrderId": "1234",
"status": "<код статуса выполнения, предоставленный Партнёром>"
}
Пример: подтверждение через changeStatus XML
POST https://partner-host.ru/orders/changeStatus
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<orderStatus>
<partnerOrderId>228</partnerOrderId>
<utekaOrderId>1234</utekaOrderId>
<status><код статуса выполнения, предоставленный Партнёром></status>
</orderStatus>
Ответ аналогичен ответу на отмену заказа — достаточно 2xx статуса.
Расчёт доставки REST #
Необходимо для:
delivery
Перед созданием заказа типа delivery Ютека запрашивает у Партнёра варианты доставки. Покупатель выбирает
подходящий вариант (тайм-слот), и выбранный вариант передаётся в запросе на создание заказа.
Для расчёта доставки со стороны Партнёра должен быть реализован эндпоинт, принимающий POST-запросы, и
возвращающий массив вариантов доставки с content-type: application/json.
Ютека будет отправлять запросы, которые будут состоять из следующих полей:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
lat |
float64 | Широта адреса доставки | |
lon |
float64 | Долгота адреса доставки | |
city |
string | Город доставки | |
address |
string | Адрес доставки | |
cart |
[]Object | ✔️ | Корзина |
cart.productId |
string | ✔️ | ID товара |
cart.quantity |
int | ✔️ | Количество |
pharmacyIDs |
[]string | ✔️ | Список ID аптек, из которых возможна доставка |
Пример запроса JSON
POST https://partner-host.ru/delivery/calculate
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
Content-Type: application/json
{
"lat": 55.753215,
"lon": 37.622504,
"city": "Москва",
"address": "ул Пушкина, д 10",
"cart": [
{
"productId": "1234",
"quantity": 2
},
{
"productId": "1235",
"quantity": 1
}
],
"pharmacyIDs": ["100", "101", "102"]
}
Ответ должен содержать массив вариантов доставки. Каждый вариант — это тайм-слот, привязанный к конкретной аптеке:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
id |
string | ✔️ | ID тайм-слота (передаётся обратно при создании заказа) |
title |
string | ✔️ | Название метода доставки (отображается покупателю) |
courierTitle |
string | Название службы доставки | |
date |
string | ✔️ | Дата доставки в формате YYYY-MM-DD |
timeFrom |
string | ✔️ | Начало временного окна, например "10:00" |
timeTo |
string | ✔️ | Конец временного окна, например "14:00" |
price |
string | ✔️ | Стоимость доставки в рублях |
pharmacyID |
string | ✔️ | ID аптеки, из которой будет осуществляться доставка |
isPaymentRequired |
bool | Требуется ли оплата при получении | |
isPaymentAvailable |
bool | Доступна ли оплата при получении | |
params |
string | Произвольные параметры (JSON-строка, пробрасывается обратно при создании заказа) |
Пример ответа JSON
Content-Type: application/json
X-Request-ID: TQaWgDfqCyWufZPvilhiyznyGfoLTDKP
[
{
"id": "slot-100-1",
"title": "Доставка сегодня 14:00–18:00",
"courierTitle": "Экспресс-доставка",
"date": "2024-04-26",
"timeFrom": "14:00",
"timeTo": "18:00",
"price": "199.00",
"pharmacyID": "100",
"isPaymentRequired": false,
"isPaymentAvailable": true,
"params": "{\"deliveryTimeSlotId\":42}"
},
{
"id": "slot-100-2",
"title": "Доставка завтра 10:00–14:00",
"courierTitle": "Стандартная доставка",
"date": "2024-04-27",
"timeFrom": "10:00",
"timeTo": "14:00",
"price": "149.00",
"pharmacyID": "100",
"isPaymentRequired": false,
"isPaymentAvailable": true,
"params": "{\"deliveryTimeSlotId\":43}"
},
{
"id": "slot-101-1",
"title": "Доставка завтра 14:00–18:00",
"courierTitle": "Стандартная доставка",
"date": "2024-04-27",
"timeFrom": "14:00",
"timeTo": "18:00",
"price": "149.00",
"pharmacyID": "101",
"isPaymentRequired": false,
"isPaymentAvailable": true,
"params": "{\"deliveryTimeSlotId\":44}"
},
{
"id": "slot-100-3",
"title": "Доставка послезавтра 10:00–14:00",
"courierTitle": "Эконом-доставка",
"date": "2024-04-28",
"timeFrom": "10:00",
"timeTo": "14:00",
"price": "99.00",
"pharmacyID": "100",
"isPaymentRequired": false,
"isPaymentAvailable": true,
"params": "{\"deliveryTimeSlotId\":45}"
}
]
Ютека покажет покупателю эти варианты. Покупатель выберет один из них. Выбранные id и params будут переданы
в запросе на создание заказа в полях deliverySlotId и deliveryParams.
Сводная схема взаимодействия по типам заказов #
pickup — самовывоз из аптеки #
sequenceDiagram
participant U as Покупатель
participant Y as Ютека
participant P as Партнёр
Note over Y,P: Выгрузки (периодически)
Y->>P: GET /pharmacies
Y->>P: GET /products
Y->>P: GET /stocks?pharmacyId=100
Note over U,P: Оформление заказа
U->>Y: Выбирает товар в аптеке
Y->>P: POST /orders/create (orderType: pickup)
P-->>Y: partnerOrderId + status
Note over Y,P: Мониторинг статуса
loop Пока заказ не завершён
Y->>P: POST /orders/status
P-->>Y: status (approved → ready → completed)
end
U->>Y: Приходит в аптеку, забирает заказ
extendedPickup — самовывоз на следующий день #
sequenceDiagram
participant U as Покупатель
participant Y as Ютека
participant P as Партнёр
Note over Y,P: Выгрузки (периодически)
Y->>P: GET /warehouses
Y->>P: GET /pharmacies (с warehouseId, deliveryDates)
Y->>P: GET /products
Y->>P: GET /stocks?warehouseId=msc
Note over U,P: Оформление заказа
U->>Y: Выбирает товар со склада, аптеку для самовывоза
Y->>P: POST /orders/create (orderType: extendedPickup, warehouseId)
P-->>Y: partnerOrderId + status + deliveryDate
Note over Y,P: Мониторинг статуса
loop Пока заказ не завершён
Y->>P: POST /orders/status
P-->>Y: status + deliveryDate (approved → ready → completed)
end
U->>Y: Получает уведомление "Заказ в аптеке"
U->>Y: Приходит в аптеку, забирает заказ
delivery — доставка курьером Партнёра #
sequenceDiagram
participant U as Покупатель
participant Y as Ютека
participant P as Партнёр
Note over Y,P: Выгрузки (периодически)
Y->>P: GET /pharmacies
Y->>P: GET /products
Y->>P: GET /stocks?pharmacyId=100
Note over U,P: Оформление заказа
U->>Y: Выбирает товар, указывает адрес доставки
Y->>P: POST /delivery/calculate (pharmacyIDs, cart, address)
P-->>Y: Массив тайм-слотов доставки
U->>Y: Выбирает тайм-слот
Y->>P: POST /orders/create (orderType: delivery, deliverySlotId, deliveryAddress)
P-->>Y: partnerOrderId + status
Note over Y,P: Мониторинг статуса
loop Пока заказ не завершён
Y->>P: POST /orders/status
P-->>Y: status (approved → ready → completed)
end
Note over P,U: Партнёр доставляет заказ
P->>U: Курьер Партнёра доставляет заказ
courier — доставка курьером Ютеки #
sequenceDiagram
participant U as Покупатель
participant Y as Ютека
participant K as Курьер Ютеки
participant P as Партнёр
Note over Y,P: Выгрузки (периодически)
Y->>P: GET /pharmacies
Y->>P: GET /products
Y->>P: GET /stocks?pharmacyId=100
Note over U,P: Оформление заказа
U->>Y: Выбирает товар, указывает адрес доставки
Y->>P: POST /orders/create (orderType: courier, courierCode)
P-->>Y: partnerOrderId + status
Note over Y,P: Мониторинг статуса
loop Пока заказ не собран
Y->>P: POST /orders/status
P-->>Y: status (approved → ready)
end
Note over Y,K: Ютека назначает курьера
Y->>K: Назначение доставки + courierCode
K->>P: Приезжает в аптеку, называет courierCode
P-->>K: Проверяет courierCode, выдаёт заказ
K->>U: Доставляет заказ покупателю
Коды ответов HTTP #
| Код | Описание |
|---|---|
200 |
Успех (GET-запросы, получение статусов) |
201 |
Создано (POST на создание заказа) |
400 |
Ошибка валидации запроса |
403 |
Ошибка авторизации |
500 |
Внутренняя ошибка сервера |
Формат ошибок:
{
"error": "описание ошибки"
}
Чек-лист для Партнёра #
Минимальная интеграция (pickup) #
- Реализовать выгрузку аптек (FTP или REST)
- Реализовать выгрузку товаров (FTP или REST)
- Реализовать выгрузку остатков (FTP или REST)
- Реализовать эндпоинт создания заказа
- Реализовать эндпоинт получения статуса заказа
- Реализовать эндпоинт отмены заказа
- Поддержать
X-Request-ID - Настроить авторизацию
Дополнительно для extendedPickup #
- Реализовать выгрузку складов
- Добавить
warehouseIdиdeliveryDatesв выгрузку аптек - Реализовать выгрузку остатков складов (аналогично остаткам аптек, с
warehouseId) - Поддержать поле
deliveryDateв ответах на статус заказа - Поддержать
warehouseIdв запросе на создание заказа
Дополнительно для delivery #
- Реализовать эндпоинт расчёта доставки (
/delivery/calculate) - Настроить тайм-слоты доставки для аптек
- Поддержать поля
deliveryAddress,deliverySlotId,deliveryParamsв запросе на создание заказа
Дополнительно для courier #
- Поддержать поле
courierCodeв запросе на создание заказа (передаётся Ютекой опционально) - Опционально: реализовать подтверждение выполнения заказа, если аптека не списывает позиции без явного подтверждения от Ютеки
Опционально #
- Поддержать частичную сборку корзины (поле
itemsв ответе на статус) - Поддержать batch-запросы статусов
- Поддержать batch-отмены заказов
- Поддержать поле
reservationEndsAtв ответах на статус - Поддержать поле
shippingStartedAtв ответах на статус (для extendedPickup)