Карты, деньги, 20 интеграций: как подружить POS-терминалы и фронтенд приложения

Когда ваш бизнес становится глобальным, его приложение приходится адаптировать под новые реалии — обновлять вёрстку, добавлять переводы и изображения. Ну а если ваше приложение тесно связано со сторонним железом, то тут уже приходится включать фантазию на всю катушку.
Два года назад @rexepted написал статью о том, как мы работаем с чеками в разных странах. В ней он описал принцип работы нашей плагинной системы для соединения с разными моделями контрольно-кассовых машин (ККМ). А ещё рассказал о том, как она работает с нашим frontend-приложением через IPC-шину (inter-process communication). С того времени мы запустились ещё в 7 странах, а число наших точек увеличилось на треть — с 890 до 1200+. Благодаря нашей плагинной системе мы ни дня не потратили на интеграцию нашего софта с налоговым ПО новых стран. Этим теперь занимались команды на аутсорсе.
Однако мы добиваемся 100-процентной диджитализации опыта в наших кофейнях и ресторанах и постоянно создаём новые гаджеты для них. Так у нас появились киоски самообслуживания — им и нужна интеграция с POS-терминалами. Как и в случае с ККМ, мы сами занимались интеграцией с POS-терминалами и, выходя на новые рынки, поняли, что больше этого делать не можем.

К тому же мы должны были законнектить кассу ресторана с POS-терминалами. Поскольку до этого интеграция с ними была только в России, в других странах кассиры вручную вводили сумму заказа на терминале, а это тормозило приём заказа.

Получив новые данные и почесав головы, мы поняли, что пора! В этой статье я поделюсь нашим опытом интеграции POS-терминалов во фронтенд приложения, который, возможно, будет полезен вам. Это не руководство и ни в коем случае не призыв к действию.

Задача
Нам необходимо:
-
научить кассы и киоски принимать оплаты через единые контракты;
-
прикрутить оплату заказа не только через терминал, но и с помощью QR-кода;
-
дать возможность любой аутсорс-команде работать над нашей плагинной системой;
-
дать киоскам и кассам возможность выбирать, с каким платёжным плагином они будут работать;
-
собрать плагины двух типов Web- и Electron-.
Контракты
Контракты — это соглашения приложений или разных их частей о взаимодействии друг с другом. Они определяют набор правил и форматов данных для обеспечения корректного взаимодействия и обмена информацией между компонентами.
Для того, чтобы плагин мог взаимодействовать с кассой или киоском, нам нужны универсальные контракты — они подходят к 90% терминалов. Если коротко, то образ результата такой: одна вилка для розеток самых разных регионов.

Есть два вида контрактов: публичные и приватные. Первые — для сторонних разработчиков, а вторые — для наших приложений. Начнём с публичных.
Выделим основные методы. Пока что их два:
pay — лежит в основе всего, ведь POS-терминал должен, прежде всего, принимать оплату. Этому методу мы сообщаем сумму и тип оплаты: карта или QR.
refund — терминал должен вернуть деньги, если клиент откажется от покупки. Одним терминалам достаточно сообщить сумму покупки и приложить карту, а некоторым для возврата нужен ID транзакции или RRN.
Затем определим, что ещё умеют POS-терминалы и как мы можем их использовать в будущем. Мы собрали следующие необязательные функции:
-
отмена транзакции прямо в интерфейсе приложения для более удобного UX;
-
HealthCheck — проверка связи с терминалом;
-
получение данных о последнем платеже для возврата средств. Поможет, если оплата прошла, а информацию о ней сервис не получил или вообще не создал заказ;
-
получение статуса платежа по его идентификаторам. Очень актуально для QR-платежей;
-
закрытие банковской смены, Z-отчёт;
-
получение банковского слипа. Например, для печати через принтер ККМ, а не POS-терминала;
-
отображение настроек плагина для сохранения полезной информации в локальном хранилище. Например, IP терминала в локальной сети.
Опираясь на прошлый опыт разработки плагинных систем, мы понимали, что на старте фронтенд-приложения, всё это дело нужно инициализировать. Для этого мы добавили обязательный метод Initialize. Он возвращает функционал плагина из перечня выше и доступные способы оплаты — карта, QR-код или всё вместе. На выходе получаем такой интерфейс:

Приватные контракты IPaymentsIntegration
отличаются от публичных IPaymentPlugin
лишь парой новых методов. Для реализации этих контрактов мы изолируем логику общения с бэкендом и получения каких-либо данных. Например, в приватном методе Initialize
мы сначала узнаём актуальную версию и тип плагина, скачиваем его и устанавливаем в зависимости от типа.
Архитектура приложения
Чтобы быстрее объяснить устройство нашей архитектуры, напомню, что у нас есть приложение кассы, написанное с помощью Electron. Оно общается с различными ККМ через IPC-коммуникацию. У киосков самообслуживания есть аналогичная Electron программа, а также iOS-приложение.
Если терминал поддерживает беспроводное соединение — например, HTTP — его можно реализовать как web-плагин. Если нужно что-то посерьезнее — проводная связь через USB или COM-порты —, то нам пригодится именно Electron плагин. Он может достучаться до «железок», а обычный браузер нет.
Ниже представлена схема взаимодействия наших frontend-приложений с платёжными плагинами:

