Разработка кастомного плагина оплаты Magento 2

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного плагина оплаты Magento 2
Сложная
~5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомного плагина оплаты Magento 2

Magento 2 — одна из самых сложных платформ для разработки платёжных модулей. Архитектура di.xml, Interceptors, Observers, отдельный payment provider flow для vault (сохранённые карты) — всё это требует глубокого знания платформы. Типичный кастомный gateway от нуля до production занимает 7–12 рабочих дней.

Структура модуля

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>

AuthorizeCommand

namespace MyCompany\MyPay\Gateway\Command;

use Magento\Payment\Gateway\CommandInterface;
use Magento\Payment\Gateway\Command\Result\ArrayResultFactory;

class AuthorizeCommand implements CommandInterface
{
    public function __construct(
        private readonly \Magento\Payment\Gateway\Http\ClientInterface $client,
        private readonly \Magento\Payment\Gateway\Http\TransferFactoryInterface $transferFactory,
        private readonly \Magento\Payment\Gateway\Request\BuilderInterface $requestBuilder,
        private readonly \Magento\Payment\Gateway\Response\HandlerInterface $handler,
        private readonly \Magento\Payment\Gateway\Validator\ValidatorInterface $validator,
    ) {}

    public function execute(array $commandSubject): void
    {
        $transferO = $this->transferFactory->create(
            $this->requestBuilder->build($commandSubject)
        );

        $response = $this->client->placeRequest($transferO);

        $result = $this->validator->validate(array_merge($commandSubject, ['response' => $response]));
        if (!$result->isValid()) {
            throw new \Magento\Payment\Gateway\Command\CommandException(
                __('Ошибка авторизации платежа: %1', implode('; ', $result->getFailsDescription()))
            );
        }

        $this->handler->handle($commandSubject, $response);
    }
}

Request Builder

class AuthorizationRequest implements BuilderInterface
{
    public function build(array $buildSubject): array
    {
        $payment = SubjectReader::readPayment($buildSubject);
        $order   = $payment->getPayment()->getOrder();

        return [
            'amount'      => (int) round($order->getGrandTotal() * 100),
            'currency'    => strtoupper($order->getOrderCurrencyCode()),
            'order_id'    => $order->getIncrementId(),
            'customer'    => [
                'email' => $order->getCustomerEmail(),
                'name'  => $order->getCustomerFirstname() . ' ' . $order->getCustomerLastname(),
            ],
            'callback_url'=> $this->urlBuilder->getUrl('mypay/payment/callback'),
            'success_url' => $this->urlBuilder->getUrl('checkout/onepage/success'),
        ];
    }
}

Response Handler: сохранение transaction ID

class AuthorizeHandler implements HandlerInterface
{
    public function handle(array $handlingSubject, array $response): void
    {
        $payment = SubjectReader::readPayment($handlingSubject)->getPayment();

        $payment->setTransactionId($response['payment_id']);
        $payment->setAdditionalInformation('payment_url', $response['payment_url']);
        $payment->setIsTransactionClosed(false);
        $payment->setShouldCloseParentTransaction(false);
    }
}

Контроллер callback

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; // Webhook не отправляет CSRF-токен
    }

    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');
    }
}

Frontend: knockout.js компонент

Magento 2 checkout использует Knockout.js. Компонент метода оплаты:

define(['Magento_Checkout/js/view/payment/default', 'mage/url'], function (Component, url) {
    'use strict';
    return Component.extend({
        defaults: { template: 'MyCompany_MyPay/payment/mypay' },
        redirectAfterPlaceOrder: false,

        afterPlaceOrder: function () {
            window.location.replace(url.build('mypay/payment/redirect'));
        },

        getData: function () {
            return {
                method: this.item.method,
                additional_data: {},
            };
        },
    });
});

Vault (сохранённые карты)

Реализация vault — отдельная задача. Magento предоставляет VaultPaymentInterface, токены хранятся в vault_payment_token. Для провайдера, поддерживающего токенизацию, реализуется TokenizerInterface и отдельный VaultCommand. Это добавляет ещё 3–4 рабочих дня к разработке.

Тестирование

Для интеграционных тестов Magento используется \Magento\TestFramework\TestCase\AbstractController. Важно тестировать весь цикл: создание заказа → редирект → callback → статус заказа. Отдельно — рефанд через admin.