При інтеграції платіжного шлюзу в 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 днів на виправлення помилок.
Отримайте консультацію — ми оцінимо завдання та запропонуємо оптимальне рішення. Замовте розробку кастомного плагіна під ваш шлюз.







