post

Create a Payment Gateway Module for Opencart

19th Apr, 2022
Anton Drobyshev
Anton Drobyshev

We begin with creating a module structure, it is quite big:

  • upload
    • admin
      • controller
        • extension
          • payment
            • gateway_module.php
      • language
        • en-gb
          • extension
            • payment
              • gateway_module.php
      • model
        • extension
          • payment
            • gateway_module.php
      • view
        • template
          • extension
            • payment
              • gateway_module.php
              • gateway_module_order.twig
    • catalog
      • controller
        • extension
          • payment
            • gateway_module.php
      • language
        • en-gb
          • extension
            • payment
              • gateway_module.php
      • model
        • extension
          • payment
            • gateway_module.php
      • view
        • theme
          • default
            • template
              • extension
                • payment
                  • gateway_module.php

Backend part

Create a controller for our payment module on the following path upload/admin/controller/extension/payment/gateway_module.php:

<?php
class ControllerExtensionPaymentGatewayModule extends Controller
{
   /**
    * Settings page
    */
   public function index()
   {
      $this->load->language('extension/payment/gateway_module');
      $this->document->setTitle($this->language->get('heading_title'));

      // Load models
      $this->load->model('localisation/order_status');
      $this->load->model('setting/setting');

      if (($this->request->server['REQUEST_METHOD'] == 'POST')) {
         $this->model_setting_setting->editSetting('payment_gateway_module', $this->request->post);
         $this->session->data['success'] = $this->language->get('text_success');
         $this->response->redirect($this->url->link('extension/payment/gateway_module', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true));
      }

      // Strings
      $data['heading_title'] = $this->language->get('heading_title');
      $data['text_edit']     = $this->language->get('text_edit');
      $data['text_enabled']  = $this->language->get('text_enabled');
      $data['text_disabled'] = $this->language->get('text_disabled');

      // Actions
      $data['action'] = $this->url->link('extension/payment/gateway_module', 'user_token=' . $this->session->data['user_token'], true);
      $data['cancel'] = $this->url->link('extension/extension', 'user_token=' . $this->session->data['user_token'], true);

      // Breadcrumbs list
      $data['breadcrumbs'] = [];

      $data['breadcrumbs'][] = [
         'text' => $this->language->get('text_home'),
         'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
      ];

      $data['breadcrumbs'][] = [
         'text' => $this->language->get('text_extension'),
         'href' => $this->url->link('extension/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true)
      ];

      $data['breadcrumbs'][] = [
         'text' => $this->language->get('heading_title'),
         'href' => $this->url->link('extension/payment/gateway_module', 'user_token=' . $this->session->data['user_token'], true)
      ];

      // Errors
      if (isset($this->error['warning'])) {
         $data['error_warning'] = $this->error['warning'];
      }

      // Blocks
      $data['header']      = $this->load->controller('common/header');
      $data['column_left'] = $this->load->controller('common/column_left');
      $data['footer']      = $this->load->controller('common/footer');

      $this->response->setOutput($this->load->view('extension/payment/gateway_module', $data));
   }

   /**
    * Method that returns payment information in the order admin panel
    */
   public function order()
   {
      $order_id = $this->request->get['order_id'];
      $this->load->model('extension/payment/gateway_module');

      // Make sure that the order exists in our database
      $order_info = $this->model_extension_payment_gateway_module->getOrder($order_id);

      if (!$order_info) {
         return false;
      }
      // Prepare our $data array for View
      $data = $order_info;
      $this->load->language('extension/payment/gateway_module');

      return $this->load->view('extension/payment/gateway_module_order', $data);
   }

   /**
    * Method that will be triggered on module installation
    */
   public function install()
   {
      // Load the module
      $this->load->model('extension/payment/gateway_module');
      // Initializing database table creation
      $this->model_extension_payment_gateway_module->install();
   }
}

After that let’s create a Model on the path upload/admin/model/extension/payment/gateway_module.php. Here we are going to specify methods to get order data and method to create the table in our database.

<?php

class ModelExtensionPaymentGatewayModule extends Model
{
	/**
	 * Create a table in the database to store orders payed by our payment module
	 */
	public function install()
	{
		$this->db->query("
          CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "gateway_module_order` (
           `gateway_module_order_id` INT(11) NOT NULL AUTO_INCREMENT,
           `order_id` INT(11) NOT NULL,
           `transaction_id` VARCHAR(50),
           `confirmed` INT(1) DEFAULT 0,
           `refund_status` INT(1) DEFAULT 0,
           `cancel_status` INT(1) DEFAULT 0,
           `refund_amount` DECIMAL( 10, 2 ) DEFAULT 0,
           `grand_total` DECIMAL( 10, 2 ) NOT NULL,
           `date_added` DATETIME NOT NULL,
           `date_modified` DATETIME NOT NULL,
           PRIMARY KEY (`gateway_module_order_id`)
          ) ENGINE=MyISAM DEFAULT COLLATE=utf8_general_ci;");
	}

