Интеграция кастомного модуля оплаты PrestaShop с Stripe, PayPal, Fondy

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

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Интеграция кастомного модуля оплаты PrestaShop с Stripe, PayPal, Fondy
Средний
~2-3 дня
Часто задаваемые вопросы

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

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

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

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

Кастомный модуль оплаты для PrestaShop: как избежать ошибок интеграции

При интеграции стороннего платежного шлюза в PrestaShop многие сталкиваются с проблемой некорректной обработки вебхуков. Типичная ошибка — статус заказа не обновляется после callback из-за отсутствия проверки подписи HMAC. Если не валидировать callback, можно получить «подвешенные» заказы, когда деньги списались, а статус остался «ожидание». Другая распространенная проблема — неправильная передача суммы в минимальных единицах валюты (копейках/центах), что приводит к расхождениям в учете. На примере интеграции MyPay разберём правильную настройку Payment API, чтобы этого избежать.

Кроме того, важно корректно формировать URL callback с учётом безопасности. Мы используем только POST-запросы с подписью в заголовке X-Signature. Такой подход исключает подмену данных. Дополнительно реализуем idempotency key, чтобы повторный callback не менял статус заказа.

Как избежать ошибок при интеграции платежного шлюза в PrestaShop?

Ключевой момент — валидация заказа до редиректа и обработка webhook с HMAC-подписью. Для предотвращения timing attack используем hash_equals при проверке подписи. В модуле нужно зарегистрировать три хука: paymentOptions, paymentReturn и actionOrderStatusUpdate. Последний обязателен для автоматических возвратов через админку. Идемпотентность достигается проверкой уникального ключа в callback. Помимо этого, необходимо проверять текущий статус заказа: если он уже оплачен, повторный callback не должен менять статус.

Почему кастомный модуль лучше готового плагина?

Параметр Кастомный модуль Готовый плагин
Гибкость 100% под бизнес-логику Ограничен настройками
Безопасность Полный контроль кода Возможны уязвимости
Поддержка Прямая связь с разработчиком Через тикеты вендора
Производительность Оптимизирован под ваш стек Часто избыточный код

Кастомная разработка окупается, если у вас нестандартная валюта, несколько складов или сложные сценарии возвратов. Например, в готовом плагине для Fondy нет поддержки частичных возвратов. В кастомном модуле мы реализовали API вызова возврата через админку PrestaShop. Готовые плагины часто не поддерживают мультивалютность и не адаптированы под специфику магазина. В кастомном модуле мы передаём код валюты (ISO 4217) в запросе к шлюзу, что особенно важно для регионов с разными валютами.

Какие типичные ошибки допускают при интеграции?

Самая частая — игнорирование проверки HMAC. Без неё злоумышленник может подделать callback и изменить статус заказа. Вторая — неправильная обработка ошибок API: если шлюз вернул 500, модуль должен корректно откатить заказ, а не оставлять его в подвешенном состоянии. Третья — некорректная работа с таймаутами: при долгом ответе шлюза пользователь видит бесконечную загрузку. Мы используем async-запросы с таймаутом 30 секунд и показываем сообщение об ошибке.

Какие шлюзы мы интегрируем?

Мы работаем со Stripe, PayPal, Fondy, LiqPay, а также с любыми другими, предоставляющими REST API. В таблице ниже — сравнение по ключевым параметрам.

Шлюз Комиссия (примерно) Поддержка 3DS Webhook HMAC SDK для PHP
Stripe 2.9% + 0.30€ Да Да stripe/stripe-php
PayPal 3.49% + 0.30€ Да Да paypal/rest-api-sdk-php
Fondy 2.3% Да Да fondy/fondy-php
LiqPay 2.5% Да Да liqpay/liqpay-php

Комиссия указана ориентировочно, точные тарифы уточняйте у провайдера.

Структура и реализация модуля

Для регистрации модуля в системе оплаты мы используем хуки paymentOptions, paymentReturn и actionOrderStatusUpdate. Дополнительно можно подключить displayPaymentTop для вывода информации перед выбором оплаты.

Структура файлов

modules/mypay/
├── controllers/front/
│   ├── payment.php     # Инициализация платежа
│   └── callback.php    # Webhook от провайдера
├── views/templates/front/
│   └── payment_infos.tpl
├── mypay.php           # Основной класс модуля
└── logo.png

Основной класс модуля

