При интеграции платёжного шлюза в Magento 2 разработчики часто сталкиваются с ошибками 404 на callback, неправильной обработкой статусов или утечкой данных. Неверная конфигурация di.xml приводит к сбоям Command Pool, а отсутствие валидации подписи — к уязвимостям. Например, один из заказчиков потерял $15 000 из-за неверно обработанного callback: статус оплаты не обновлялся, и заказы уходили в "Pending", хотя деньги были списаны. За время работы мы решили более 20 таких кейсов, от простых редиректов до многошаговых сценариев с токенизацией. Типичная разработка занимает 7–12 рабочих дней — под ключ с гарантией 30 дней.
Какие проблемы решает кастомный плагин?
Проблема 1: Несовместимость стандартного API шлюза с Magento. Большинство шлюзов не имеют готового модуля, а те, что есть, часто работают через устаревшие методы (например, SOAP вместо REST) или не поддерживают vault. Кастомный плагин реализует нужные команды (authorize, capture, refund, void) через Payment Gateway API.
Проблема 2: Ошибки валидации callback. Многие шлюзы присылают callback с неполными данными или без подписи. Без HMAC-валидации злоумышленник может подменить статус. В нашем плагине мы реализуем строгую проверку с использованием CsrfAwareActionInterface.
Проблема 3: Низкая производительность готовых модулей. Готовые расширения часто загружают лишние JS и CSS, увеличивая время загрузки checkout. Кастомный плагин весит меньше 100 KB и не влияет на LCP.
Как разработать кастомный плагин оплаты Magento 2?
Payment Gateway API Magento 2 основан на виртуальных типах и Command Pool. Это не простой «модуль с контроллерами», а система из множества взаимосвязанных классов. Разберём на реальном кейсе — интеграция гипотетического шлюза MyPay.
Из документации Magento: "Gateway API предоставляет гибкий механизм для создания команд и обработки ответов, что позволяет интегрировать любой платёжный провайдер без изменения ядра." (Источник: DevDocs Magento)
Почему di.xml — основа плагина?
В Magento 2 di.xml переопределяет почти всё. Для плагина оплаты мы создаём виртуальный тип MyPayGatewayFacade, наследуемый от Magento\Payment\Model\Method\Adapter. В нём подключаем свой Command Pool, Value Handler и Info блок. Так получаем полный контроль над жизненным циклом транзакции.
Структура модуля:
app/code/MyCompany/MyPay/
├── Api/
│ └── Data/
│ └── PaymentResponseInterface.php
├── Controller/Payment/
│ ├── Redirect.php
│ └── Callback.php
├── Gateway/
│ ├── Command/
│ │ ├── AuthorizeCommand.php
│ │ └── RefundCommand.php
│ ├── Http/
│ │ ├── Client/Curl.php
│ │ └── TransferFactory.php
│ ├── Request/
│ │ ├── AuthorizationRequest.php
│ │ └── RefundRequest.php
│ ├── Response/
│ │ ├── AuthorizeHandler.php
│ │ └── ValidateHandler.php
│ └── Validator/
│ └── ResponseValidator.php
├── Model/
│ └── Ui/
│ └── ConfigProvider.php
├── view/frontend/
│ ├── layout/checkout_index_index.xml
│ ├── requirejs-config.js
│ └── web/js/view/payment/
│ ├── method-renderer/mypay.js
│ └── mypay-payments.js
├── etc/
│ ├── config.xml
│ ├── di.xml
│ └── payment.xml
├── registration.php
└── composer.json
payment.xml
<?xml version="1.0"?>
<payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Payment/etc/payment.xsd">
<groups>
<group id="mypay">
<label>MyPay</label>
</group>
</groups>
<methods>
<method name="mypay">
<allow_multiple_address>0</allow_multiple_address>
</method>
</methods>
</payment>
di.xml: сборка Gateway
<virtualType name="MyPayGatewayFacade" type="Magento\Payment\Model\Method\Adapter">
<arguments>
<argument name="code" xsi:type="const">MyCompany\MyPay\Model\Ui\ConfigProvider::CODE</argument>
<argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument>
<argument name="infoBlockType" xsi:type="string">Magento\Payment\Block\Info</argument>
<argument name="valueHandlerPool" xsi:type="object">MyPayValueHandlerPool</argument>
<argument name="commandPool" xsi:type="object">MyPayCommandPool</argument>
</arguments>
</virtualType>
<virtualType name="MyPayCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
<arguments>
<argument name="commands" xsi:type="array">
<item name="authorize" xsi:type="string">MyCompany\MyPay\Gateway\Command\AuthorizeCommand</item>
<item name="refund" xsi:type="string">MyCompany\MyPay\Gateway\Command\RefundCommand</item>
<item name="void" xsi:type="string">MyCompany\MyPay\Gateway\Command\VoidCommand</item>
</argument>
</arguments>
</virtualType>
Как реализовать callback и валидацию подписи?
Callback-контроллер — критический элемент. Он получает уведомления от шлюза, проверяет подпись HMAC и обновляет статус заказа. В Magento 2 необходимо отключить CSRF-защиту через CsrfAwareActionInterface. Пример callback-уведомления от шлюза:
{
"payment_id": "txn_123abc",
"order_id": "000000001",
"status": "succeeded",
"amount": 1500,
"currency": "USD",
"signature": "a1b2c3..."
}
Пример реализации контроллера:
namespace MyCompany\MyPay\Controller\Payment;
class Callback extends \Magento\Framework\App\Action\Action implements \Magento\Framework\App\CsrfAwareActionInterface
{
public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
{
return null;
}
public function validateForCsrf(RequestInterface $request): ?bool
{
return true;
}
public function execute(): void
{
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if (!$this->signatureValidator->validate($raw, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
http_response_code(403);
exit;
}
$order = $this->orderRepository->get(
$this->orderFactory->create()->loadByIncrementId($data['order_id'])->getId()
);
if ($data['status'] === 'succeeded') {
$payment = $order->getPayment();
$payment->setTransactionId($data['payment_id'])->capture(null);
$order->setState(\Magento\Sales\Model\Order::STATE_PROCESSING)
->setStatus(\Magento\Sales\Model\Order::STATE_PROCESSING);
}
$this->orderRepository->save($order);
$this->getResponse()->setBody('OK');
}
}
Vault: сохранённые карты ускоряют checkout в 2 раза
Реализация vault — отдельная задача. Magento предоставляет VaultPaymentInterface, токены хранятся в vault_payment_token. Для провайдера, поддерживающего токенизацию, реализуется TokenizerInterface и отдельный VaultCommand. Это добавляет 3–4 рабочих дня к разработке, но окупается: пользователи совершают покупки в один клик. Экономия на лицензиях готовых модулей с vault может достигать $3000 в год, а окупаемость разработки составляет 3–6 месяцев.
Сравнение: кастомный плагин vs готовые модули
| Характеристика | Кастомный плагин | Готовый модуль |
|---|---|---|
| Размер кода | 200–400 строк | 1000+ строк |
| Скорость выполнения | на 20% быстрее | средняя |
| Безопасность | полный контроль | возможны уязвимости |
| Стоимость лицензии | отсутствует | от $50/мес |
| Гибкость | под любые требования | ограничен настройками |
Кастомное решение легче, безопаснее и легко адаптируется под специфику бизнеса. Стоимость разработки сопоставима с покупкой нестандартного модуля, а результат — гибкость под любые задачи.
Пошаговый процесс разработки
- Анализ API шлюза, спецификация callback-ов.
- Проектирование структуры модуля, создание виртуальных типов в di.xml.
- Реализация Request Builders и Response Handlers для каждой команды (authorize, capture, refund, void).
- Настройка платежного фасада и интеграция с checkout (Knockout.js).
- Разработка callback-контроллера с валидацией подписи.
- Тестирование: юнит-тесты (PHPUnit), интеграционные тесты (Magento TestFramework), цикл заказ-редирект-callback.
- Деплой на staging, нагрузочное тестирование, релиз.
Что входит в работу над кастомным плагином?
Мы предоставляем:
- Документацию по интеграции и схеме данных.
- Доступы к тестовому контуру и production.
- Исходный код с комментариями и тестами.
- Обучение команды (1 час zoom).
- Гарантию 30 дней на исправление ошибок.
Получите консультацию — мы оценим задачу и предложим оптимальное решение. Закажите разработку кастомного плагина под ваш шлюз.







