Оплата, которая не теряется между банком и заказом
В работающем интернет-магазине на Drupal 11 и Drupal Commerce нужно было подключить оплату картой через эквайринг Т-Банка: покупатель платит на стороне банка, магазин получает подтверждение, заказ переходит в оплаченный, а бухгалтерия — чек.
Платёж — самая чувствительная точка магазина. Деньги списываются не в интерфейсе сайта, а во внешней системе, и между этими двумя событиями может произойти что угодно: покупатель закроет страницу, банк ответит с задержкой, уведомление придёт дважды или не придёт вовсе. Ошибка здесь стоит дороже, чем в любой другой интеграции: покупатель уже заплатил.
Я разработал собственный платёжный шлюз для Drupal Commerce и фоновую сверку статусов. Шлюз создаёт платёж, отправляет покупателя в банк, проверяет банковские уведомления и приводит состояние заказа в порядок даже там, где ответ банка потерялся.
Технологии этого решения
Магазин, платёжный шлюз Commerce и собственный слой работы с API банка.
Drupal 11: заказы, права и журналы
Платежи, состояния заказа и чеки
Эквайринг, уведомления и статусы платежей
Подпись запросов, очередь и сверка
Один платёж — три источника правды
Платёж проходит через три системы: заказ в Commerce, платёжная страница банка и уведомления банка. Задача шлюза — свести их в одно состояние, не доверяя ни одной из сторон на слово.
На стороне покупателя
Оплата картой как обычный способ оплаты в оформлении заказа. Покупатель переходит на платёжную страницу банка, возвращается в магазин и видит актуальный статус заказа: оплачен, отменён или ждёт подтверждения.
На стороне магазина
Платёж создаётся внутри Commerce и связан с заказом, сумма и валюта берутся из заказа, а не из браузера. Уведомления банка обновляют платёж, чек уходит в банк вместе с платежом, а фоновая сверка возвращает в строй заказы, чей статус остался неопределённым.
Каждое состояние подтверждается дважды. Уведомление от банка принимается только с корректной подписью и правильным терминалом. Если уведомление не пришло или пришло с опозданием, платёж не «зависает»: фоновая задача сама запрашивает статус у банка и применяет его по тем же правилам.
Создание платежа
Commerce собирает платёж по заказу, шлюз подписывает запрос и получает от банка ссылку на оплату. Идентификаторы сохраняются в заказе и в платеже.
Оплата в банке
Покупатель платит на стороне банка. Магазин в этот момент ничего не выдумывает: он ждёт подтверждения от банка, а не считает оплату состоявшейся по факту возврата на сайт.
Уведомление и проверка
Банковское уведомление проходит проверку подписи и терминала, затем применяется к платежу и заказу под блокировкой.
Сверка и восстановление
Фоновая задача периодически опрашивает платежи в незавершённых состояниях и приводит их к актуальному статусу, включая оплаты, созданные до появления сверки.
Платёж нельзя «дописать» дважды
В эквайринге главный риск — не ошибка в интерфейсе, а неверное состояние денег. Поэтому шлюз устроен так, чтобы повторные и запоздавшие события не меняли уже принятое решение.
Подпись вместо доверия
Каждый запрос к банку и каждое входящее уведомление подписываются токеном: значения запроса сортируются, к ним добавляется секрет терминала, результат сравнивается со строкой подписи. Сравнение — постоянное по времени, иначе подпись можно подобрать по времени ответа. Дополнительно проверяется терминал: уведомление от чужого терминала не применяется.
Один платёж — одна блокировка
Обновление платежа идёт под блокировкой по идентификатору банковского платежа, общей для уведомлений, возврата покупателя и фоновой сверки. Два одновременных события не могут переписать состояние друг за другом.
Сумме верим только после сверки
Подтверждение банка применяется как полная оплата только тогда, когда совпадают и идентификатор платежа, и сумма с локальным платежом. Частичное или несовпадающее списание не превращается в «заказ оплачен» — такой платёж уходит на ручной разбор с записью в лог.
Поздние события не разворачивают историю
Запоздавшая авторизация не отменяет уже подтверждённую оплату, а устаревшее подтверждение не отменяет возврат. Конечные состояния платежа не переписываются задним числом — история оплаты остаётся однозначной.
Отдельно — про баланс заказа. Сумма оплаты обновляется в том же фоновом прогоне, который применил статус, а не «когда-нибудь при завершении запроса»: иначе администратор видел бы оплаченный заказ с нулевым балансом.
Фоновая сверка вместо ожидания уведомления
Уведомление от банка — не гарантия доставки. Магазин не должен зависеть от того, дошёл ли один запрос: поэтому статус незавершённых платежей периодически проверяется сам.
Что попадает в сверку
Платежи в состояниях «новый» и «авторизован», у которых есть банковский идентификатор. Сканируются только включённые шлюзы, а платежи, созданные раньше самой сверки, подхватываются тем же механизмом.
Порции и очередь
Cron раз в пять минут набирает ограниченную порцию платежей и ставит их в очередь, которая обрабатывается со своим бюджетом времени. Следующая порция ждёт, пока очередь разберётся, — так сбой одного прогона не превращается в лавину запросов к банку.
Курсор, чтобы старые не голодали
Список обходится по курсору идентификаторов: если часть платежей остаётся незавершённой надолго, они не заслоняют собой более свежие. Каждый платёж выбирается повторно через увеличивающуюся задержку, а завершённые больше не опрашиваются.
Двухстадийная схема — осознанно
Авторизованная оплата остаётся авторизованной: сверка не подтверждает списание сама и не списывает деньги автоматически. Если магазину нужна двухстадийная схема с отдельным подтверждением, это остаётся управляемым действием, а не побочным эффектом фоновой задачи.
Сверка — операция только на чтение: она запрашивает статус у банка и применяет его теми же правилами, что и уведомление. Ошибки сети и ответы банка с ошибкой логируются, платёж остаётся в выборке и будет проверен в следующий проход.
Чек собирается из заказа, а не из браузера
Если магазин работает с чеками, вместе с платежом в банк уходит состав заказа: позиции, количество, цена, ставка налога и признак предмета расчёта. Данные берутся из заказа Commerce, поэтому подменить их со стороны покупателя невозможно.
Доставка и другие положительные наценки уходят отдельными позициями: чек обязан сходиться с суммой платежа. Скидка, применённая к заказу целиком (например, списанные бонусы), распределяется по позициям — отрицательных строк в чеке быть не может.
Отдельная тонкость — маркировка. Код маркировки в позиции чека и обычный штрихкод товара — разные вещи, поэтому штрихкод EAN-13 в это поле не отправляется: ошибка в маркировке дороже, чем лишняя строка настройки.
Платёжный шлюз внутри Drupal Commerce
Шлюз реализован как платёжный плагин Commerce с формой настроек: терминал, секрет, описание платежа, настройки чеков и адреса возврата. Отдельные сервисы отвечают за сверку и за постановку платежей в очередь, а обработчик очереди применяет статусы.
Работа с банком вынесена в один слой: подпись запросов, разбор ответов, обработка ошибок и логирование без секретов. В исходниках есть unit-тесты на сверку и восстановление статусов — с подменёнными HTTP-ответами банка, без реальных запросов и без базы.
Стек: Drupal 11, Drupal Commerce, Commerce Payment, PHP, Т-Банк API (Init, GetState, уведомления), Queue API, Lock API.
Оплата как управляемый процесс
Магазин принимает оплату картой, видит статус каждого платежа в заказе и в админке, а расхождения с банком разбираются по логу, а не по памяти.
Для покупателя
Оплата картой в общем оформлении заказа, возврат на сайт с понятным статусом и без повторных списаний при обновлении страницы.
Для команды магазина
Оплаты не «зависают»: неопределённые платежи догоняются фоновой сверкой, а несовпадения сумм не превращаются в оплаченные заказы молча.
Этот кейс показывает, как я подхожу к платежам: считать деньги там, где их нельзя потерять, и делать состояние оплаты однозначным при любом сбое. Эквайринг здесь — часть разработки интернет-магазинов; когда платёжный контур встраивается в платформу с кабинетами, каталогом и API, подключается разработка сайтов и веб-платформ.
Вопросы про эквайринг на Drupal
Есть ли готовый модуль Т-Банка для Drupal Commerce?
Публичного модуля, который закрывает полный цикл — платёж, уведомления, чеки, сверку и возвраты, — для Drupal нет. Обычно интеграция разрабатывается под магазин: она зависит от схемы оплаты, настроек чеков, правил доставки и того, как в заказе появляются скидки и бонусы.
Т-Банк и Тинькофф — это одно и то же?
Да, это одна организация: банк работает под новым именем, а в технической документации и адресах API по-прежнему встречается прежнее название. Для интеграции это ничего не меняет — важен договор эквайринга, терминал и секрет, выданные банком.
Как магазин узнаёт, что заказ оплачен?
По банковскому уведомлению, которое приходит на сайт: оно проверяется по подписи и терминалу и только после этого меняет состояние платежа и заказа. Если уведомление задержалось или потерялось, статус подтягивается фоновой сверкой — считать заказ оплаченным «по факту возврата покупателя на сайт» нельзя.
Покупатель говорит, что оплатил, а заказ не оплачен. Что происходит?
Такой платёж сначала проверяется у банка: сверка запрашивает актуальный статус и применяет его. Если банк подтверждает оплату, а сумма совпадает с локальным платежом, заказ становится оплаченным автоматически. Если сумма или идентификатор не совпадают, платёж уходит на ручной разбор с записью в лог — молча закрыть такой заказ опаснее, чем показать его оператору.
Что с чеками по 54-ФЗ?
Если чеки включены в настройках, состав заказа уходит в банк вместе с платежом: позиции, количество, цена, налог и предмет расчёта. Доставка добавляется отдельной позицией, а скидка на весь заказ распределяется по позициям, потому что отрицательных строк в чеке быть не может.
Можно ли сделать двухстадийную оплату?
Да, авторизация поддерживается: платёж остаётся в состоянии авторизации, а подтверждение (списание) выполняется отдельным действием. Модуль намеренно не подтверждает и не списывает деньги автоматически: за это решение должен отвечать магазин, а не фоновая задача.
Что нужно, чтобы подключить эквайринг к магазину на Drupal?
Договор эквайринга и доступы терминала (терминал, секрет, тестовый и рабочий контуры), адрес уведомлений на сайте, настройки чеков и правила возврата. Дальше — разбор оформления заказа: состав платежа, скидки, доставка и порядок действий оператора при спорных оплатах.