class MyPay extends PaymentModule
{
    public function __construct()
    {
        $this->name          = 'mypay';
        $this->tab           = 'payments_gateways';
        $this->version       = '1.0.0';
        $this->author        = 'Your Company';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = ['min' => '8.0.0', 'max' => _PS_VERSION_];
        $this->bootstrap     = true;

        parent::__construct();

        $this->displayName = $this->trans('MyPay', [], 'Modules.Mypay.Admin');
        $this->description = $this->trans('Оплата картой через MyPay', [], 'Modules.Mypay.Admin');
    }

    public function install(): bool
    {
        return parent::install()
            && $this->registerHook('paymentOptions')
            && $this->registerHook('paymentReturn')
            && $this->registerHook('actionOrderStatusUpdate');
    }

    public function hookPaymentOptions(array $params): array
    {
        if (!$this->active) return [];

        $this->context->smarty->assign([
            'mypay_action_url' => $this->context->link->getModuleLink('mypay', 'payment', [], true),
        ]);

        $option = new \PrestaShop\PrestaShop\Core\Payment\PaymentOption();
        $option->setCallToActionText($this->trans('Оплата картой', [], 'Modules.Mypay.Shop'))
               ->setAction($this->context->link->getModuleLink('mypay', 'payment', [], true))
               ->setAdditionalInformation(
                   $this->fetch('module:mypay/views/templates/front/payment_infos.tpl')
               );

        return [$option];
    }
}

Контроллер инициализации платежа

class MyPayPaymentModuleFrontController extends ModuleFrontController
{
    public function postProcess(): void
    {
        $cart = $this->context->cart;
        if (!$this->module->checkCurrency($cart)) {
            Tools::redirect('index.php?controller=order');
        }

        $total    = (int) round($cart->getOrderTotal(true) * 100);
        $customer = new Customer($cart->id_customer);
        $currency = new Currency($cart->id_currency);

        $this->module->validateOrder(
            $cart->id,
            Configuration::get('MYPAY_OS_PENDING'),
            $cart->getOrderTotal(true),
            $this->module->displayName,
            null,
            [],
            (int) $currency->id,
            false,
            $customer->secure_key
        );

        $orderId = Order::getIdByCartId($cart->id);
        $client  = new MyPayApiClient(
            Configuration::get('MYPAY_API_KEY'),
            Configuration::get('MYPAY_SECRET_KEY')
        );

        $payment = $client->createPayment([
            'amount'       => $total,
            'currency'     => $currency->iso_code,
            'order_id'     => $orderId,
            'callback_url' => $this->context->link->getModuleLink('mypay', 'callback', [], true),
            'success_url'  => $this->context->link->getPageLink('order-confirmation', true, null, [
                'id_cart'    => $cart->id,
                'id_module'  => $this->module->id,
                'id_order'   => $orderId,
                'key'        => $customer->secure_key,
            ]),
        ]);

        $order = new Order($orderId);
        $order->reference = $payment['payment_id'];
        $order->save();

        Tools::redirect($payment['payment_url']);
    }
}

Обработка callback

class MyPayCallbackModuleFrontController extends ModuleFrontController
{
    public function postProcess(): void
    {
        $raw    = file_get_contents('php://input');
        $data   = json_decode($raw, true);
        $secret = Configuration::get('MYPAY_SECRET_KEY');

        if (!hash_equals(hash_hmac('sha256', $raw, $secret), $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
            http_response_code(403);
            exit;
        }

        $order = Order::getByReference($data['payment_id'])->getFirst();
        if (!$order) {
            http_response_code(404);
            exit('Order not found');
        }

        $statusMap = [
            'succeeded' => Configuration::get('MYPAY_OS_PAID'),
            'failed'    => Configuration::get('PS_OS_ERROR'),
            'cancelled' => Configuration::get('PS_OS_CANCELED'),
        ];

        if (isset($statusMap[$data['status']])) {
            $history = new OrderHistory();
            $history->id_order = $order->id;
            $history->changeIdOrderState((int) $statusMap[$data['status']], $order);
            $history->addWithemail(true);
        }

        http_response_code(200);
        echo 'OK';
        exit;
    }
}

Что входит в работу

