PrestaShop Payment Gateway Integration
PrestaShop 8.x uses an updated Payment API compared to 1.7.x — main classes are the same, but added PaymentOption with iframe support. Custom payment module is registered via hooks and implements several required methods. Development timeline — 2–4 working days.
Module Structure
modules/mypay/
├── controllers/front/
│ ├── payment.php # Payment initialization
│ └── callback.php # Webhook from provider
├── views/templates/front/
│ └── payment_infos.tpl
├── mypay.php # Main module class
└── logo.png
Main Module Class
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('Card payment via 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('Card Payment', [], 'Modules.Mypay.Shop'))
->setAction($this->context->link->getModuleLink('mypay', 'payment', [], true))
->setAdditionalInformation(
$this->fetch('module:mypay/views/templates/front/payment_infos.tpl')
);
return [$option];
}
}
Payment Initialization Controller
// controllers/front/payment.php
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);
// Validate order before redirect
$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 Processing
// controllers/front/callback.php
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;
}
}
Refunds via Admin
PrestaShop doesn't call module automatically on refund — need to subscribe to actionOrderStatusUpdate hook:
public function hookActionOrderStatusUpdate(array $params): void
{
$newStatus = $params['newOrderStatus'];
$order = $params['order'];
if ($newStatus->id !== (int) Configuration::get('PS_OS_REFUND')) {
return;
}
$client = new MyPayApiClient(
Configuration::get('MYPAY_API_KEY'),
Configuration::get('MYPAY_SECRET_KEY')
);
$client->refund($order->reference, (int) round($order->total_paid * 100));
}
Configuration in Admin
Module configuration page (getContent()) uses HelperForm to render form with API key fields, test mode, order status mapping. Order statuses (Pending, Paid, Failed) are created during module installation and stored in ps_order_state table.







