A payment extension
A payment method is the last step of the checkout: the customer chooses how to pay and presses the confirm button. Each method is an extension of the type payment. It has more parts than a shipping method, because it not only appears in the list but also shows the button and finishes the order.
Files
extension/acme_hello/
├── admin/
│ ├── controller/payment/acme_pay.php settings page
│ ├── language/en-gb/payment/acme_pay.php
│ └── view/template/payment/acme_pay.twig
└── catalog/
├── model/payment/acme_pay.php is the method available
├── controller/payment/acme_pay.php button and confirmation
├── language/en-gb/payment/acme_pay.php
└── view/template/payment/acme_pay.twigThe settings page is built like the one of the module; the code of the settings is payment_acme_pay. Fields: payment_acme_pay_status, payment_acme_pay_sort_order and payment_acme_pay_order_status_id — the status a paid order gets.
The model
// extension/acme_hello/catalog/model/payment/acme_pay.php
namespace Opencart\Catalog\Model\Extension\AcmeHello\Payment;
class AcmePay extends \Opencart\System\Engine\Model
{
public function getMethods(array $address = []): array
{
$this->load->language('extension/acme_hello/payment/acme_pay');
$option['acme_pay'] = [
'code' => 'acme_pay.acme_pay',
'name' => $this->language->get('heading_title'),
];
return [
'code' => 'acme_pay',
'name' => $this->language->get('heading_title'),
'option' => $option,
'sort_order' => (int) $this->config->get('payment_acme_pay_sort_order'),
];
}
}The method returns the payment method with one option; an empty array hides it. This is the format of releases 4.0.2 and later. In 4.0.0 and 4.0.1 the method was called getMethod() and returned a flat array without options. Compare with extension/opencart/catalog/model/payment/cod.php of your copy.
The button
When the customer chooses the method, the checkout calls index() of the storefront controller and inserts the returned HTML:
// extension/acme_hello/catalog/controller/payment/acme_pay.php
namespace Opencart\Catalog\Controller\Extension\AcmeHello\Payment;
class AcmePay extends \Opencart\System\Engine\Controller
{
public function index(): string
{
$this->load->language('extension/acme_hello/payment/acme_pay');
$data['confirm'] = $this->url->link('extension/acme_hello/payment/acme_pay.confirm', 'language=' . $this->config->get('config_language'));
return $this->load->view('extension/acme_hello/payment/acme_pay', $data);
}
}{# extension/acme_hello/catalog/view/template/payment/acme_pay.twig #}
<form action="{{ confirm }}" method="post" data-oc-toggle="ajax">
<button type="submit" class="btn btn-primary">{{ button_confirm }}</button>
</form>Confirmation
public function confirm(): void
{
$this->load->language('extension/acme_hello/payment/acme_pay');
$json = [];
if (!isset($this->session->data['order_id'])) {
$json['error'] = $this->language->get('error_order');
}
if (!$json) {
$this->load->model('checkout/order');
$this->model_checkout_order->addHistory(
(int) $this->session->data['order_id'],
(int) $this->config->get('payment_acme_pay_order_status_id')
);
$json['redirect'] = $this->url->link('checkout/success', 'language=' . $this->config->get('config_language'), true);
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}Install the method in Extensions → Extensions, type Payments, and turn it on. At index.php?route=checkout/checkout choose it and press the button — the success page opens, and the order appears in Sales → Orders with the status from the settings.
Core methods also check in confirm() that the method saved in $this->session->data['payment_method'] is this one. The shape of that value differs between releases, so copy the check from cod.php of your copy.
A real payment gateway
With a bank or a payment service, confirm() does not place the order. The customer is sent to the page of the service, and the service later calls a separate method of your controller — a callback. That method checks the signature of the request and only then calls addHistory().
Common mistake
The order is marked as paid when the customer returns from the payment page to the shop. The return address can be opened by hand without paying. Mark the order as paid only in the callback from the service, after checking its signature and the amount.