  • Документация: описание установки, конфигурации и API-методов модуля.
  • Доступы: передача исходного кода, файлов модуля и дампов БД (при необходимости).
  • Поддержка: 2 недели бесплатных доработок после сдачи проекта. Возможна постгарантийная поддержка по договорённости.

Мы документируем каждый шаг, чтобы вы могли самостоятельно вносить изменения.

Процесс и сроки

  1. Аналитика (2–4 часа): изучаем документацию шлюза, согласовываем требования, оцениваем риски.
  2. Проектирование (1 день): схема интеграции, определение необходимых хуков, прототип модуля.
  3. Разработка (2–3 дня): написание кода с использованием Symfony-компонентов и Dependency Injection, реализация API-клиента и хуков, написание PHPUnit-тестов.
  4. Тестирование (1 день): интеграционное тестирование с песочницей шлюза, проверка всех сценариев (успех, отказ, возврат).
  5. Деплой (2 часа): установка на staging, затем на production с помощью CI/CD.

Каждый этап завершается демонстрацией заказчику. Мы используем Git для контроля версий и Code Review для обеспечения качества. Срок разработки — от 2 до 5 рабочих дней в зависимости от сложности шлюза. Стоимость рассчитывается индивидуально и включает аудит текущей конфигурации магазина.

Свяжитесь с нами для оценки вашего проекта — это займет 2 часа. Получите консультацию по интеграции — это бесплатно. Закажите интеграцию и мы настроим прием платежей без головной боли.

Доверие и гарантии

Мы интегрировали платежные шлюзы для 50+ магазинов на PrestaShop. Опыт работы с платформой — более 5 лет. На все модули предоставляется гарантия 3 месяца. Наши инженеры всегда на связи.

Интеграция платёжных систем: ЮKassa, Stripe, PayPal, Apple Pay, Google Pay

Конверсия упала на 12% сразу после редизайна. Команда запулила новый SPA-чекаут на Vue 3, забыв про обработку fallback-сценариев. Sentry зафиксировал шквал ошибок: Payment method not available, 3DS2 challenge flow failed, webhook signature verification failed. Пользователи бросали корзину на этапе выбора способа оплаты. Проверка показала, что Stripe Elements не получал корректный clientSecret после редиректа, а webhook-эндпоинт отвечал 500 из-за отсутствия идемпотентности. После замены checkout-формы на кастомную интеграцию с раздельным хранением event ID в Redis ошибки ушли, конверсия восстановилась за двое суток. Задача не в том, чтобы «подключить SDK» — платёжка требует синхронизации с требованиями банков, SCA в Европе и 54-ФЗ в России. Наш опыт — 7 лет интеграций для 50+ проектов, от интернет-магазинов до SaaS-платформ с миллионными оборотами.

Что входит в работу под ключ