	public function getOrder($order_id)
	{
		$qry = $this->db->query("SELECT * FROM `" . DB_PREFIX . "gateway_module_order` WHERE `order_id` = '" . (int)$order_id . "' LIMIT 1");

		if ($qry->num_rows) {
			return $qry->row;
		}
		return false;
	}

	public function updateConfirmed($order_id, $status)
	{
		$this->db->query("UPDATE `" . DB_PREFIX . "gateway_module_order` SET `confirmed` = '" . (int)$status . "' WHERE `order_id` = '" . (int)$order_id . "'");
	}

	public function updateRefundedAmount($order_id, $amount)
	{
		$this->db->query("UPDATE `" . DB_PREFIX . "gateway_module_order` SET `refund_amount` = '" . (int)$amount . "' WHERE `order_id` = '" . (int)$order_id . "'");
	}

	public function updateCancelStatus($order_id)
	{
		$this->db->query("UPDATE `" . DB_PREFIX . "gateway_module_order` SET `cancel_status` = 1 WHERE `order_id` = '" . (int)$order_id . "'");
	}

	public function updateRefundStatus($order_id)
	{
		$this->db->query("UPDATE `" . DB_PREFIX . "gateway_module_order` SET `refund_status` = 1 WHERE `order_id` = '" . (int)$order_id . "'");
	}
}

Are you looking for a full stack development service company?

Let’s work together
graph 2

Now let’s create a language file on the path upload/admin/language/en-gb/extension/payment/gateway_module.php:

<?php
$_['heading_title']  = 'Custom Payment Gateway';
$_['text_success']   = 'Success';
$_['text_edit']      = 'Custom Payment Gateway - Settings';
$_['text_extension'] = 'Extensions';

The last step for our backend part – we should create 2 template files:

  1. upload/admin/view/template/extension/payment/gateway_module.twig This file displays a settings page of our payment module.
{{ header }}
{{ column_left }}
<div id="content">
	<div class="page-header">
		<div class="container-fluid">
			<div class="pull-right">
				<button type="submit" form="form-payment" data-toggle="tooltip" title="{{ button_save }}"
						class="btn btn-primary"><i class="fa fa-save"></i></button>
				<a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i
						class="fa fa-reply"></i></a>
			</div>
			<h1>{{ heading_title }}</h1>
			<ul class="breadcrumb">
				{% for breadcrumb in breadcrumbs %}
				<li>
					<a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a>
				</li>
				{% endfor %}
			</ul>
		</div>
	</div>
	<div class="container-fluid">
		{% if error_warning %}
		<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }}
			<button type="button" class="close" data-dismiss="alert">×</button>
		</div>
		{% endif %}
		<div class="panel panel-default">
			<div class="panel-heading">
				<h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3>
			</div>
			<div class="panel-body">
				<form action="{{ action }}"
					  method="post"
					  enctype="multipart/form-data"
					  id="form-cod"
					  class="form-horizontal">
					<div class="form-group">
						<label class="col-sm-2 control-label"
							   for="input-status">{{ entry_status }}</label>
						<div class="col-sm-10">
							<select name="payment_gateway_module_status"
									id="input-status"
									class="form-control">
								{% if payment_gateway_module_status %}
								<option value="1"
										selected="selected">{{ text_enabled }}
								</option>
								<option value="0">{{ text_disabled }}</option>
								{% else %}
								<option value="1">{{ text_enabled }}</option>
								<option value="0"
										selected="selected">{{ text_disabled }}
								</option>
								{% endif %}
							</select>
						</div>
					</div>
					<div class="form-group">
						<label class="col-sm-2 control-label"
							   for="gateway_module_public_key">{{ public_key_text }}</label>
						<div class="col-sm-10">
							<input type="text"
								   name="payment_gateway_module_public_key"
								   value="{{ payment_gateway_module_public_key }}"
								   placeholder="{{ public_key_text }}"
								   id="input-total"
								   class="form-control"/>
						</div>
					</div>
				</form>
			</div>
		</div>
	</div>
</div>
{{ footer }}

2. upload/admin/view/template/extension/payment/gateway_module_order.twig This file displays the payment information in order details page

<div id="payment_gateway">
	<h2>{{ text_info }}</h2>
	<div class="form-horizontal">
		<div class="form-group">
			<label class="col-sm-2"
				   for="input-vendor">{{ transaction_id_title }}</label>
			<div class="col-sm-10">
				<span>{{ transaction_id }}</span>
			</div>
		</div>
		<div class="form-group">
			<label class="col-sm-2"
				   for="input-vendor">{{ text_status }}</label>
			<div class="col-sm-10">
				<span>{{ status }}</span>
			</div>
		</div>
	</div>
	<hr>
	<h2>{{ text_refund }}</h2>
	<div class="form-horizontal">
		<div class="form-group">
			<label class="col-sm-2"
				   for="input-vendor">{{ total_refunded_title }}</label>
			<div class="col-sm-10">
				<span id="refund_amount">{{ refund_amount }}</span>
			</div>
		</div>
	</div>
</div>

Frontend part