Как это выглядит на практике? При загрузке кассы ресторана или киоска самообслуживания импортируется микрофронтенд — объект, реализующий приватные контракты IPaymentsIntegration
. После этого вызывается метод Initialize
. Он скачивает и устанавливает актуальную версию плагинов, доступных для текущего устройства.
Вызывая другие методы, мы уже знаем, какой плагин инициализирован. На основе его типа получаем wrapper
, а он вызывает код самого плагина. Многие вещи уже реализованы в нашем микрофронтенде:
-
гарантии доставки данных до нашего бэкенда;
-
фоновые джобы, отслеживающие статус QR-платежей;
-
установка и кеширование версий плагина;
-
логирование всех операций для понимания того, что происходит с оплатой на конкретной кассе или киоске.
Как происходит разработка плагина
Разработка плагина начинается с выдачи доступов сторонним разработчикам нашему монорепозиторию. После того, как разработчик стянул к себе репозиторий, он должен ввести команду и следовать инструкции:

Для примера создадим тестовый web-плагин:

Видим, что файлы для нового плагина добавились. В них входят:
-
базовые файлы настройки TypeScript;
-
конфигурация eslint, чтобы код был красивый и единообразный;
-
базовая vite-конфигурация для простейшей сборки плагина.

При выполнении команды добавляется и GitHub Action. Он позволяет опубликовать версию плагина при мёрже кода в основную ветку.
Разработчики плагина могут подсматривать в соседние папки, чтобы найти лучшие решения или похихикать с чужого кода. Однако ничего изменить в них не получится — настройки доступа установлены на уровне папки. Если плагину нужны будут какие-то особые настройки, деплой или билд, то разработчики смогут модифицировать сгенерированные файлы так, как им нужно.
Код написан и протестирован на локальном стенде. Разработчики приносят Pull Request. Если в нём нет ничего криминального, то мы мёржим его в основную ветку. После успешных тестов и билда плагин публикуется в блоб-хранилище, где ему указывается актуальная версия в файле version.
Теперь при загрузке страницы на кассе или киоске во время инициализации наш бэкенд пойдет в блоб-хранилище. Там он увидит обновлённый плагин, скачает его актуальную версию и начнёт с ней работать.
Администрирование и настройка
Возникает вопрос: как конкретная касса или киоск узнает, с каким плагином нужно работать? Когда мы только запустили плагинную систему, эта информация была в коде. Первый плагин был написан для касс в Кыргызстане. Их идентификатор указывал на страну, а для неё определялся конкретный плагин.
Один POS-терминал — одна страна? Так просто? На самом деле нет. Иногда в одной стране могут быть несколько интеграций с разными терминалами. А ещё же есть оплата по QR-коду, которая идёт параллельно POS-терминалам.
В итоге мы решили собрать админку для платежей в нашей системе Dodo IS, в которой для конкретных касс и киосков указывались доступные платёжные провайдеры. Оставался всего один вопрос: где хранить логины, пароли, идентификаторы и т.д. каждого из провайдеров?
Мы вспомнили об админке для платежей в киоске — её мы собрали для старых интеграций и решили доработать. Теперь при настройке киоска — а в будущем и кассы — мы можем указать его настройки и доступных для него провайдеров.
Как это работает? Сначала нам нужно добавить к устройству тип оплаты. Подразумевается, что платёжные провайдеры есть двух видов. У первого нет никаких параметров — всё настраивается на самом устройстве, например, после указания IP терминала.

Второй вид посложнее. Ему нужны настройки и секретная информация, такая как в примере с KaspiQR.

Как это выглядит в интерфейсе настроек:

Всё идеально?
Не совсем. Периодически нам придётся работать над соединением плагинов и наших девайсов. Например, если аутсорс-команда разработала плагин, а его версия подтянулась в блоб, нам нужно:
-
добавить в наш бэкенд данные нового платёжного провайдера и его мета-информацию о транзакциях — обычно transactionID и RRN, но иногда и другие данные. Так мы сможем хранить всю информацию в нашей базе данных в нормальном виде;
-
указать настройки, которые поддерживает платёжный провайдер. В большинстве случаев достаточно указать локальный IP терминала на самом устройстве. Однако оплаты по QR-коду требуют логины и пароли для общения с сервисами банка и определением счёта, на который нужно зачислить средства;
-
добавить платёжный провайдер в админку, чтобы привязать его к конкретному устройству и настроить;
-
указать в плагинной системе местонахождение плагина для конкретного платёжного провайдера.
Однако это просто рутина. Любой разработчик из нашей команды справится с этими задачками за 2-3 часа.
Что в итоге?
У нас есть рабочая плагинная система и несколько плагинов. Первый мы написали сами для Кыргызстана, пока собирали систему. В прод запустили их синхронно, чтобы обнаружить все проблемы на старте и пофиксить их.
Позже появился другой подрядчик для того же Кыргызстана и очень быстро написал для нас второй плагин. По итогу для этой страны уже есть выбор из двух плагинов. В зависимости от того, что предлагает тот или иной провайдер, управляющие ресторана выбирают и настраивают всё под свои нужды.
Принимать платежи с помощью QR-кодов мы учились в Казахстане. Для этого мы добавили в качестве способа оплаты KaspiQR — с этим мы тоже справились быстро.
В это же время наши аутсорс-команды, собиравшие плагины для ККМ, разработали платёжные плагины для Беларуси и Кипра. Для Беларуси, кстати, уже тестируют и планируют активно внедрять во всех ресторанах страны. А вот Кипр уже успешно работает с новой интеграцией.
Планы на будущее
Осталось всего 20 интеграций в разных странах, но на каждую мы не потратим больше 2-3 часов. Теперь новые страны смогут открываться без нашего участия, а мы сможем сфокусироваться на других задачах и проектах.
В будущем хотим попробовать устройства 2 в 1. Они часто встречаются, когда мы ищем платёжных провайдеров в других странах. Эти устройства умеют принимать оплату, фискализировать и печатать чеки. Конечно, их пока нельзя привязать к нашим плагинным системам, но, возможно, мы подумаем о том, как их применять в наших реалиях. Но об этом уже как-нибудь в другой раз…
Автор: VasilkovN