Задача звучала коротко: написать коннектор между сайтом пиццерии на CS-Cart и r_keeper Delivery и выгружать по регламенту заказы, покупателей, товары с остатками и ценами, категории. На деле самое интересное началось после того, как коннектор заработал.
Как устроена синхронизация
Модуль ходит в API r_keeper Delivery по OAuth: Client ID и Client Secret выдаёт дилер r_keeper. Дальше три потока:
- Заказы — с сайта в r_keeper. Cron раз в 5 минут, плюс ручная кнопка «Синхронизировать заказы сейчас».
- Статусы — из r_keeper на сайт. Мгновенно по вебхуку, а cron раз в 5 минут перепроверяет для подстраховки.
- Меню — из r_keeper на сайт, раз в 30 минут. Отдельный режим обновляет только цены и остатки и ничего не создаёт.
Для вебхука на сайте поднят отдельный endpoint. Первая версия принимала токен параметром в URL, но интерфейс r_keeper такие ссылки не принимает: только чистый адрес и заголовок. В итоге авторизация переехала в заголовок X-Webhook-Token.
Проблема 1: меню, которое расходится на треть
API r_keeper принимает в заказе только блюда, которые пришли из его же меню. Произвольный товар с сайта касса отклоняет. А меню на сайте и в кассе жили отдельно и расходились примерно на треть.
Сопоставлять вручную сотни позиций никто не будет, поэтому модуль сравнивает названия нечётко:
- игнорирует кавычки и регистр;
- учитывает характеристики: «Пицца Феличита» на сайте с диаметром 50 см и «Пицца Феличита 50 см» в кассе — одно и то же блюдо;
- считает процент схожести и сопоставляет автоматически, если он выше порога. На проекте порог 92%.
Нечёткое сравнение ошибается на коротких и похожих названиях: «Пицца 4 вкуса 50 см» однажды склеилась с «33 см». Поэтому у модуля есть отчёт сравнения, где каждое совпадение можно принять или отклонить, и предпросмотр синхронизации без изменений.
Ещё одна ловушка: соусы, у которых в кассе цена 0. Синхронизация цен честно перенесла этот ноль на сайт. После этого появился режим «только цены и остатки» и привычка смотреть отчёт перед массовым обновлением.
Проблема 2: Симферополь, Украина
Тестовый заказ на «бульвар Ленина, 22, Симферополь» в r_keeper определился как Украина. Оказалось, что Delivery проверяет адреса через DaData и собирает строку как street + cityName. Когда в запросе есть только полный адрес одной строкой и нет координат, сервис угадывает.
Решение — передавать город отдельным полем и координаты точки доставки. После этого адреса стали определяться правильно.
Проблема 3: скидка, с которой заказ не идёт на кухню
На сайте свои промо-акции, и скидку нужно передавать в кассу. Первая версия раскладывала её по каждому блюду. Касса такой заказ принимала, но передать его на кухню было нельзя.
Правильный формат — одна скидка на весь заказ общей суммой и с id скидки, заведённой в кассе. Причём важен именно id: по одному названию касса скидку не узнаёт. Это выяснилось только когда я включил тестовый режим, посмотрел реальный запрос и сравнил с тем, что ожидает касса.
Что помогло в отладке
- Тестовый режим: заказ не уходит в r_keeper, только логируется вместе с полным запросом. На боевом объекте без этого никак: каждый тестовый заказ иначе рискует оказаться на кухне.
- Подробные логи с уровнем «отладочный».
- Плашка на странице заказа: «Заказ отправлен в r_keeper, номер …» и кнопка повторной отправки. Если заказ не ушёл, это сразу видно и менеджеру, и в письме.
Итог
Заказы с сайта уходят на кассу и кухню без участия человека, статусы возвращаются, цены и остатки обновляются по расписанию. Главный вывод для себя: документация API описывает формат, но не поведение. Поведение видно только на живом объекте, поэтому тестовый режим и понятные логи стоит закладывать в интеграцию с первого дня.