Простое подключение эквайринга Тинькофф (Т-Банк) к WooCommerce

Практика · WooCommerce · интернет-эквайринг · PHP

Исторический пример подключения платёжной формы Тинькофф к нестандартному сценарию оформления заказа в WooCommerce. Код создавал заказ, собирал данные чека и переводил покупателя на оплату. Это простой и примитивный вариант интеграции: он запускает платёжную форму, но не отслеживает дальнейшее состояние транзакции.

Важно: этот подход нельзя считать полноценной production-интеграцией эквайринга. В нём нет серверного учёта PaymentId, обработки уведомлений банка, проверки финального статуса, идемпотентного обновления заказа и автоматической сверки платежей.

Какую задачу решал этот код

Проект использовал WooCommerce, но оформление заказа проходило не по стандартному сценарию плагина. Нужно было взять товары и данные покупателя из текущей корзины, создать заказ программно, подготовить состав чека и открыть платёжную форму банка.

Упрощённый поток выглядел так:

  1. Прочитать корзину

    Получить товары, варианты, сумму и контактные данные покупателя из WooCommerce.

  2. Создать заказ

    Добавить позиции, адрес и доставку, пересчитать итог и оставить заказ в ожидании оплаты.

  3. Сформировать чек

    Передать наименование, цену, количество, сумму, систему налогообложения и контакт покупателя.

  4. Открыть форму банка

    Заполнить скрытые поля и отправить пользователя на платёжную страницу через клиентский скрипт.

Что именно реализовано

Связь с корзиной

Заказ создаётся из содержимого текущей корзины WooCommerce.

Контактные данные

В форму передаются имя, телефон и e-mail покупателя.

Данные чека

Собирается JSON с позициями, ценами, количеством и налоговыми параметрами.

Переход к оплате

Платёжный JavaScript отправляет форму и открывает страницу банка.

Почему это только базовое подключение

Переход пользователя на страницу оплаты ещё не означает, что деньги получены. Покупатель может закрыть вкладку, получить отказ банка, повторить попытку, оплатить после задержки или вернуться на сайт по сохранённой ссылке.

В исходном варианте приложение не сохраняло банковский идентификатор операции и не вело конечный автомат состояний платежа. Заказ связывался с браузером упрощённо, а дальнейший результат не подтверждался независимо от страницы возврата.

Граница возможностей старого примера
ЕстьНет
Создание локального заказаСохранение и контроль PaymentId
Подготовка суммы и чекаОбработка серверных уведомлений банка
Открытие платёжной формыПроверка подписи уведомления
Передача контактов покупателяОтслеживание переходов статуса транзакции
Возврат пользователя после формыИдемпотентное подтверждение заказа
Одна попытка оплатыПовторы, отмена, возврат и reconciliation

Без отслеживания транзакции нельзя менять заказ на «оплачен»

Страница успешного возврата удобна для интерфейса, но не является доказательством оплаты. Её может не открыть сам покупатель, а параметры запроса нельзя использовать как доверенный источник.

Статус заказа должен меняться только после подтверждения со стороны банка: по проверенному серверному уведомлению либо после отдельного запроса состояния операции. Повтор одного и того же уведомления не должен повторно начислять товар, баланс или бонусы.

local order: pending_payment
        ↓
create bank payment → save PaymentId
        ↓
customer pays on the bank page
        ↓
verified notification / status request
        ↓
paid | failed | cancelled | refunded

Как строить полноценную интеграцию

  1. Зафиксировать заказ на сервере

    Сумма, валюта, позиции и идентификатор заказа вычисляются на сервере. Данные из скрытых полей браузера нельзя считать доверенными.

  2. Инициализировать платёж серверным запросом

    Приложение создаёт операцию у банка, сохраняет полученный идентификатор и URL оплаты рядом с локальным заказом.

  3. Хранить историю статусов

    Операция проходит несколько состояний. Полезно сохранять не только текущий статус, но и журнал переходов с временем и источником события.

  4. Принимать уведомления

    Публичный callback проверяет подпись, связывает событие с ожидаемой операцией и применяет изменение в транзакции базы данных.

  5. Обеспечить идемпотентность

    Повторное уведомление или retry запроса не должны повторно подтверждать заказ или выполнять бизнес-операцию.

  6. Проверять зависшие платежи

    Периодическая задача запрашивает актуальное состояние операций, для которых уведомление не пришло или обработка завершилась ошибкой.

  7. Выполнять сверку

    Заказы, банковские операции, возвраты и фактические поступления регулярно сопоставляются. Расхождения попадают в отдельную очередь разбора.

Минимальный production-чек-лист

  • СекретыПароль терминала хранится только на сервере и не попадает в HTML, JavaScript или репозиторий.
  • СуммаРассчитывается по данным заказа на сервере, а не принимается из браузера.
  • СвязьЛокальный заказ связан с банковским PaymentId и каждой попыткой оплаты.
  • CallbackУведомление проверяется криптографически и обрабатывается идемпотентно.
  • СтатусыДопустимые переходы описаны явно; неожиданный переход не применяется молча.
  • ПовторыСетевые ошибки обрабатываются ограниченными retry без двойного списания.
  • СверкаЕсть периодическая проверка незавершённых и расходящихся операций.
  • НаблюдаемостьЛоги содержат ID заказа и операции, но не платёжные данные и секреты.

Когда упрощённый вариант допустим

Такой подход можно использовать как прототип для проверки интерфейса, демонстрации перехода к оплате или временного сценария, где результат затем проверяется вручную. Даже в этом случае нельзя автоматически выдавать товар или считать заказ оплаченным только по факту отправки формы.

Для магазина, подписки, цифрового товара, внутреннего баланса или любого финансово значимого процесса необходим серверный жизненный цикл транзакции. Платёжная форма — это только начало операции, а не подтверждение результата.

WooCommerce · PHP · Интернет-эквайринг · Payment Status · Webhooks · Idempotency


Давайте обсудим проект

Расскажите, что хотите сделать. Я отвечу на вашу почту.

Или напишите в Telegram @ifwcom