Кастомный модуль оплаты для 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 недели бесплатных доработок после сдачи проекта. Возможна постгарантийная поддержка по договорённости.
Мы документируем каждый шаг, чтобы вы могли самостоятельно вносить изменения.
Процесс и сроки
- Аналитика (2–4 часа): изучаем документацию шлюза, согласовываем требования, оцениваем риски.
- Проектирование (1 день): схема интеграции, определение необходимых хуков, прототип модуля.
- Разработка (2–3 дня): написание кода с использованием Symfony-компонентов и Dependency Injection, реализация API-клиента и хуков, написание PHPUnit-тестов.
- Тестирование (1 день): интеграционное тестирование с песочницей шлюза, проверка всех сценариев (успех, отказ, возврат).
- Деплой (2 часа): установка на staging, затем на production с помощью CI/CD.
Каждый этап завершается демонстрацией заказчику. Мы используем Git для контроля версий и Code Review для обеспечения качества. Срок разработки — от 2 до 5 рабочих дней в зависимости от сложности шлюза. Стоимость рассчитывается индивидуально и включает аудит текущей конфигурации магазина.
Свяжитесь с нами для оценки вашего проекта — это займет 2 часа. Получите консультацию по интеграции — это бесплатно. Закажите интеграцию и мы настроим прием платежей без головной боли.
Доверие и гарантии
Мы интегрировали платежные шлюзы для 50+ магазинов на PrestaShop. Опыт работы с платформой — более 5 лет. На все модули предоставляется гарантия 3 месяца. Наши инженеры всегда на связи.







