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