  • Аудит текущего payment flow и требований (валюты, фискализация, подписки).
  • Выбор провайдера с учётом географии и бизнес-модели.
  • Backend-интеграция (Laravel/Node.js/Go) с обработкой webhook'ов, идемпотентностью и ретраями.
  • Frontend-виджет (Stripe Elements / ЮKassa SDK) с поддержкой Apple Pay и Google Pay.
  • Тестирование всех сценариев: успех, отказ, 3DS, возвраты, чек коррекции.
  • Мониторинг первых транзакций и документация.

Оценим проект за 1 день — для получения консультации напишите в чат.

Сравнение провайдеров: что выбрать

Критерий ЮKassa Stripe PayPal
Валюты RUB только 135+ 25+
Фискализация 54-ФЗ Встроена Нет (нужен ОФД) Нет
Поддержка Apple/Google Pay Через SDK Через PaymentElement Через Braintree
Комиссия за транзакцию 2.5–4% 2.9% + $0.30 2.99% + $0.49
Рекуррентные платежи Через автоплатежи Stripe Billing Reference Transactions
PCI DSS SAQ A (токены) SAQ A (Elements) SAQ A (токены)

Stripe выигрывает по гибкости: 135+ валют против одной у ЮKassa. Но для РФ с 54-ФЗ и СБП ЮKassa в 3 раза быстрее в интеграции — не нужен внешний ОФД. Для подписок Stripe Billing — готовый engine с trial'ами и email-уведомлениями в 2 клика.

Как выбрать подходящего провайдера?

Ключевых точек три. Где живут ваши клиенты? Только РФ — ЮKassa, глобально — Stripe. Нужна ли фискализация по 54-ФЗ? Да — ЮKassa, иначе Stripe + облачный ОФД. Планируете ли подписки? Да — Stripe Billing как эталон, ЮKassa требует собственной логики с автоплатежами. Экономия на комиссиях при выборе правильного провайдера — до 1.5% с оборота. Для проекта с 2 млн ₽ в месяц это 360 000 ₽ в год.

Где прячутся реальные сложности

Подключить тестовый режим — час. Правильно обработать все сценарии — несколько недель.

Webhook надёжность. Webhook может не дойти — сервер недоступен, таймаут, сеть. Провайдер повторяет с экспоненциальным backoff (Stripe — до 3 дней). Обработчик обязан быть идемпотентным: если payment.succeeded придёт дважды с одним payment_id, заказ обновится только раз. Реализуется через хранение event ID в Redis с TTL.

3DS2 и redirect flow. При оплате картой с 3DS2 пользователь уходит на страницу банка, затем возвращается по return_url. За это время сессия могла истечь, корзина очиститься. Статус проверяем не по query-параметрам, а прямым запросом к API провайдера при возврате.

Частичные возвраты и чеки. Клиент вернул часть товаров — нужен чек коррекции (ФНС) и частичный refund в ЮKassa. Stripe делает partial_refund нативно. В обоих случаях синхронизация статусов между платёжкой, БД и складом — отдельная задача.

Валютные ограничения. ЮKassa — только рубли. Если клиент из РФ платит в евро через Stripe, конвертация идёт через его банк, и вы не управляете курсом.

Почему webhook'и требуют идемпотентности?

Webhook может быть доставлен дважды из-за сетевых таймаутов или повторных попыток провайдера. Без идемпотентности второй вызов вызовет дублирование заказа или ошибочное начисление. Решение — сохранять уникальный ID события (например, Stripe event id + timestamp) в Redis с TTL 24 часа и проверять перед обработкой. Если ID уже существует — возвращаем 200, не выполняя бизнес-логику. Типичные ошибки при интеграции webhook'ов: не проверять подпись HMAC (любой может отправить фальшивый payment.succeeded), не использовать очередь (обработчик блокирует ответ — провайдер считает фейлом и шлёт повторно), не сохранять event ID (дубликаты рассинхронизируют статусы).

Как строим интеграцию

Архитектура. Никогда не храним данные карт — только токены провайдера. Flow: Order в БД → Payment Intent → редирект/виджет → webhook подтверждает → обновляем статус. База истины — статус в платёжной системе.

Для Laravel используем stripe/stripe-php или yookassa-sdk. Webhook — отдельный контроллер с VerifyCsrfToken исключением, проверка подписи в первой строке, Queue job для бизнес-логики.

Для Next.js/React — @stripe/stripe-js + @stripe/react-stripe-js. PaymentElement включает Apple/Google Pay автоматически. Пример:

const stripe = await stripePromise;
const { error } = await stripe.confirmPayment({
  elements,
  confirmParams: { return_url: 'https://example.com/order/thank-you' },
});

Тестирование. Stripe CLI: stripe listen --forward-to localhost:8000/webhook. Тест-карты для всех сценариев (3DS, decline, insufficient funds). Cypress-тест checkout flow в CI — обязательная гарантия стабильности.

Мы отлаживали интеграцию Stripe Billing для SaaS с 50 000 подписчиков. Проблема возникла с обработкой invoice.payment_succeeded: фронтенд обновлял подписку сразу после редиректа, но webhook мог задержаться на 10 секунд, и статус перезаписывался на incomplete. Решение — добавить polling API с проверкой статуса инвойса до показа успешной страницы. Это снизило количество ошибочных отписок на 18%.

Процесс и сроки

Аудит → выбор провайдера → backend → frontend → тесты → деплой → мониторинг.

Сценарий Срок
Один провайдер (ЮKassa или Stripe), базовый flow 1–2 недели
Несколько методов оплаты + Apple/Google Pay 2–4 недели
Мультивалютность + частичные возвраты + фискализация 4–8 недель
SaaS подписки через Stripe Billing 3–6 недель

Стоимость рассчитывается индивидуально. Закажите интеграцию, и ваш checkout не упадёт при следующем обновлении.

Ссылки:

Гарантируем: 7 лет опыта, 50+ успешных интеграций. Свяжитесь с нами для аудита вашего checkout'а — мы оценим проект и подберём оптимального провайдера.