OpenCart Custom Payment Plugin Development
OpenCart 3.x and 4.x have different extension architecture. In 3.x — classic MVC structure in catalog/ folder. In 4.x — Event system + namespaced classes, structure closer to modern frameworks. Custom plugin written for specific version.
Development takes 3–5 business days. For support both versions — multiply by 1.5.
OpenCart 4.x Plugin Structure
extension/mypay/
├── admin/
│ ├── controller/payment/mypay.php
│ ├── language/en-gb/payment/mypay.php
│ ├── language/ru-ru/payment/mypay.php
│ └── view/template/payment/mypay.twig
├── catalog/
│ ├── controller/payment/mypay.php
│ ├── language/ru-ru/payment/mypay.php
│ └── view/template/payment/mypay.twig
└── install.json
Catalog: Payment Controller
namespace Opencart\Catalog\Controller\Extension\Mypay\Payment;
class Mypay extends \Opencart\System\Engine\Controller
{
public function index(): string
{
$this->load->language('extension/mypay/payment/mypay');
$data['action'] = $this->url->link(
'extension/mypay/payment/mypay.confirm',
'language=' . $this->config->get('config_language')
);
$data['amount'] = $this->currency->format($this->session->data['mypay_total'], 'RUB', 1, false);
$data['order_id'] = $this->session->data['order_id'];
return $this->load->view('extension/mypay/payment/mypay', $data);
}
public function confirm(): void
{
$this->load->model('checkout/order');
$orderId = (int) $this->session->data['order_id'];
$order = $this->model_checkout_order->getOrder($orderId);
$total = (int) round($order['total'] * 100);
$client = new \Mypay\ApiClient(
$this->config->get('payment_mypay_api_key'),
$this->config->get('payment_mypay_secret_key')
);
$payment = $client->createPayment([
'amount' => $total,
'currency' => 'RUB',
'order_id' => $orderId,
'callback_url' => HTTPS_CATALOG . 'index.php?route=extension/mypay/payment/mypay.callback',
'success_url' => HTTPS_CATALOG . 'index.php?route=checkout/success',
'fail_url' => HTTPS_CATALOG . 'index.php?route=checkout/failure',
]);
if (!$payment['payment_url']) {
$this->session->data['error'] = 'Payment creation error';
$this->response->redirect(HTTPS_CATALOG . 'index.php?route=checkout/checkout');
return;
}
$this->model_checkout_order->addHistory(
$orderId,
$this->config->get('payment_mypay_pending_status'),
'MyPay Payment ID: ' . $payment['payment_id'],
false
);
$this->response->redirect($payment['payment_url']);
}
public function callback(): void
{
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$secret = $this->config->get('payment_mypay_secret_key');
$expectedSig = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expectedSig, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
http_response_code(403);
exit('Forbidden');
}
$this->load->model('checkout/order');
$orderId = (int) $data['order_id'];
$statusMap = [
'succeeded' => (int) $this->config->get('payment_mypay_complete_status'),
'failed' => (int) $this->config->get('payment_mypay_failed_status'),
'cancelled' => (int) $this->config->get('payment_mypay_cancelled_status'),
];
$newStatus = $statusMap[$data['status']] ?? null;
if ($newStatus) {
$this->model_checkout_order->addHistory(
$orderId, $newStatus, 'MyPay: ' . $data['status'], $data['status'] === 'succeeded'
);
}
http_response_code(200);
echo 'OK';
exit;
}
}
Admin: Plugin Settings
namespace Opencart\Admin\Controller\Extension\Mypay\Payment;
class Mypay extends \Opencart\System\Engine\Controller
{
public function save(): void
{
$this->load->language('extension/mypay/payment/mypay');
if (!$this->user->hasPermission('modify', 'extension/mypay/payment/mypay')) {
$this->response->setOutput(json_encode(['error' => ['warning' => 'No permission']]));
return;
}
$keys = ['status', 'api_key', 'secret_key', 'testmode', 'pending_status', 'complete_status', 'failed_status'];
foreach ($keys as $key) {
$this->config->set('payment_mypay_' . $key, $this->request->post['payment_mypay_' . $key] ?? '');
$this->model_setting_setting->editSettingValue('payment_mypay', 'payment_mypay_' . $key, $this->request->post['payment_mypay_' . $key] ?? '');
}
$this->response->setOutput(json_encode(['success' => 'Settings saved']));
}
}
install.json
{
"name": "MyPay Payment Gateway",
"version": "1.0.0",
"author": "Your Company",
"link": "https://yourcompany.com",
"type": "payment",
"category": "payment"
}
OpenCart 3.x Specifics
In OpenCart 3.x different folder structure: upload/catalog/controller/payment/mypay.php. No namespace, classes called ControllerPaymentMypay. Installation via Extension Installer (zip-archive) or manually. Instead of install.json — upload/ layout.
Testing and Debugging
OpenCart logs errors in /system/storage/logs/. When developing callback — use ngrok for localhost access. Order statuses configured in admin → System → Order Statuses, their IDs plugged into plugin settings.