Here we start with the controller like we did on the backend part upload/catalog/controller/extension/payment/gateway_module.php:

<?php

class ControllerExtensionPaymentGatewayModule extends Controller
{
	public function index()
	{
		$this->load->language('extension/payment/gateway_module');
		$this->load->model('checkout/order');
		$data['button_confirm'] = $this->language->get('button_confirm');
		
		return $this->load->view('extension/payment/gateway_module', $data);
	}
	
	/**
	 * Method that sends order data to our payment API
	 */
	public function send()
	{
		$this->load->model('checkout/order');
		
		$order_id   = $this->session->data['order_id'];
		$return_url = $this->url->link('extension/payment/gateway_module/callback', '', true) . '&order_id=' . $order_id . '&id=%CKKEY%';
		$cancel_url = $this->url->link('checkout/checkout', '', true);
		
		if ($this->customer->isLogged()) {
			$customer_id = $this->customer->getId();
		}
		
		$json = [
			$gateway_module_data['order_items'],
			$gateway_module_data['billing_address'],
			$gateway_module_data['shipping_address'],
			$gateway_module_data['charges'],
			$order_id,
			$customer_id,
			$return_url,
			$cancel_url, 'redirect'
		];
		
		$this->response->addHeader('Content-Type: application/json');
		$this->response->setOutput(json_encode($json));
	}
	
	/**
	 * Callback method that will catch response from payment API and handle it
	 */
	public function callback()
	{
		$gateway_data      = $_POST['gateway_data'];
        $order_id          = $gateway_data['order_id'];
        $order_info        = $gateway_data['order_info'];
        $complete_checkout = $gateway_data['is_completed'];
    if ($complete_checkout) {
		$this->model_checkout_order->addOrderHistory($order_id, 2, 'Order is created via our custom gateway', true);
		$this->model_extension_payment_gateway_module->addOrder($order_data);
	}

    //Redirect the user to order success page after handleing the payment
    $this->response->redirect($this->url->link('checkout/success', '', true));
  }
}
layout 3
Everland Music

Everland Music

Online music store

Read case study

After that let’s create a Model on the path upload/catalog/language/en-gb/extension/payment/gateway_module.php:

<?php

class ModelExtensionPaymentGatewayModule extends Model
{
	/**
	 * This is the most important method in our model
	 * Without this method, our custom payment gateway won't show up in the checkout
	 */
	public function getMethod($address, $total)
	{
		$method_data = [
			'code'       => 'gateway_module',
			'title'      => 'Custom Payment Gateway',
			'terms'      => '',
			'sort_order' => $this->config->get('cod_sort_order')
		];
		return $method_data;
	}

	/**
	 * Method for adding information about the transaction
	 */
	public function addOrder($order_data)
	{
		$this->db->query("INSERT into `" . DB_PREFIX . "gateway_module_order` SET `transaction_id` = '" . $this->db->escape($order_data['transaction_id']) . "', `order_id` = '" . $this->db->escape($order_data['order_id']) . "', `date_added` = now(), `date_modified` = now(), `confirmed` = '" . $this->db->escape($order_data['confirmed']) . "', `grand_total` = '" . $this->db->escape($order_data['grand_total']) . "'");
	}
}

Now we should create a language file

<?php
$_['button_confirm'] = 'Pay';

Then we should create a template which will be displayed in the checkout after the user chose our custom payment gateway. Here we can display any required fields like Card Number, Expiry date and CVC. Or we can just show a button, which will submit our form via AJAX and make a redirect to the external payment page.

<div class="buttons">
	<div class="pull-right">
		<input type="button" value="{{ button_confirm }}" id="button-confirm" class="btn btn-primary"/>
	</div>
</div>
<script type="text/javascript">
	/**
	 * Call our send() controller on button click. Get a URL in the response
	 * and redirect our customer there
	 */
	$('#button-confirm').bind('click', function () {
		$.ajax({
			url: 'index.php?route=extension/payment/gateway_module/send',
			type: 'post',
			data: $('#payment :input'),
			dataType: 'json',
			cache: false,
			beforeSend: function () {
				$('#button-confirm').button('loading');
			},
			complete: function () {
				$('#button-confirm').button('reset');
			},
			success: function (json) {
				if (json['error']) {
					alert(json['error']);
				}
				if (json['redirect']) {
					location = json['redirect'];
				}
			}
		});
	});
</script>

After the customers pay for the order they will be redirected to our callback method on the path index.php?route=extension/payment/gateway_module/callback, where we will complete the order and redirect users to Thank you page.

In case you would like to use any additional libraries, for example include some SDK, then you should locate the files with classes in the following format:

  • uploads
    • system
      • gatewaySDK (SDK directory)
        • SdkClass.php (Our SDK class)

After that we can use our class via the namespace: use gatewaySDK\\SdkClass;

Rate this article:
5 / 5 2 votes
Anton Drobyshev
Anton Drobyshev

Get our regular digests with
checklists, guides, and best back-end and front-end practices.

Subscribe

Comments / 2

Leave a comment

Leave a comment

Let's get acquainted!

This website uses cookies.