У клиента уже была настроена связка Битрикс + физическая касса АТОЛ. Менять оборудование и тем более переводить всё на облачную кассу не хотелось. Во-первых, это дополнительные расходы. Во-вторых, существующая схема устраивала клиента и не было особого смысла менять её целиком ради одной задачи.
Поэтому сначала посмотрели, что вообще умеет сама касса и какие варианты интеграции предоставляет АТОЛ. В документации нашли WebRequest — HTTP-интерфейс, через который можно передавать кассовые команды непосредственно устройству.
На этом и решили попробовать построить свою интеграцию.
Почему именно WebRequest
Идея здесь довольно простая: Битрикс формирует данные чека, а дальше вместо штатного обработчика мы передаем их в АТОЛ через WebRequest.
То есть архитектура получается примерно такая:
Битрикс → собственный обработчик → ATOL WebRequest → касса.
Никакой отдельной облачной кассы между магазином и физическим устройством в этой схеме нет.
Через WebRequest можно отправлять не только чеки. Есть команды для работы со сменой и проверки состояния кассы. Это пригодилось, потому что хотелось сделать не просто «отправку JSON», а полноценный кассовый обработчик для Битрикс.
В нашем случае были нужны в первую очередь:
- отправка чеков;
- открытие смены;
- закрытие смены;
- проверка состояния смены;
- проверка результата выполнения задания;
- работа с UUID операций.
На бумаге всё это выглядело достаточно просто. На практике больше всего времени ушло не на HTTP-запрос, а на то, чтобы правильно состыковать жизненный цикл кассовой операции Битрикс с асинхронной моделью АТОЛ.
Сначала разбирались с Битрикс
Первое, что стало понятно после изучения модуля «Кассы»: просто перехватить создание чека и отправить его в WebRequest не получится.
У Битрикс есть собственная модель кассовых обработчиков. Система ожидает от них определенный набор методов и определенное поведение. Поэтому пошли по более правильному пути — сделали собственный обработчик на базе стандартного класса кассы.
В качестве основы используется Bitrix\Sale\Cashbox\Cashbox. Дальше уже нужно было связать методы этого класса с форматом запросов АТОЛ.
Получилась достаточно обычная прослойка: снаружи она выглядит для Битрикс как касса, внутри занимается подготовкой данных и отправкой заданий в WebRequest.
Регистрация обработчика
Для начала обработчик нужно зарегистрировать в Битрикс. Для этого используется событие onGetCustomCashboxHandlers.
AddEventHandler('sale', 'onGetCustomCashboxHandlers', function() {
return [
'redramka_webrequest' => RedramkaWebRequestHandler::class
];
});
После этого новый обработчик появляется среди доступных вариантов кассы.
Дальше уже начинается самое интересное — нужно было разобраться, какие данные Битрикс передает обработчику и в каком виде АТОЛ ожидает получить эти же данные.
Формирование чека
Битрикс внутри уже знает практически всё необходимое для чека: товары, количество, цену, скидки, способ оплаты и остальные данные заказа.
Поэтому не стали собирать чек заново с нуля. Логика формирования запроса была вынесена в отдельный метод, а дальше результат преобразовывался в структуру, которую понимает WebRequest.
Если упростить код до самой сути, отправка выглядит примерно так:
public function sendCheck(Check $check)
{
$uuid = Uuid::generate();
$query = $this->buildCheckQuery($check);
$payload = [
'uuid' => $uuid,
'request' => [$query],
];
$this->sendToAtol($payload);
return new EventResult(EventResult::SUCCESS);
}
Это не полный код обработчика, а только схема работы. В реальной реализации вокруг неё еще есть подготовка данных, обработка ответа и работа со статусом задания.
Отдельно пришлось внимательно пройтись по составу позиций и платежей. В таких интеграциях проблема часто возникает не на уровне самого запроса, а из-за какого-нибудь поля, которое в одной системе называется одним образом, а в другой имеет совершенно другую структуру.
Самый неожиданный момент — чек не готов сразу
Вот здесь как раз пришлось немного изменить первоначальный подход.
Поначалу логика казалась очевидной: отправили запрос — получили ответ — считаем, что чек пробит.
Но WebRequest работает асинхронно.
Когда АТОЛ принимает запрос, это еще не означает, что касса уже закончила операцию. В ответ мы получаем идентификатор задания, а дальше нужно проверять его состояние.
В упрощенном виде процесс выглядит так:
- Битрикс формирует чек.
- Создается UUID.
- Задание отправляется в WebRequest.
- АТОЛ принимает его.
- Касса обрабатывает задание.
- По UUID можно получить результат.
Из-за этого нельзя бездумно воспринимать любой успешный HTTP-ответ как успешную фискализацию.
И это, пожалуй, главный момент всей интеграции.
Зачем нужен UUID
UUID используется как идентификатор конкретного задания. Его нужно сохранять, потому что именно по нему потом можно понять, что произошло с операцией.
Это особенно важно при проблемах с сетью.
Представим ситуацию: Битрикс отправил чек, АТОЛ его получил, но соединение оборвалось до того, как магазин получил ответ. Если в этот момент просто повторить отправку, можно получить уже две операции вместо одной.
Поэтому логика должна быть другой: сначала попытаться определить состояние уже созданного задания, и только после этого принимать решение о повторной отправке.
Для меня это был один из тех моментов, которые хорошо выглядят в простой схеме на бумаге, но становятся важными сразу после первого реального сбоя.
Открытие и закрытие смены
С чеками разобрались, но оставался еще один обязательный кусок — управление сменой.
Для открытия смены в WebRequest передается соответствующая команда. Например:
{
"uuid": "...",
"request": [
{
"type": "openShift",
"operator": {
"name": "Иванов",
"vatin": "1234567890"
}
}
]
}
Закрытие выполняется аналогичным образом:
{
"uuid": "...",
"request": [
{
"type": "closeShift",
"operator": {
"name": "Иванов",
"vatin": "1234567890"
}
}
]
}
Кроме самих команд, добавили проверку состояния смены. Это позволяет не пытаться отправлять кассовую операцию в ситуацию, когда касса находится в неподходящем состоянии.
В итоге перед отправкой чека можно проверить состояние кассы, а после отправки — отдельно контролировать результат задания.
Что оказалось важнее самого API
Если смотреть только на документацию WebRequest, задача кажется довольно прямолинейной: сформировать JSON и отправить HTTP-запрос.
Но в реальной интеграции проблемные места находятся немного в другом.
Во-первых, нужно учитывать очередь. Физическая касса не может обрабатывать бесконечное количество операций одновременно.
Во-вторых, нельзя путать принятие задания с его выполнением.
В-третьих, нужно продумать поведение при обрыве соединения. Именно здесь UUID становится действительно полезным, а не просто обязательным полем запроса.
И еще один момент — сетевое окружение. WebRequest предъявляет свои требования к адресу, с которого идут запросы, поэтому при переносе проекта или изменении инфраструктуры это тоже необходимо учитывать.
Что получилось в итоге
В результате оставили существующую физическую кассу и не стали добавлять к проекту еще один облачный сервис.
Со стороны Битрикс появился собственный кассовый обработчик. Он получает данные стандартным для Битрикс способом, преобразует их в формат АТОЛ и передает задания через WebRequest.
При этом отдельно обрабатываются смены, UUID и статусы операций.
В итоге получили:
- интеграцию Битрикс с физической кассой АТОЛ;
- возможность передавать необходимые данные в чек;
- работу без аренды отдельной облачной кассы;
- контроль над логикой интеграции на своей стороне;
- возможность менять обработчик под конкретный проект.
При этом я бы не сказал, что это какой-то универсальный способ заменить облачную кассу. У решения есть свои ограничения, и перед внедрением их нужно учитывать.
Но для проекта, где уже есть физическая касса АТОЛ и требуется связать её с Битрикс, WebRequest оказался вполне рабочим вариантом.
Главный вывод для нас оказался довольно простым: сложность здесь не в том, чтобы отправить JSON по HTTP. Самое важное — правильно связать две разные модели работы. Битрикс считает, что у него есть кассовая операция, а АТОЛ сначала принимает задание, потом ставит его в очередь и только после этого выполняет.
Если учитывать эту разницу изначально, интеграция получается вполне управляемой.