Home Blog Page 18

How does the Store credit of Order total works in Opencart

In this Opencart user manual, we are showing how you can install the store credit extension, add the transaction amount to the customer, and how the customer can use the amount while checkout.

Install the store credit extension

Go to admin >> Extensions >> Extensions >> Choose the Extensions Type >> Order totals >> Then, install the Store Credit.

Order totals credit store

Once you installed the Store Credit then you can edit it and enable it and enter the sort order as you want to show on the cart/checkout page.

Store credit Opencart setting

Click Save and store credit order total extension is installed and configured.

Assign the amount to the customer through transactions

To add or assign an amount to the customer, go to admin >> Customers >> Customers list >> Edit the customer you want to add the amount >> then go to the Transactions tab >> Enter the description and amount you want to add for that customer and the amount is assigned to that customer.

Transactions store credit

How the customer will use the store credit assigned from the transactions?

Once the admin assigns or adds the amount from the transactions, then the customer can use that amount to their orders. Customers can view the Store credit amount from the transactions.

Customer Store Credit transactions

The customer can use these Store credits, which will be automatically decreased from their orders. You can see in the following how it decreased the amount in the shopping cart and confirm checkout page.

store credit shopping cart customers
checkout confirm store credit

In this way, you can set up the Store Credit and customers can use it. As always please don’t forget to post your questions or comments so that we can add extra topics. You can follow us at our Twitter account @rupaknpl and subscribe to our youtube channel Opencart tutorials and click opencart user manual for more docs. Similarly, keep on visiting the blog where you will find lots of free modules.

Edit products directly from the list in the Opencart 3 – editable table widget

In this free Opencart module, we can edit products directly from the product list in the opencart 3, it is using editable table widget javascript, with this you can easily edit the product name, module, price, quantity, and status.

First, download the Opencart 3 free module by clicking the button below:

Download the “EditFromProductsList” module

From the above download, you will get a zip file named “editfromproductslist.ocmod.zip“.

Now login to your admin section >> go to Extensions >> Installer >> Upload the zip file and you will see the editfromproductslist.ocmod.zip in the install history.

installer Opencart

Now, go to admin >> Extensions >> Modifications >> you will see the “Edit directly from Products list” in the list >> Then click the refresh button

Modifications on edit directly from products list

Now go to the admin >> Catalog >> Products, now Product name, Model, Price, Quantity, and Status is editable directly from the product list.

Edit products list Opencart 3

Now edit any of the product names, then it will ask to choose the active language. If you want to update a different language then we need to enter the language id in the prompt box.

Prompt box for selecting the language for product name

Once you click Ok or enter the language and click Ok, then, the product name is updated and it will show the alert box like below:

Edit product list Opencart

Similarly, you can edit the Model. But for the price you don’t need to enter the $ sign, you just add the number of the price that you want to update. If you enter something other than a number then it will alert is like below:

Only numeric value for price

In the same way, for quantity also you just need to enter the number, if you don’t enter the number it will show an alert like above.

For the status, you need to enter 1 or 0 only. 1 is to enabled and 0 is to disabled. If you don’t enter then it will alert like:

Status change to Opencart

Once you enter the 1 or 0 then it will show a success alert message like below:

Product status update Opencart

In this way, you can easily edit the products’ name, model, price, quantity, and status directly from the product lists.

We are using the editable table Js and the following OCMOD modification:

<?xml version="1.0" encoding="utf-8"?>
<modification>
    <name>Edit directly from Products list</name>
    <version>3.0</version>
    <author>Rupak Nepali</author>
    <link>https://webocreation.com</link>
    <code>webocreation_edit_products_in_admin</code>
    <description>Edit products directly from the list in Opencart 3</description>

    <file path="admin/controller/catalog/product.php">
        <operation>
            <search><![CDATA[public function autocomplete()]]></search>
            <add position="before"><![CDATA[
                public function editableTableUpdate()
                {
                    $updated = "Please edit the right column";
                    $id = explode("___", $_POST['value']['id']);
                    $product_id = $id[1];
                    $columnToUpdate = $id[0];
                    if (in_array($columnToUpdate, array('name', 'model', 'price', 'quantity', 'status'))) {
                        $valueToUpdate = $_POST['value']['value'];
                        $lang = $_POST['value']['lang'];
                        $this->load->model('catalog/product');
                        $updated = $this->model_catalog_product->updateEditableTable($columnToUpdate, $product_id, $valueToUpdate, $lang);
                    }
                    $this->response->addHeader('Content-Type: application/json');
                    $this->response->setOutput(json_encode($updated));
                }
            ]]>
            </add>
        </operation>

        <operation>
            <search><![CDATA[$data['products'] = array();]]></search>
            <add position="before"><![CDATA[
          $data['active_language'] = $this->config->get('config_language_id');    
        ]]>
            </add>
        </operation>
    </file>

    <file path="admin/model/catalog/product.php">
        <operation>
            <search><![CDATA[public function getTotalProductsByLayoutId($layout_id)]]></search>
            <add position="before"><![CDATA[ 
            public function updateEditableTable($columnToUpdate, $product_id, $valueToUpdate, $lang)
            {
                try {
                    if ($columnToUpdate == "name") {
                        $this->db->query("UPDATE " . DB_PREFIX . "product_description SET " . $columnToUpdate . " = '" . $valueToUpdate . "' WHERE product_id = '" . (int) $product_id . "' and language_id = '" . $lang . "'");
                    } else {
                        $this->db->query("UPDATE " . DB_PREFIX . "product SET " . $columnToUpdate . " = '" . $valueToUpdate . "' WHERE product_id = '" . (int) $product_id . "'");
                    }
                    return "Product ID " . $product_id . "'s " . $columnToUpdate . " is updated with value " . $valueToUpdate;

                } catch (ConnectionException $e) {
                    return $e->getMessage();
                } catch (\RuntimeException $e) {
                    return $e->getMessage();
                }
            }
            
            ]]>            </add>
        </operation>
    </file>

    <file path="admin/view/template/catalog/product_list.twig">
        <operation>
            <search><![CDATA[{{ footer }} ]]></search>
            <add position=" "><![CDATA[ 

          <script type="text/javascript" src="view/javascript/jquery/mindmup-editabletable.js"></script>
<script>
            $('#editable').editableTableWidget();
            $('#editable td').on('change', function (evt, newValue) {
              var targetid = evt.target.id;
              if (['', 'name', 'status', 'quantity', 'price'].indexOf(targetid.split('___')[0])) {
              }else{return false;}
              if (targetid.split('___')[0] == 'name') {
                var lang = prompt(
                  'The active language is' +
                    {{active_language}} +
                    '. If you want to update different language then please enter here',
                  {{active_language}}
                );
              }
              if (targetid.split('___')[0] == 'status') {
                var statusenter = 1;
                if (newValue == 0 || newValue == 1) {
                  statusenter = 0;
                }
                if (statusenter) {
                  alert('The value need to be 0 or 1');
                  exit();
                }
              }
              if (targetid.split('___')[0] == 'quantity') {
                if (isNaN(newValue)) {
                  alert('Please enter the numeric value');
                  exit();
                }
              }
              if (targetid.split('___')[0] == 'price') {
                if (isNaN(newValue)) {
                  alert('Please enter the numeric value');
                  exit();
                }
              }
              const postvalue = { id: targetid, value: newValue, lang: {{active_language}} };
              $.post( 'index.php?route=catalog/product/editableTableUpdate&user_token={{ user_token }}',
                { value: postvalue }
              ).done(function (data) {
                alert(data);
              });
            });

            $('#editable td.uneditable').on('change', function (evt, newValue) {
              return false;
            });         
          </script>
            ]]>            </add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-left">{{ product.status }}]]></search>
            <add position="replace"><![CDATA[<td class="text-left" id="status___{{product.product_id}}">{{ product.status }}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-right">{% if product.quantity <= 0 %}]]></search>
            <add position="replace"><![CDATA[<td class="text-right" id="quantity___{{product.product_id}}">{% if product.quantity <= 0 %}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-right">{% if product.special %}]]></search>
            <add position="replace"><![CDATA[<td class="text-right" id="price___{{product.product_id}}">{% if product.special %}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-left">{{ product.model }}]]></search>
            <add position="replace"><![CDATA[<td class="text-left" id="model___{{product.product_id}}">{{ product.model }}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-left">{{ product.name }}]]></search>
            <add position=" "><![CDATA[<td class="text-left" id="name___{{product.product_id}}">{{ product.name }}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-center">{% if product.image %}]]></search>
            <add position=" "><![CDATA[<td class="text-center uneditable">{% if product.image %}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<td class="text-center">{% if product.product_id in selected %}]]></search>
            <add position=" "><![CDATA[<td class="text-center uneditable">{% if product.product_id in selected %}]]></add>
        </operation>

        <operation>
            <search><![CDATA[<table class="table table-bordered table-hover">]]></search>
            <add position=" "><![CDATA[<table id="editable" class="table table-bordered table-hover">]]></add>
        </operation>
    </file>
    
</modification>

We hope that this module will help someone who needs to edit the products frequently for the product name, model, price, quantity, and status. Hope you liked these Opencart free modules, let us know if you have any questions or suggestions, please subscribe to our YouTube Channel for Opencart video tutorials and find more free opencart modules here. You can also join us on Twitter and Facebook.

Javascript custom email validation for company email only, Unbounce page

We write a simple Javascript custom email validation that allows only company emails and it was implemented on Unbounce landing pages. We use pattern, oninvalid and onchange attributes. We set regex (^[a-zA-Z0-9.%+-]+@(?!gmail.com)(?!yahoo.com)(?!hotmail.com)(?!yahoo.co.in)(?!aol.com)(?!live.com)(?!outlook.com)[a-zA-Z0-9-]+.[a-zA-Z0-9-.]{2,61}$) to validate the email pattern and set the custom validity message for oninvalid attribute with setCustomValidity() method of the HTMLObjectElement interface sets a custom validity message for the element, and finally onchange attribute we set the setCustomValidity() method to empty.

How to validate the email field to allow only company email?

With HTML5, we can do the email input field validation with pattern, setCustomValidity method easily. If you write the code like below, it will not allow the email that contains gmail.com, yahoo.com, hotmail.com, yahoo.co.in, aol.com, live.com and outlook.com

<input id="Email" name="Email" type="email" required pattern="^[a-zA-Z0-9._%+-]+@(?!gmail.com)(?!yahoo.com)(?!hotmail.com)(?!yahoo.co.in)(?!aol.com)(?!live.com)(?!outlook.com)[a-zA-Z0-9_-]+.[a-zA-Z0-9-.]{2,61}$" oninvalid="setCustomValidity('Please enter business email')" onchange="try{setCustomValidity('')}catch(e){}')">

Similarly, we can add custom Javascript into the Unbounce like below, which will allow only company emails. The Javascript code is like below:

<script>
/* Allow only company emails */
document
  .getElementById("Email")
  .setAttribute(
    "pattern",
    "^[a-zA-Z0-9._%+-]+@(?!gmail.com)(?!yahoo.com)(?!hotmail.com)(?!yahoo.co.in)(?!aol.com)(?!live.com)(?!outlook.com)[a-zA-Z0-9_-]+.[a-zA-Z0-9-.]{2,61}$"
  ).setAttribute(
    "oninvalid",
    "setCustomValidity('Please enter business email')"
  ).setAttribute("onchange", "try{setCustomValidity('')}catch(e){}')");
</script>

** If you email id is different then change the “Email” in the above code.

Read more: Marketo API – export all Forms and their Fields from our Marketo database

Regular expression tested

regular expression

How to add custom JS to the Unbounce page?

  • Log in to your Unbounce, click your page where you want to add the email validation.
  • Edit the variant of the page
  • Click the JavaScript at the bottom of the page editor
  • Then, you will see a popup where you can add the Javascript like below.
  • Click “Add Script to the Variant”
  • Then enter the “Script Name”
  • Add the above JS code
  • Click Done
Unbounce JS code addition

After adding the above code in the Javascript manager of the Unbounce page and you tried to submit gmail email then it shows error like below:

Email Validation code

In this way, you can add custom email validation in form field and similarly on the Unbounce page. Hope you liked this article, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook.

API – Get the country code as per your IP addresses for free

We were searching for the free API which gives the country code as per your IP addresses as per our requirement we were looking to show team members as per the Country, defaulting different currency as per the active country, defaulting different language as per the active country, etc. Our research found ip-api.com which is free for non-commercial use, no API key required, easy to integrate, and available in JSON, which fulfilled all our requirements, so we use ip-api.com. As they provide free for non-commercial use so we like to share with you as well.

We use a basic fetch request of Javascript. The following code is to get the country code as per your IP address

var requestOptions = {
  method: 'GET',
  redirect: 'follow'
};
fetch("http://ip-api.com/json/?fields=countryCode", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

A PHP code block can be like this:

<?php
$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => 'http://ip-api.com/json/?fields=countryCode',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;

Read more: Opencart API uses and its documentation

Have a look at their documentation of Geolocation API

We can get the following fields from the API:
status, message, continent, continentCode, country, countryCode, region, regionName, city, district, zip, lat, on, timezone, offset, currency, isp, org, as, asname, reverse, mobile, proxy, hosting, query

Their endpoints are limited to 45 HTTP requests per minute from an IP address but they provide a Pro version with more benefits and unlimited HTTP requests.

So, hope this helps for your personal project to get the country code as per IP address and can use it as per your benefits. With this simple and easy API, we are able to show different team members as per country, and similarly, show different currencies and languages as per the country. Let us know if you find any other easy and better solutions.

Show selected categories as featured categories OpenCart module free

We planned to show my top categories at the homepage as featured categories so we planned to make the OpenCart module for free to get this functionality. Let’s say it “Category Featured Show Module”. As we created an OpenCart module so we can enjoy an unlimited module instance system which means we can show the same module in different layouts on the same pages and also on different pages. Please take into consideration that we have created this module for OpenCart version 3.0, 2.0.3.1. and suppose that it will be compatible with above 2.0.2, we are lazy to test them. Please provide feedback if it is not compatible then we can go through the problem.

Let’s start to install Category Featured Show Module.

Check our setting in the image below:

Category featured Opencart module

Now set layout and position for Category Featured Show Module. We are showing the Category Featured Show Module at homepage so follow the same steps as below if you like to show to different layout then choose the respective layout.

  • Go to Admin >> System >> Design >> Layouts.
  • Then click the blue plus button at the bottom which will add another row.
  • Then select “Category Featured > Top Category” at the Module column.
  • Select “Content Top” at Position Column.
  • Enter your desired Sort Order at Sort Order Column.
  • Then click the save button.

Our setting to show at home page is as shown in the image below:

Opencart layouts

Go to the home page and you will see the image below:

Top category show in Opencart module

You can show as many module instances as you like and your heading title is shown as Module Name from admin so it is also customized as needed.

This module has been successfully tested for a standard OpenCart 2.0.3.1.

If you get any problem with the module then let me know by commenting or through the contact form.

Future upgrade: Options to hide and show of category description, name and upload the customized image for the category and different layouts. Likewise, make the module to show products as per the category. Keep on visiting for updates 🙂

Support:

Further help and customized versions: If you need a customized version of this module and any other Opencart help and support then let me know. You can email us at webocreation.com@gmail.com

Easily make Full-width with simple CSS for Opencart modules

We saw some posts in Stackoverflow and Opencart forum to make the slideshow module full-width, similarly, the HTML content module full-width and similar posts asking help. Thus, we are showing you a simple way to make full-width with CSS for Opencart.

Make Slideshow full-width

The CSS used is similar to below which calculates the left position and starts from it making it 100% vertical width.

<style>
  .slideshow{
  	left: calc(-1 * (100vw - 100%) / 2);
        width: 100vw;
  	position: relative;
  	padding: 50px 20px;
  }
</style>

Now let’s start with the slideshow module. As we need to add the above code, so the easy we add it is on the Google Analytics module section. For that, go to admin >> Extensions >> Extensions >> Choose the extension type >> Analytics >> Install the Google Analytics >> Edit it >> Paste the above code in the Google Analytics Code field >> Enabled it >> Then, click the save button.

With this simple CSS code, the slideshow of the Opencart core becomes full-width.

See our free module: OpenCart free extension – full-width position in the layout of v 3.0.2.0

Make HTML content module full width

A simple way is to add the title, content, and CSS all in the HTML description like below:

<div class="htmlcontentfullwidth">
	<h2>This is heading title of HTML module</h2>
	<p>
        HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description HTML Module description
    </p>
</div>
<style>
  .htmlcontentfullwidth{
      left: calc(-1 * (100vw - 100%) / 2);
      width: 100vw;
      position: relative;
      padding: 50px 20px;
  }
</style>

An HTML content module doesn’t have any class or id by default so we implemented it like above. With that the HTML module becomes full width like below:

In this way, you can make a full-width module with a simple CSS in the Opencart version. Let us know if you have any other better ideas and need support to make any modules full-width. Please subscribe to our YouTube Channel for Opencart video tutorials and get lots of other Opencart free modules. You can also find us on Twitter and Facebook.

Opencart 3 development tutorial, multi-instance slider testimonials module

In this Opencart 3 module development tutorial, we are showing you how to create a multi-instance opencart 3 module frontend section and make an autoplay slider of testimonials. In our last two posts, we show how to perform Opencart CRUD functionalities of testimonials, create an admin custom listing page, form, validate it and save in the database, then create the admin section of the multi-instance opencart module now we will use those settings and show the testimonials at the frontend. The testimonial module at the frontend will look like in the image below. It will have a title, image, testimonial title, testimonial description, and sliders with autoplay and pagination.

Testimonial Front End

Let’s start by creating the files and folders in the catalog folders for testimonials module, here are the lists of files and its path:

  • catalog/language/en-gb/extension/module/testimonial.php
  • catalog/controller/extension/module/testimonial.php
  • catalog/model/extension/module/testimonial.php
  • catalog/view/theme/default/template/extension/module/testimonial.twig

Language file code description

We start by creating the language file go to catalog/language/en-gb/extension/module/ and create testimonial.php and paste the following code. We don’t have lots of text needed in the frontend so just adding heading_title.

<?php
// Heading
$_['heading_title'] = 'Latest Testimonails';

Controller file code description

After that, we create the controller file at catalog/controller/extension/module/ and name it testimonial.php, following is the code and the code is described in the comment. You can see one difference for multi-instance and single instance index method, in multi-instance we pass the $setting argument.

<?php

/*** As our file is testimonial.php so Class name is ControllerExtensionModuleTestimonial which extends the Controller base class ***/
class ControllerExtensionModuleTestimonial extends Controller
{
	/*** Index method is called automatically or by default, if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4 . 
	$setting is the argument of index which contain all details of the module ***/
	public function index($setting)
	{
		/*** Loads the language file catalog/language/en-gb/extension/module/testimonial.php by which the varaibles of the language file are accessible in twig file. ***/
		$this->load->language('extension/module/testimonial');
		/*** This is to load the model file catalog/model/extension/module/testimonial.php by which we can access all the method in ModelExtensionModuleTestimonial. ***/
		$this->load->model('extension/module/testimonial');
		/*** We can add custom CSS and JS as per needed for the controller. These two lines is to add the custom CSS that is needed only for this controller. Good for performance as it loads only when needed. We add these to show the slider effects for the testimonials ***/
		$this->document->addStyle('catalog/view/javascript/jquery/swiper/css/swiper.min.css');
		$this->document->addStyle('catalog/view/javascript/jquery/swiper/css/opencart.css');
		/*** We add this to add custom JS for the slider effects of testimonials ***/
		$this->document->addScript('catalog/view/javascript/jquery/swiper/js/swiper.jquery.js');
		$data['testimonials'] = array();
		/*** Setting array to pass for getTestimonials for filtering the testimonials
		 * $setting['limit'] is the value we get from the database which we added in the testimonial module setting section. Opencart find out for us to use the $setting. ***/
		$filter_data = array(
			'sort'  => 'p.date_added',
			'order' => 'DESC',
			'start' => 0,
			'limit' => $setting['limit']
		);
		/*** This is to call getTestimonials method of class ModelExtensionModuleTestimonial and retrieve the filtered out testimonials ***/		
		$results = $this->model_extension_module_testimonial->getTestimonials($filter_data);
		if ($results) {
			/*** This is to load the model file catalog/model/tool/image.php by which we can access all the method and especially we use resize method for resizing the image. ***/
			$this->load->model('tool/image');
			/*** We loop through the $results and sets testimonials array which is used in the twig or view file ***/
			foreach ($results as $result) {
				if ($result['image']) {
					$image = $this->model_tool_image->resize($result['image'], $setting['width'], $setting['height']);
				} else {
					$image = $this->model_tool_image->resize('placeholder.png', $setting['width'], $setting['height']);
				}

				$data['testimonials'][] = array(
					'testimonial_id'  => $result['testimonial_id'],
					'thumb'       => $image,
					'name'        => $result['name'],
					'description' => html_entity_decode($result['description'], ENT_QUOTES, 'UTF-8')
				);
			}
			/*** This is to load the view catalog/view/theme/default/template/extension/module/testimonial.twig. ***/
			return $this->load->view('extension/module/testimonial', $data);
		}
	}
}

Model file code description

Testimonials are custom data, so we need to create a model file also to retrieve the testimonials from the database. So let go to catalog/model/extension/module/ and create a testimonial.php and paste the following code. The following code is SQL to retrieve the testimonials.

<?php
class ModelExtensionModuleTestimonial extends Model
{
    /** getTestimonials method is to retrieve the testimonials which is called from controller like $results = $this->model_extension_module_testimonial->getTestimonials($filter_data);. $data is the filtering parameter. Multiple testimonials are returned  ***/
    public function getTestimonials($data = array())
    {
        $sql = "SELECT * FROM " . DB_PREFIX . "testimonial c1 LEFT JOIN " . DB_PREFIX . "testimonial_description cd2 ON (c1.testimonial_id = cd2.testimonial_id) WHERE cd2.language_id ='" . (int) $this->config->get('config_language_id') . "'";
        $sort_data = array(
            'name',
            'sort_order'
        );
        if (isset($data['sort']) && in_array($data['sort'], $sort_data)) {
            $sql .= " ORDER BY " . $data['sort'];
        } else {
            $sql .= " ORDER BY sort_order";
        }
        if (isset($data['order']) && ($data['order'] == 'DESC')) {
            $sql .= " DESC";
        } else {
            $sql .= " ASC";
        }
        if (isset($data['start']) || isset($data['limit'])) {
            if ($data['start'] < 0) {
                $data['start'] = 0;
            }
            if ($data['limit'] < 1) {
                $data['limit'] = 20;
            }
            $sql .= " LIMIT " . (int) $data['start'] . "," . (int) $data['limit'];
        }
        $query = $this->db->query($sql);
        return $query->rows;
    }
}

View file or template file code description

At last, the view file or template file, which we create at catalog/view/theme/default/template/extension/module/, we named it testimonial.twig and paste the following code. We are showing the testimonial as a slider so we are using the Opencart default Swiper slider.

<h3>{{ heading_title }}</h3>
<div class="swiper-viewport">
    <div id="carousel{{ module }}" class="swiper-container">
        <div class="swiper-wrapper">
            {% for testimonial in testimonials %}
                <div class="swiper-slide text-center">
                  <div>
                      <div class="image">
                          <img src="{{ testimonial.thumb }}" alt="{{ testimonial.name }}" title="{{ testimonial.name }}" class="img-responsive"/>
                      </div>
                      <div class="caption">
                          <h4>
                              {{ testimonial.name }}
                          </h4>
                          <p>{{ testimonial.description }}</p>
                      </div>
                  </div>
                </div>
            {% endfor %}
        </div>
    </div>
    <div class="swiper-pagination carousel{{ module }}"></div>
    <div class="swiper-pager">
        <div class="swiper-button-next"></div>
        <div class="swiper-button-prev"></div>
    </div>
</div>
<script
    type="text/javascript">
    <!--
    $( '#carousel{{ module }}' ).swiper( {
        mode: 'horizontal',
        slidesPerView: 1,
        pagination: '.carousel{{ module }}',
        paginationClickable: true,
        nextButton: '.swiper-button-next',
        prevButton: '.swiper-button-prev',
        autoplay: 2500,
        loop: true
    } );
    -->
</script>

In this way, we create an Opencart 3 testimonial module’s frontend and show the setting that we entered in the multi-instance module admin section. Similarly, we make a slider of the testimonial module. Hope you liked this post, let us know if you have any questions or suggestions, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook. Enjoy!

How to create an off-site payment extension in Opencart 3? Part II of III

In this Opencart tutorial, we go through how to create an off-site payment extension in Opencart 3.0.3.3, in our last tutorial, we show the eCommerce payment flow that happens in Opencart, here we will show how to create the PayPal Standard payment extension, as all 3rd party extensions are removed from the download files, saying it is taking too much time to maintain but these will be uploaded to opencart.com and will be provided in a JSON string to be downloaded on demand.

SEE Difference between Off-site payment extension and On-site payment extension

Here is the step by step guide to integrate PayPal Standard in your website and here are the HTML variables than can be used on form for PayPal Standard.

For OpenCart payment extension, here are the lists of files you need to create, the link is for GitHub page where you can get all the codes, below we are just describing what is important:

Here files are folders in the admin section is to create the setting fields for the payment module, for PayPal Standard seeing the HTML form:

https://developer.paypal.com/docs/paypal-payments-standard/integration-guide/formbasics/#sample-html-code-for-overriding-addresses-stored-with-paypal and https://developer.paypal.com/docs/paypal-payments-standard/ht-test-pps-buttons/#create-test-buttons-in-the-sandbox

We see the form action URL is different so that field setting is needed in the admin, merchant email or business email is need where the payment is made, transaction method whether it is an Authorization or Sale transactions, Total field, Geo Zone field, Status, Sort Order field and order status fields are needed. You can know which fields are needed by going through the documentation of the Payment method.

So, for PayPal Standard here is the output for the admin section:

Paypal Standard Settings
Paypal Order Status

Let’s start with the admin controller file of Payment extension:

Open admin >> controller >> extension >> payment folder and create the paypal_standard.php, for yours you can name what your payment name is. In the file name _ is not needed, we add it just to differentiate it. Now open the paypal_standard.php and start creating the Controller Class.

As our file name is paypal_standard.php, the class name is ControllerExtensionPaymentPaypalStandard which extends the Controller base class. Although the filename includes _ (underscore), it is not needed for the Class name.

class ControllerExtensionPaymentPaypalStandard extends Controller {

Declaration of the private property ‘error’ so that we can get check if any error occurs in the whole class.

private $error = array();

Create an index method. The index method is called automatically if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4

public function index() {

Loads the language file by which the variables of language file are accessible in twig files

$this->load->language('extension/payment/paypal_standard');

Set the page or document title with following code:

$this->document->setTitle($this->language->get('heading_title'));

Loads the model admin/model/setting/setting.php so that we can use the methods defined there.

$this->load->model('setting/setting');

This is how we check if the form is submitted. When we submit the form then this block of code also runs. Then it also validates the modified permission and other validation.

if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {

Look at this line of codes, this is the code section which distinguishes from Single Instance to Multi-Instance. If it is a payment extension then it acts as a Single instance module, which will have editSetting function, see the first parameters “payment_paypal_standard”, which need to be unique and it should start with “payment_”, all $_POST values will be saved on the setting database table. Then, it sets the success message in session and then redirects to the payment listing page.

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

In the database setting table, the code is “payment_paypal_standard” and have the key which is the field name and value is the field value.

Opencart setting database payment paypal standard

The editSetting saves the data to oc_setting database table, see payment_ is important else it will not be saved.
The following code sets the success message in the session.

$this->session->data['success'] = $this->language->get('text_success');

This code is to redirect to the payment extensions listing page.

$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true));

This is to check if there are any warnings

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

Similarly, other fields value error are checked, like for email error it is checked with the following code:

if (isset($this->error['email'])) {
$data['error_email'] = $this->error['email'];
} else {
$data['error_email'] = '';
}

Following are for breadcrumbs

$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_extension'),
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/login', 'user_token=' . $this->session->data['user_token'], true)
);

Form action URL

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

Form cancel URL

$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=payment', true);

This is to check what we fill out in the form, whether the email is added. If it is the loading time then it gets the value from the database config value which was stored in the oc_setting database table.

if (isset($this->request->post['payment_paypal_standard_email'])) {
$data['payment_paypal_standard_email'] = $this->request->post['payment_paypal_standard_email'];
} else {
$data['payment_paypal_standard_email'] = $this->config->get('payment_paypal_standard_email');
}

Similarly, other fields are checked, like for fields payment_paypal_standard_test, payment_paypal_standard_transaction, payment_paypal_standard_debug, payment_paypal_standard_total, payment_paypal_standard_canceled_reversal_status_id, payment_paypal_standard_completed_status_id, payment_paypal_standard_denied_status_id, payment_paypal_standard_expired_status_id, payment_paypal_standard_failed_status_id, payment_paypal_standard_pending_status_id, payment_paypal_standard_processed_status_id, payment_paypal_standard_refunded_status_id, payment_paypal_standard_reversed_status_id, payment_paypal_standard_voided_status_id, payment_paypal_standard_status, and payment_paypal_standard_sort_order. You can check those all opencart codes here.

Mostly, we use geo zones for payment extensions so that we can control as per the country or zones. So to pull the geo zones in opencart we use the following code:

$this->load->model('localisation/geo_zone');
$data['geo_zones'] = $this->model_localisation_geo_zone->getGeoZones();

This is how we load the header, column left, and footer and pass to the template file.

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

This is to set output data variables to the view or twig files and twig file is loaded and HTML rendering is done with it.

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

In this way the index method is closed.

Now, we see the validate method, this is how validation is done, we check whether the user has permission to modify or not. If you want to validate the form data then you can check it here as well. If there is an error then $this->error[‘warning’] is set and a warning is shown. We check whether the Paypal email is added or not. Similarly, you can check other validation here.

private function validate()
{
if (!$this->user->hasPermission('modify', 'extension/payment/paypal_standard')) {
$this->error['warning'] = $this->language->get('error_permission');
}
if (!$this->request->post['payment_paypal_standard_email']) {
$this->error['email'] = $this->language->get('error_email');
}
return !$this->error;
}

In this way, we write the code in the controller for the payment extension module at admin section.

Now, admin language file of Payment extension:

Let’s work on Language file admin/language/en-gb/extension/payment/paypal_standard.php, let’s define some variables which are needed for the Paypal standard payment extension:
https://github.com/rupaknepali/Opencart-free-modules/blob/master/paypal-standard-opencart-extension/upload/admin/language/en-gb/extension/payment/paypal_standard.php

<?php
// Heading
$_['heading_title']	= 'PayPal Payments Standard';

// Text
$_['text_extension']	= 'Extensions';
$_['text_success']	= 'Success: You have modified PayPal account details!';
$_['text_edit']            = 'Edit PayPal Payments Standard';
$_['text_paypal_standard']= '<a target="_BLANK" href="https://www.paypal.com/uk/mrb/pal=V4T754QB63XXL"><img src="view/image/payment/paypal.png" alt="PayPal Payments Standard" title="PayPal Payments Standard" style="border: 1px solid #EEEEEE;" /></a>';
$_['text_authorization']= 'Authorization';
$_['text_sale']		   = 'Sale'; 

// Entry
$_['entry_email']   = 'E-Mail';
$_['entry_test']	    = 'Sandbox Mode';
$_['entry_transaction'] = 'Transaction Method';
$_['entry_debug']  = 'Debug Mode';
$_['entry_total'].    = 'Total';
$_['entry_canceled_reversal_status'] = 'Canceled Reversal Status';
$_['entry_completed_status']		 = 'Completed Status';
$_['entry_denied_status']			 = 'Denied Status';
$_['entry_expired_status']			 = 'Expired Status';
$_['entry_failed_status']			 = 'Failed Status';
$_['entry_pending_status']			 = 'Pending Status';
$_['entry_processed_status']		 = 'Processed Status';
$_['entry_refunded_status']		 = 'Refunded Status';
$_['entry_reversed_status']		 = 'Reversed Status';
$_['entry_voided_status']			 = 'Voided Status';
$_['entry_geo_zone']				 = 'Geo Zone';
$_['entry_status']				 = 'Status';
$_['entry_sort_order']			 = 'Sort Order';

// Tab
$_['tab_general']			= 'General';
$_['tab_order_status']       		= 'Order Status';

// Help
$_['help_test']	= 'Use the live or testing (sandbox) gateway server to process transactions?';
$_['help_debug'] = 'Logs additional information to the system log';
$_['help_total']	= 'The checkout total the order must reach before this payment method becomes active';

// Error
$_['error_permission']= 'Warning: You do not have permission to modify payment PayPal Payments Standard!';
$_['error_email']  = 'E-Mail required!';

In the above code, you can find the following code:

$_['text_paypal_standard']= '<a target="_BLANK" href="https://www.paypal.com/uk/mrb/pal=V4T754QB63XXL"><img src="view/image/payment/paypal.png" alt="PayPal Payments Standard" title="PayPal Payments Standard" style="border: 1px solid #EEEEEE;" /></a>';

This is the code which shows the payment logo at the payment extension listings.

Paypal Payments standard image

Now, admin template file of Payment extension:

Third, open admin/view/template/extension/payment and create paypal_standard.twig. Here we create the form and other layouts. You can see the code here: https://github.com/rupaknepali/Opencart-free-modules/blob/master/paypal-standard-opencart-extension/upload/admin/view/template/extension/payment/paypal_standard.twig

Be sure to start the name with “payment_” for every fields name.

{{ 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-payment" class="form-horizontal">
          <ul class="nav nav-tabs">
            <li class="active"><a href="#tab-general" data-toggle="tab">{{ tab_general }}</a></li>
            <li><a href="#tab-status" data-toggle="tab">{{ tab_order_status }}</a></li>
          </ul>
          <div class="tab-content">
            <div class="tab-pane active" id="tab-general">
              <div class="form-group required">
                <label class="col-sm-2 control-label" for="entry-email">{{ entry_email }}</label>
                <div class="col-sm-10">
                  <input type="text" name="payment_paypal_standard_email" value="{{ payment_paypal_standard_email }}" placeholder="{{ entry_email }}" id="entry-email" class="form-control"/>
                  {% if error_email %}
                  <div class="text-danger">{{ error_email }}</div>
                  {% endif %}
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-live-demo"><span data-toggle="tooltip" title="{{ help_test }}">{{ entry_test }}</span></label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_test" id="input-live-demo" class="form-control">
                    {% if payment_paypal_standard_test %}
                    <option value="1" selected="selected">{{ text_yes }}</option>
                    <option value="0">{{ text_no }}</option>
                    {% else %}
                    <option value="1">{{ text_yes }}</option>
                    <option value="0" selected="selected">{{ text_no }}</option>
                    {% endif %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-debug"><span data-toggle="tooltip" title="{{ help_debug }}">{{ entry_debug }}</span></label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_debug" id="input-debug" class="form-control">
                    {% if payment_paypal_standard_debug %}
                    <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="input-transaction">{{ entry_transaction }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_transaction" id="input-transaction" class="form-control">
                    {% if not payment_paypal_standard_transaction %}
                    <option value="0" selected="selected">{{ text_authorization }}</option>
                    {% else %}
                    <option value="0">{{ text_authorization }}</option>
                    {% endif %}
                    {% if payment_paypal_standard_transaction %}
                    <option value="1" selected="selected">{{ text_sale }}</option>
                    {% else %}
                    <option value="1">{{ text_sale }}</option>
                    {% endif %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-total"><span data-toggle="tooltip" title="{{ help_total }}">{{ entry_total }}</span></label>
                <div class="col-sm-10">
                  <input type="text" name="payment_paypal_standard_total" value="{{ payment_paypal_standard_total }}" placeholder="{{ entry_total }}" id="input-total" class="form-control"/>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-geo-zone">{{ entry_geo_zone }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_geo_zone_id" id="input-geo-zone" class="form-control">
                    <option value="0">{{ text_all_zones }}</option>
                    {% for geo_zone in geo_zones %}
                    {% if geo_zone.geo_zone_id == payment_paypal_standard_geo_zone_id %}
                    <option value="{{ geo_zone.geo_zone_id }}" selected="selected">{{ geo_zone.name }}</option>
                    {% else %}
                    <option value="{{ geo_zone.geo_zone_id }}">{{ geo_zone.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <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_paypal_standard_status" id="input-status" class="form-control">
                    {% if payment_paypal_standard_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="input-sort-order">{{ entry_sort_order }}</label>
                <div class="col-sm-10">
                  <input type="text" name="payment_paypal_standard_sort_order" value="{{ payment_paypal_standard_sort_order }}" placeholder="{{ entry_sort_order }}" id="input-sort-order" class="form-control"/>
                </div>
              </div>
            </div>
            <div class="tab-pane" id="tab-status">
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-canceled-reversal-status">{{ entry_canceled_reversal_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_canceled_reversal_status_id" id="input-canceled-reversal-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_canceled_reversal_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-completed-status">{{ entry_completed_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_completed_status_id" id="input-completed-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_completed_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-denied-status">{{ entry_denied_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_denied_status_id" id="input-denied-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_denied_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-expired-status">{{ entry_expired_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_expired_status_id" id="input-expired-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_expired_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-failed-status">{{ entry_failed_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_failed_status_id" id="input-failed-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_failed_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-pending-status">{{ entry_pending_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_pending_status_id" id="input-pending-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_pending_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-processed-status">{{ entry_processed_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_processed_status_id" id="input-processed-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_processed_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-refunded-status">{{ entry_refunded_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_refunded_status_id" id="input-refunded-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_refunded_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-reversed-status">{{ entry_reversed_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_reversed_status_id" id="input-reversed-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_reversed_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
              <div class="form-group">
                <label class="col-sm-2 control-label" for="input-void-status">{{ entry_voided_status }}</label>
                <div class="col-sm-10">
                  <select name="payment_paypal_standard_voided_status_id" id="input-void-status" class="form-control">
                    {% for order_status in order_statuses %}
                    {% if order_status.order_status_id == payment_paypal_standard_voided_status_id %}
                    <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
                    {% else %}
                    <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
                    {% endif %}
                    {% endfor %}
                  </select>
                </div>
              </div>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
{{ footer }}

We describe some important code only as mostly they are same.

Let’s start with the submit or save button code:

<button type="submit" form="form-payment" data-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fa fa-save"></i></button>

The button type is submitted, and form equals value needs to be the form id. The form id is form-payment that is why it is, form=”form-payment”, the form code is like below:

<form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-payment" class="form-horizontal">

Following is the code to create a tab in the Opencart admin section, as they use the bootstrap v3.3.7 so it is the same as bootstrap.

<ul class="nav nav-tabs">
  <li class="active"><a href="#tab-general" data-toggle="tab">{{ tab_general }}</a></li>
  <li><a href="#tab-status" data-toggle="tab">{{ tab_order_status }}</a></li>
</ul>

We create the form email field like below, similarly we create other fields.

<div class="form-group required">
  <label class="col-sm-2 control-label" for="entry-email">{{ entry_email }}</label>
  <div class="col-sm-10">
    <input type="text" name="payment_paypal_standard_email" value="{{ payment_paypal_standard_email }}" placeholder="{{ entry_email }}" id="entry-email" class="form-control"/>
  </div>
</div>

If some error need to show then here is the code that we set in the controller for the email.

{% if error_email %}
  <div class="text-danger">{{ error_email }}</div>
{% endif %}

Another code to see is the geo zones code, here the geo_zones is for loop to show lists of geo zones available.

 <select name="payment_paypal_standard_geo_zone_id" id="input-geo-zone" class="form-control">
    <option value="0">{{ text_all_zones }}</option>
    {% for geo_zone in geo_zones %}
        {% if geo_zone.geo_zone_id == payment_paypal_standard_geo_zone_id %}
            <option value="{{ geo_zone.geo_zone_id }}" selected="selected">{{ geo_zone.name }}</option>
        {% else %}
            <option value="{{ geo_zone.geo_zone_id }}">{{ geo_zone.name }}</option>
        {% endif %}
    {% endfor %}
</select>

Likewise, look at Order status tab, the code is similar for all the statuses, the select field name starts with payment_ and it loops order_statuses and the selected order status is saved in the database setting table.

<select name="payment_paypal_standard_canceled_reversal_status_id" id="input-canceled-reversal-status" class="form-control">
    {% for order_status in order_statuses %}
        {% if order_status.order_status_id == payment_paypal_standard_canceled_reversal_status_id %}
            <option value="{{ order_status.order_status_id }}" selected="selected">{{ order_status.name }}</option>
        {% else %}
            <option value="{{ order_status.order_status_id }}">{{ order_status.name }}</option>
        {% endif %}
    {% endfor %}
</select>

This way you can check other codes and let us know in a comment if you need any code explanation.

With above four files created, our admin side coding is completed.

Now, catalog language file of Payment extension:

Let’s start with the language file catalog/language/en-gb/extension/payment and create paypal_standard.php, let’s define some variables which are useful for module

<?php
// Text
$_['text_title']	= 'PayPal';
$_['text_testmode']	= 'Warning: The payment gateway is in \'Sandbox Mode\'. Your account will not be charged.';
$_['text_total']	= 'Shipping, Handling, Discounts & Taxes';

Now, catalog controller file of Payment extension:

The following code is of front end controller file, go to catalog/controller/extension/payment and create paypal_standard.php and paste following code, here there are two methods index method and callback method.

<?php
class ControllerExtensionPaymentPaypalStandard extends Controller
{
    public function index()
    {
        $this->load->language('extension/payment/paypal_standard');

        $data['text_testmode'] = $this->language->get('text_testmode');
        $data['button_confirm'] = $this->language->get('button_confirm');

        $data['testmode'] = $this->config->get('payment_paypal_standard_test');

        if (!$this->config->get('payment_paypal_standard_test')) {
            $data['action'] = 'https://www.paypal.com/cgi-bin/webscr';
        } else {
            $data['action'] = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
        }

        $this->load->model('checkout/order');

        $order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']);

        if ($order_info) {
            $data['business'] = $this->config->get('payment_paypal_standard_email');
            $data['item_name'] = html_entity_decode($this->config->get('config_name'), ENT_QUOTES, 'UTF-8');

            $data['products'] = array();

            foreach ($this->cart->getProducts() as $product) {
                $option_data = array();

                foreach ($product['option'] as $option) {
                    if ($option['type'] != 'file') {
                        $value = $option['value'];
                    } else {
                        $upload_info = $this->model_tool_upload->getUploadByCode($option['value']);

                        if ($upload_info) {
                            $value = $upload_info['name'];
                        } else {
                            $value = '';
                        }
                    }

                    $option_data[] = array(
                        'name' => $option['name'],
                        'value' => (utf8_strlen($value) > 20 ? utf8_substr($value, 0, 20) . '..' : $value),
                    );
                }

                $data['products'][] = array(
                    'name' => htmlspecialchars($product['name']),
                    'model' => htmlspecialchars($product['model']),
                    'price' => $this->currency->format($product['price'], $order_info['currency_code'], false, false),
                    'quantity' => $product['quantity'],
                    'option' => $option_data,
                    'weight' => $product['weight'],
                );
            }

            $data['discount_amount_cart'] = 0;

            $total = $this->currency->format($order_info['total'] - $this->cart->getSubTotal(), $order_info['currency_code'], false, false);

            if ($total > 0) {
                $data['products'][] = array(
                    'name' => $this->language->get('text_total'),
                    'model' => '',
                    'price' => $total,
                    'quantity' => 1,
                    'option' => array(),
                    'weight' => 0,
                );
            } else {
                $data['discount_amount_cart'] -= $total;
            }

            $data['currency_code'] = $order_info['currency_code'];
            $data['first_name'] = $order_info['payment_firstname'];
            $data['last_name'] = $order_info['payment_lastname'];
            $data['address1'] = $order_info['payment_address_1'];
            $data['address2'] = $order_info['payment_address_2'];
            $data['city'] = $order_info['payment_city'];
            $data['zip'] = $order_info['payment_postcode'];
            $data['country'] = $order_info['payment_iso_code_2'];
            $data['email'] = $order_info['email'];
            $data['invoice'] = $this->session->data['order_id'] . ' - ' . $order_info['payment_firstname'] . ' ' . $order_info['payment_lastname'];
            $data['lc'] = $this->session->data['language'];
            $data['return'] = $this->url->link('checkout/success');
            $data['notify_url'] = $this->url->link('extension/payment/paypal_standard/callback', '', true);
            $data['cancel_return'] = $this->url->link('checkout/checkout', '', true);

            if (!$this->config->get('payment_paypal_standard_transaction')) {
                $data['paymentaction'] = 'authorization';
            } else {
                $data['paymentaction'] = 'sale';
            }

            $data['custom'] = $this->session->data['order_id'];

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

    public function callback()
    {
        if (isset($this->request->post['custom'])) {
            $order_id = $this->request->post['custom'];
        } else {
            $order_id = 0;
        }

        $this->load->model('checkout/order');

        $order_info = $this->model_checkout_order->getOrder($order_id);

        if ($order_info) {
            $request = 'cmd=_notify-validate';

            foreach ($this->request->post as $key => $value) {
                $request .= '&' . $key . '=' . urlencode(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
            }

            if (!$this->config->get('payment_paypal_standard_test')) {
                $curl = curl_init('https://www.paypal.com/cgi-bin/webscr');
            } else {
                $curl = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
            }

            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($curl, CURLOPT_HEADER, false);
            curl_setopt($curl, CURLOPT_TIMEOUT, 30);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

            $response = curl_exec($curl);

            if (!$response) {
                $this->log->write('paypal_standard :: CURL failed ' . curl_error($curl) . '(' . curl_errno($curl) . ')');
            }

            if ($this->config->get('payment_paypal_standard_debug')) {
                $this->log->write('paypal_standard :: IPN REQUEST: ' . $request);
                $this->log->write('paypal_standard :: IPN RESPONSE: ' . $response);
            }

            if ((strcmp($response, 'VERIFIED') == 0 || strcmp($response, 'UNVERIFIED') == 0) && isset($this->request->post['payment_status'])) {
                $order_status_id = $this->config->get('config_order_status_id');

                switch ($this->request->post['payment_status']) {
                    case 'Canceled_Reversal':
                        $order_status_id = $this->config->get('payment_paypal_standard_canceled_reversal_status_id');
                        break;
                    case 'Completed':
                        $receiver_match = (strtolower($this->request->post['receiver_email']) == strtolower($this->config->get('payment_paypal_standard_email')));

                        $total_paid_match = ((float) $this->request->post['mc_gross'] == $this->currency->format($order_info['total'], $order_info['currency_code'], $order_info['currency_value'], false));

                        if ($receiver_match && $total_paid_match) {
                            $order_status_id = $this->config->get('payment_paypal_standard_completed_status_id');
                        }

                        if (!$receiver_match) {
                            $this->log->write('paypal_standard :: RECEIVER EMAIL MISMATCH! ' . strtolower($this->request->post['receiver_email']));
                        }

                        if (!$total_paid_match) {
                            $this->log->write('paypal_standard :: TOTAL PAID MISMATCH! ' . $this->request->post['mc_gross']);
                        }
                        break;
                    case 'Denied':
                        $order_status_id = $this->config->get('payment_paypal_standard_denied_status_id');
                        break;
                    case 'Expired':
                        $order_status_id = $this->config->get('payment_paypal_standard_expired_status_id');
                        break;
                    case 'Failed':
                        $order_status_id = $this->config->get('payment_paypal_standard_failed_status_id');
                        break;
                    case 'Pending':
                        $order_status_id = $this->config->get('payment_paypal_standard_pending_status_id');
                        break;
                    case 'Processed':
                        $order_status_id = $this->config->get('payment_paypal_standard_processed_status_id');
                        break;
                    case 'Refunded':
                        $order_status_id = $this->config->get('payment_paypal_standard_refunded_status_id');
                        break;
                    case 'Reversed':
                        $order_status_id = $this->config->get('payment_paypal_standard_reversed_status_id');
                        break;
                    case 'Voided':
                        $order_status_id = $this->config->get('payment_paypal_standard_voided_status_id');
                        break;
                }

                $this->model_checkout_order->addOrderHistory($order_id, $order_status_id);
            } else {
                $this->model_checkout_order->addOrderHistory($order_id, $this->config->get('config_order_status_id'));
            }

            curl_close($curl);
        }
    }
}

As our file is paypal_standard.php so Class name is ControllerExtensionPaymentPaypalStandard which extends the Controller base class

class ControllerExtensionPaymentPaypalStandard extends Controller{ 

Create index method. Index method is called automatically if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4. In payment extension you don’t need to pass any parameter in index() method.

public function index() { 

Loads the language file by which the varaibles of language file are accessible in twig files

$this->load->language('extension/payment/paypal_standard'); 

Text to show when it is in test mode.

$data['text_testmode'] = $this->language->get('text_testmode'); 

Text to show for the button.

$data['button_confirm'] = $this->language->get('button_confirm'); 

Get the configured value, and find when it is on test mode or not.

$data['testmode'] = $this->config->get('payment_paypal_standard_test'); 

If it is on test mode then set the form action URL to sandbox paypal URL else set the form action URL to live paypal URL

if (!$this->config->get('payment_paypal_standard_test')) { 
$data['action'] = 'https://www.paypal.com/cgi-bin/webscr'; 
} else { 
$data['action'] = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; 
}

Notify URL is important to get the callback once the payment gateway processes the payment.

$data['notify_url'] = $this->url->link('extension/payment/paypal_standard/callback', '', true);

This is the custom data that we are passing to the PayPal and it will return us back to the website server, in our case we are sending the Order Id.

$data['custom'] = $this->session->data['order_id'];

Now, let’s check the callback() method. Some Instant Update variables set up the Cart Upload to use your callback server. Include the following required variables in the Cart Upload command to have PayPal send Instant Update requests to your callback server. It means the Paypal will call this URL and update the order status in the database of the website.

public function callback() {

PayPal sends back the order id so that we know it it is the right order that we are processing back.

if (isset($this->request->post['custom'])) {
$order_id = $this->request->post['custom'];
} else {
$order_id = 0;
}

Once the Paypal sends us back data, we need to verify again with the returned data to finalized if the data are not tampered. The image shows the flow:

Paypal IPN
https://developer.paypal.com/docs/api-basics/notifications/ipn/IPNIntro/#ipn-overview

In PHP we use CURL to call the Paypal server to verify it.

$request = 'cmd=_notify-validate';

foreach ($this->request->post as $key => $value) {
$request .= '&' . $key . '=' . urlencode(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
}
if (!$this->config->get('payment_paypal_standard_test')) {
$curl = curl_init('https://www.paypal.com/cgi-bin/webscr');
} else {
$curl = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
}
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($curl);
if (!$response) {
$this->log->write('paypal_standard :: CURL failed ' . curl_error($curl) . '(' . curl_errno($curl) . ')');
}

if ($this->config->get('payment_paypal_standard_debug')) {
$this->log->write('paypal_standard :: IPN REQUEST: ' . $request);
$this->log->write('paypal_standard :: IPN RESPONSE: ' . $response);
}

Once we call the CURL, it returns back the responses whether it is verified or not. Then, we can check and update our order status, for checking the code is like below:

if ((strcmp($response, 'VERIFIED') == 0 || strcmp($response, 'UNVERIFIED') == 0) && isset($this->request->post['payment_status'])) {

In the switch statement, check Canceled_Reversal case, the code is

case 'Canceled_Reversal':
  $order_status_id = $this->config->get('payment_paypal_standard_canceled_reversal_status_id');
  break;

It sets the $order_status_id to Canceled Reversal value or as per the setting configuration we do in the Paypal standard extension. Other order statuses are similar except Completed. See the Completed switch case code:

case 'Completed':
$receiver_match = (strtolower($this->request->post['receiver_email']) == strtolower($this->config->get('payment_paypal_standard_email')));

$total_paid_match = ((float) $this->request->post['mc_gross'] == $this->currency->format($order_info['total'], $order_info['currency_code'], $order_info['currency_value'], false));

if ($receiver_match && $total_paid_match) {
$order_status_id = $this->config->get('payment_paypal_standard_completed_status_id');
}

if (!$receiver_match) {
$this->log->write('paypal_standard :: RECEIVER EMAIL MISMATCH! ' . strtolower($this->request->post['receiver_email']));
}

if (!$total_paid_match) {
$this->log->write('paypal_standard :: TOTAL PAID MISMATCH! ' . $this->request->post['mc_gross']);
}
break;

For the completed status, we check whether the received amount and paid amount are matched or not. If it matched then only order status is set to completed. If the amount is not matched then a log is written on the log file.

Finally the order status is added on the history with the following lines of code:

$this->model_checkout_order->addOrderHistory($order_id, $order_status_id);

In this way, we write the code at the catalog controller of Paypal Standard, similarly way we can write for other payment extensions.

Now, catalog model file of Payment extension

We need to create the model file, although we don’t call in the controller file. It is automatically called for payment methods. For that go to catalog/model/extension/payment and create paypal_standard.php and paste following code.

<?php
class ModelExtensionPaymentPaypalStandard extends Model {
	public function getMethod($address, $total) {
		$this->load->language('extension/payment/paypal_standard');

		$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id = '" . (int)$this->config->get('payment_paypal_standard_geo_zone_id') . "' AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");

		if ($this->config->get('payment_paypal_standard_total') > $total) {
			$status = false;
		} elseif (!$this->config->get('payment_paypal_standard_geo_zone_id')) {
			$status = true;
		} elseif ($query->num_rows) {
			$status = true;
		} else {
			$status = false;
		}

		$currencies = array(
			'AUD',
			'CAD',
			'EUR',
			'GBP',
			'JPY',
			'USD',
			'NZD',
			'CHF',
			'HKD',
			'SGD',
			'SEK',
			'DKK',
			'PLN',
			'NOK',
			'HUF',
			'CZK',
			'ILS',
			'MXN',
			'MYR',
			'BRL',
			'PHP',
			'TWD',
			'THB',
			'TRY',
			'RUB'
		);

		if (!in_array(strtoupper($this->session->data['currency']), $currencies)) {
			$status = false;
		}

		$method_data = array();

		if ($status) {
			$method_data = array(
				'code'       => 'paypal_standard',
				'title'      => $this->language->get('text_title'),
				'terms'      => '',
				'sort_order' => $this->config->get('payment_paypal_standard_sort_order')
			);
		}

		return $method_data;
	}
}

The payment class is created and we need the getMethod() method whose parameters are address and total. In getMethod() method we return the value when the currencies is supported, when geo zones are supported.

$method_data = array(
'code' => 'paypal_standard',
'title' => $this->language->get('text_title'),
'terms' => '',
'sort_order' => $this->config->get('payment_paypal_standard_sort_order')
);

Once this is returned then only it is shown in the payment methods.

Now, catalog template file of Payment extension:

Finally we create the frontend template file, go to catalog/view/theme/default/extension/payment/ and create paypal_standard.twig.

the form should match the provided form from payment gateway.

For paypal, the form is provided here: https://developer.paypal.com/docs/paypal-payments-standard/integration-guide/formbasics/#sample-html-code-for-auto-fill-forms

Paste the following code in paypal_standard.twig

{% if testmode %}
<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ text_testmode }}</div>
{% endif %}
<form action="{{ action }}" method="post">
  <input type="hidden" name="cmd" value="_cart" />
  <input type="hidden" name="upload" value="1" />
  <input type="hidden" name="business" value="{{ business }}" />
  {% set i = 1 %}
  {% for product in products %}
  <input type="hidden" name="item_name_{{ i }}" value="{{ product.name }}" />
  <input type="hidden" name="item_number_{{ i }}" value="{{ product.model }}" />
  <input type="hidden" name="amount_{{ i }}" value="{{ product.price }}" />
  <input type="hidden" name="quantity_{{ i }}" value="{{ product.quantity }}" />
  <input type="hidden" name="weight_{{ i }}" value="{{ product.weight }}" />
  {% set j = 0 %}
  {% for option in product.option %}
  <input type="hidden" name="on{{ j }}_{{ i }}" value="{{ option.name }}" />
  <input type="hidden" name="os{{ j }}_{{ i }}" value="{{ option.value }}" />
  {% set j = j + 1 %}
  {% endfor %}
  {% set i = i + 1 %}
  {% endfor %}
  {% if discount_amount_cart %}
  <input type="hidden" name="discount_amount_cart" value="{{ discount_amount_cart }}" />
  {% endif %}
  <input type="hidden" name="currency_code" value="{{ currency_code }}" />
  <input type="hidden" name="first_name" value="{{ first_name }}" />
  <input type="hidden" name="last_name" value="{{ last_name }}" />
  <input type="hidden" name="address1" value="{{ address1 }}" />
  <input type="hidden" name="address2" value="{{ address2 }}" />
  <input type="hidden" name="city" value="{{ city }}" />
  <input type="hidden" name="zip" value="{{ zip }}" />
  <input type="hidden" name="country" value="{{ country }}" />
  <input type="hidden" name="address_override" value="0" />
  <input type="hidden" name="email" value="{{ email }}" />
  <input type="hidden" name="invoice" value="{{ invoice }}" />
  <input type="hidden" name="lc" value="{{ lc }}" />
  <input type="hidden" name="rm" value="2" />
  <input type="hidden" name="no_note" value="1" />
  <input type="hidden" name="no_shipping" value="1" />
  <input type="hidden" name="charset" value="utf-8" />
  <input type="hidden" name="return" value="{{ return }}" />
  <input type="hidden" name="notify_url" value="{{ notify_url }}" />
  <input type="hidden" name="cancel_return" value="{{ cancel_return }}" />
  <input type="hidden" name="paymentaction" value="{{ paymentaction }}" />
  <input type="hidden" name="custom" value="{{ custom }}" />
  <input type="hidden" name="bn" value="OpenCart_2.0_WPS" />
  <div class="buttons">
    <div class="pull-right">
      <input type="submit" value="{{ button_confirm }}" class="btn btn-primary" />
    </div>
  </div>
</form>

No header, no footer, no left or right column are needed on the template, with this, only the submit button is shown. Check the following code, this is to show the text saying it is only for test.

{% if testmode %}
<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ text_testmode }}</div>
{% endif %}

In this way, we write the code at the catalog template of PayPal Standard, similarly way we can write for other payment extensions. Please let us know in the comment if we need to define any of the code above.

Now once the Paypal Standard is activated then it will show like below:

Paypal Standard Opencart

In this way, you can create the form in the admin section, validate the form data, validate the permission, and save the data to the database in the setting database table for the Payment extension and show it in the front. Hope you liked this article, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook.

How to create a payment module or extension in the Opencart 3? Developer guide Part I of III

In this Opencart tutorial developer guide, we are showing how to create a custom payment module or extension in the Opencart version 3. Opencart by default supports more than 45 payment modules. Log in into admin, go to Extensions >> Extensions >> Choose the extension type “Payments”, you can see the lists of payments module in Opencart.

Here is the flow of the most eCommerce website.

eCommerce flow diagram

Today we are showing you postmortem the payment gateway section.

Here are some of the normal steps that happen in the payment section:

ecommerce payment flow
  1. When the customer is ready to pay for goods or services on your website, they select the payment method on your website.
  2. There are two ways of payment gateway processing:
    • One is Off-site payment processing
      In the off-site payment, the website is redirected to the payment service website and payment is made on the payment service website.
      There will be no place to enter the credit card information or login to payment gateways.
      The payment gateway provides a Payment form in which we matched similar fields.
      Data are passed from Form.
      Need return URL or cancel URL so that when payment is a success it returns to that return URL and if the payment fails then it returns to the cancel URL.
      Skrill, Paypal Standard, Liqpay, etc. are the default payment modules for Off-site payment processing.
    • Another is on-site payment processing.
      In the on-site payment, the payment is made on the same site, the payment processing is done through APIs.
      The payment gateway provides a JSON request.
      There will be a form where they can enter the credit card details or bank information etc.
      Data are passed as JSON format.
      It will return success or failure messages through API in JSON format. Once you get the success or failure data then further processing can be done in the website server and the message can be shown or redirected to the success page.
      Authorize.net’s AIM, Paypal Pro, Square, etc. are the default payment modules for on-site payment processing.
  3. When the payment method is Off-side payment processing then data are passed on hidden fields in the form. If we check the documentation of NoChex you can see the form code like below:
    Off site payment form code
    https://ssl.nochex.com/downloads/Payment%20Guides/payment_page_integration_guide.pdf page no. 5

    Mostly the payment gateway provides us with the form method type, action URL, and fields that we can pass to them for processing. Once you click the “Confirm Order” button, these hidden fields data are passed to the payment gateway and processing happens in their website.
  4. When a payment gateway process the payment and is successful then the customer is directed to the success URL.
  5. When the payment method is On-side payment processing then data are passed from website Server to payment gateway through API, as we are using Opencart which is builtin PHP so we use CURL to call the API.
  6. The website server passes the data to the payment gateway through API, mostly CURL is used to communicate with the APIs, we pass JSON-formatted HTTP requests to the API endpoint in either the sandbox or production environment of the payment gateway.
  7. Payment gateway requests authorization for the payment from the customer’s bank, third party provider, or card issuer.
  8. The bank/provider/card issuer approves or rejects the transaction.
  9. Payment gateway sends the success or failure response, containing the transaction result, and calls the Callback where the website server can process the data.
  10. The website server processes data returned by the payment gateway and it redirects to the success page.

These are the steps that happen on Opencart for payments. So we need to take each step into consideration while developing the payment extensions.

Checklist for Developers while integrating the payment extension

Here is the checklist for the developer while integrating the payment extension on the Opencart or custom platform

  1. Always make a google search if the Opencart payment extension is available, sometimes it is available for free.
  2. If free is not available then start by creating/asking an account and visiting the documentation.
  3. Mostly each payment gateway has a test environment so get test credentials. Create a playground account to get test credentials and get familiar with the test environment.
  4. Check out the PHP SDKs, with this, it will be easy to understand the code. Like for Paypal https://packagist.org/packages/paypal/paypal-checkout-sdk
  5. Follow the integration guide. Each of the product documentation provides an integration guide. Follow the instructions to make sure your payment process works as needed.
  6. Be sure to check if what ISO country codes (3‐digit or 4-digit), what language support, and what currencies support are available for that payment gateway.
  7. Check all your APIs in Postman to confirm everything, before development
  8. Test the system. Make sure everything works. Remember you can place a test order using test credentials on Postman.
  9. Be sure to read how error handling is done, how many rate limits, if any domains & IP address restrictions, policies, and compliance
  10. After reading the documentation, now you can decide whether you need to make the Off-site payment extension or on-site payment extension.
  11. Then start the development.

In our next post, we will show you how to create the off-site payment extensions, we start by cloning the Skrill or Opencart Paypal standard payment extension, which will help us to understand how we can create the Opencart payment extensions. Till then, please let us know if you need any help or custom development, and keep on learning Opencart.

Bibliography

Opencart code snippets VSCode extensions

Today we launched OpenCart code snippets VSCode extensions which you can find at https://marketplace.visualstudio.com/items?itemName=webocreationcom.ocsnippets, We published OpenCart code snippets VScode extensions for OpenCart developers. It contains some OpenCart code snippets for PHP and Twigs. Github repository for this https://github.com/rupaknepali/opencartsnippetsvscode.

How to use Opencart code snippets VSCode extensions?

  • Find “Opencart code snippets” extensions to install using the Extensions View.
  • Install an extension.
VScode extensions Install
  • Once you installed the “Opencart Code snippets”, open ***.php or ***.twig or ***.xml
  • Then start typing oc… then you will see a list of Opencart related code snippets available.
  • For example, create a file called install.xml, then start typing oc, it will show ocmod and ocmodf, these are the Opencart code snippets available in the VScode extensions that you installed.
Opencart code snippets
  • Once you finished the word like ocmod then it creates the codes automatically.
Ocmode code generated
  • It highlights the word to be changed, enter the word that you need and click tab, then it will highlights the another words to be change

In this way, you can use the OpenCart Code snippets for rapid development.

Creating and publishing Visual Studio code extensions

Creating and publishing Visual Studio code extensions is easy than I thought just follow https://code.visualstudio.com/api/get-started/your-first-extension and https://code.visualstudio.com/api/working-with-extensions/publishing-extension

vscode opencart

Opencart Snippets for Visual Studio Code

This VSCode extension is for OpenCart developer which has a collection of OpenCart snippets. This is just starting, Please let us know if need to add more Contact Us

Documentation

All snippets start with oc. Some of the snippets are:

PHP page supported snippets:

  • occ: It creates a new Controller class
  • ocm: It creates a new Method or function
  • occm: It creates new starter Model class
  • ocl: It creates new variables for language files
  • ocprf: It creates print_r with pre

Twig page supported snippets:

  • oct: It creates starting template layouts in twig files
  • octfor: It creates for loop in twig files
  • octif: It creates if statement in twig files
  • ocbtn: It creates button markup
  • ocimg: It creates image markup
  • ocbc: It creates breadcrumbs loop in twig files

OCMOD XML page supported snippets:

  • ocmod: It creates OCMOD XML code boilerplate
  • ocmodf: It creates OCMOD XML code boilerplate for files only

Release Notes

Users appreciate release notes as you update your extension.

1.2.0
– OCMOD support added.
– ocmod- for install.xml boilerplate creation
– ocmodf- OCMOD XML file boilerplate
– ocprf- print_r with pre

1.0.0

  • The initial release of Opencart Snippets for vscode
  • Added some snippets. Others are coming soon.

For more information

Please install the extension, try it out and share it with other Opencart developers which may help the development speed. It is free and open-source. Any suggestions, feedback, and other code snippets of Opencart are appreciated.

Creating a Custom Page in OpenCart 3 – categories listing page

In this opencart tutorial, we are showing you, how to create an additional custom page in my OpenCart 3 website by coding. We are showing you by creating the categories listing custom page in Opencart 3.0.3.1.

 “OpenCart theme and Module development” book says “OpenCart is an e-commerce shopping cart application built with its own in-house framework which uses MVCL (Model, view, controller, and language) pattern. Thus, each module in OpenCart also follows the MVCL pattern. The controller creates logic and gathers data from the model and it passes data to display in the view.” So to create a custom page, we will need a few files for it to work as a page.

Required files:

  1. A controller file
  2. A template file

Optional files:

  1. Model file
  2. Language file

Before you start reading below first, understand about the request and response in OpenCart, just understand the image and watch the following video which will clarify MVCL flow of Opencart.:

MVCL flow of opencart

https://webocreation.com/code-flow-request-response-mvcl-pattern-opencart

After watching the above video, you know about MVCL so it will be easy to understand below.

Controller File

Let’s start by creating a controller file in the controller folder. For this tutorial, we create a file named ‘categorieslist.php’ in the /catalog/controller/catalog/ folder. Since we named the file categorieslist.php and put it at the catalog/ folder, the controller class name will be ControllerCatalogCategorieslist which inherits the Controller like below, if you are thinking of any other custom pages then you also need to do same.

<?php
class ControllerProductCategorieslist extends Controller
{
    public function index()
    {
        echo "This is category listing page";
    }
}

The user requests to the controller through URL, so the URL here become YOURSITE.com/index.php?route=product/categorieslist. I was working in my localhost with “opencart.loc” domain so it shows like below:

Categories list page in Opencart

If you get an error like below don’t panic just got to Admin >> Extensions >> Modifications and click the Refresh button.

Fatal error: Uncaught Error: Class ‘Controllerproductcategorieslist’ not found in …/modification/system/engine/action.php:71 Stack trace: #0 ../catalog/controller/startup/router.php(25): Action->execute(Object(Registry)) #1 …/modification/system/engine/action.php(79): ControllerStartupRouter->index() #2 /system/engine/router.php(67): Action->execute(Object(Registry)) #3 /system/engine/router.php(56): Router->execute(Object(Action)) #4 /system/framework.php(165): Router->dispatch(Object(Action), Object(Action)) #5 system/startup.php(104): require_once(‘/Applications/X…’) #6 /index.php(19): start(‘catalog’) #7 {main} thrown in modification/system/engine/action.php on line 71

Clear the cache in Opencart

Auto Cache clearing instead of doing manually

https://webocreation.com/twig-cache-removing-while-developing-theme-or-module-developer-tips/

Activating twig debugging

Go to /system/library/template/twig.php •Find this line:
$this->twig = new \Twig_Environment($loader, $config);

Below that line adds following two lines:

//Adding Twig Debug Extension
 $this->twig->addExtension(new \Twig_Extension_Debug()); 

These will help on easy debugging and no need to keep on clearing the cache again and again.

Now let’s add header, footer, column left, column right, content top and content-bottom like below and render the response output to categories list template.

<?php
class ControllerProductCategorieslist extends Controller
{
    public function index()
    {
        $data['column_left'] = $this->load->controller('common/column_left');
        $data['column_right'] = $this->load->controller('common/column_right');
        $data['content_top'] = $this->load->controller('common/content_top');
        $data['content_bottom'] = $this->load->controller('common/content_bottom');
        $data['footer'] = $this->load->controller('common/footer');
        $data['header'] = $this->load->controller('common/header');
        $this->response->setOutput($this->load->view('product/categorieslist', $data));
    }
}

As we write in our code $this->response->setOutput($this->load->view(‘product/categorieslist’, $data));, it means we are outputting code in categorieslist.twig file at catalog/theme/view/THEMENAME/template/product/categorieslist.twig. Let create the categorieslist.twig in that folder and put the following code:

{{ header }}
{{ column_left }}
{{ column_right }}
<div class="container">
<div class="col-sm-12">
This is categories list page
</div>
</div>
{{ content_top}}
{{ content_bottom }}
{{ footer }} 

Then go to the URL YOURSITE.com/index.php?route=product/categorieslist and you will see like below with header and footer as we have not added any modules for this pages so there will be no module in this page for now:

Custom page created in Opencart

If you don’t see the page, it means you have not cleared the theme cache and SCSS cache, which you can do from the admin dashboard:

Clear theme cache and SCSS cache in Opencart

Template File

Nothing fancy here, but whatever data you passed from the controller in $data[‘YOURVARIABLE’], you have access to the YOURVARIABLE in template twig file and just output as {{ YOURVARIABLE }}. Like this way you build logic in the controller, get the desired results and assign it in $data array and send it to the template.

Language File

OpenCart supports multi-language. So let’s create a language file and transfer data from language file to template twig file. For that let’s create a file named categorieslist.php in catalog/language/en-gb/product/categorieslist.php and paste the code below:

<?php
// Text
$_['meta_title']        = 'All Categories';
$_['meta_description']  = 'All Categories';
$_['meta_keyword']      = 'All Categories';
$_['categories_text']   = 'This is categories list page';

Whatever you define here, it will be access to template twig file when you load in a file in the controller. Now let’s change the /catalog/controller/catalog/categorieslist.php like below:

<?php
class ControllerProductCategorieslist extends Controller
{
    public function index()
    {
        $this->load->language('product/categorieslist');
        $this->document->setTitle($this->language->get('meta_title'));
        $this->document->setDescription($this->language->get('meta_description'));
        $this->document->setKeywords($this->language->get('meta_keyword'));

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

        $this->response->setOutput($this->load->view('product/categorieslist', $data));
    }
}

This is how we load the language files in the controller:

$this->load->language('product/categorieslist');

Then we can use the language file variable in controller and language file. In the controller, we use the language variable like:

$this->language->get('meta_title')

In the template twig file that is rendered by the controller, we can use directly with {{LANGUAGEVARIABLE}}, so our catalog/theme/view/THEMENAME/template/product/categorieslist.twig can directly access to categories_text variable of the language file.

{{ header }}
{{ column_left }}
{{ column_right }}
<div class="container">
<div class="col-sm-12">
{{categories_text}}
</div>
</div>
{{ content_top}}
{{ content_bottom }}
{{ footer }} 

Please be careful you can directly access to language variable into the twig file in Opencart 3, in OpenCart you cannot directly access without assigning to $data variable in the controller.

You can download all files from this tutorial in the file below:

We have successfully made our custom page. W can now access the page we created from YOURSITE.com/index.php?route=product/categorieslist .

In our next post, we will show all logics implemented to pull the categories from the database and work with the model class, databases, whole twig files by which we will make a whole module to show categories on a page.

Please let us know if you have any questions, suggestions and follow us at twitter @rupaknpl and subscribe to our youtube channel Opencart tutorials where you can see OpenCart tutorials for beginners to advance programmers.

Swapping image when hovering over in products – Opencart free module

Another free Opencart module for this Christmas and Happy New Year, we are providing Opencart module when activated we can add hovering image and in the frontend when we hover over to the image in products of category page it will swap with the hovering image.

How to install the swapping image Opencart module?

First, download the module by clicking the download button above and you will get hoveroverimage.ocmod.zip file. Then go to admin >> Extensions >> Installer >> then upload the hoveroverimage.ocmod.zip file.

Second go to admin >> Extensions >> Modifications >> then click the refresh button

Third, go to admin >> Extensions >> Extensions >> Choose module in extension type >> then find the “Hover Over Image” and click the install button for the Hover Over Image module. Once you install click edit and then choose enabled and click save.

Fourth, go to admin >> Catalog >> Products >> add or edit the product >> then enter the details of the product and click the Image tab, where you will see the “Upload Hover over Image” field, upload the image to show when it hovers over.

swapping image on hover Opencart

Finally, go to the frontend and visit the category where the product is assigned and then hover over the product image and see the swap of the image.

On hover image change opencart

You installed the module, configured it and see the result like above. Let us know if you see any issues or want any improvement.

For developers:

The OCMOD file https://github.com/rupaknepali/Opencart-free-modules/blob/master/hoveroverimage/install.xml will modify the following files:

modified:   admin/controller/catalog/product.php
modified:   admin/language/en-gb/catalog/product.php
modified:   admin/model/catalog/product.php
modified:   admin/view/template/catalog/product_form.twig
modified:   catalog/controller/product/category.php
modified:   catalog/model/catalog/product.php
modified:   catalog/view/theme/default/template/product/category.twig

The new files added when you upload the module are the following:

added:      admin/controller/extension/module/hoveroverimage.php
added:      admin/language/en-gb/extension/module/hoveroverimage.php
added:      admin/model/extension/module/hoveroverimage.php
added:      admin/view/template/extension/module/hoveroverimage.twig

Once you upload the module then install the module, then it will create the oc_product_to_image_back database table. We need to create a new table because Opencart 3 doesn’t allow us to alter the core database tables.

public function installTableBackImage()
{
    $this->db->query("CREATE TABLE `" . DB_PREFIX ."product_to_image_back` ( `id` INT NOT NULL AUTO_INCREMENT , `image_back` VARCHAR(255) NOT NULL , `product_id` INT NOT NULL , PRIMARY KEY (`id`))");
}

public function dropTableBackImage()
{
    $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "product_to_image_back`");
}

When you upload the image in “Upload Hover over Image” field at product section then all data are stored in the DB_PREFIX.product_to_image_back database table.

The Ocmod install.xml contains the following code:

<?xml version="1.0" encoding="utf-8"?>
<modification>
  <name>Module Hover Over Image</name>
  <version>1.0</version>
  <author>Rupak Nepali</author>
  <link>https://webocreation.com</link>
  <code>webocreation_module_hover_over_image</code>
  <file path="admin/view/template/catalog/product_form.twig">
    <operation>
      <search><![CDATA[ <div class="tab-pane" id="tab-image"> ]]></search>
      <add position="after"><![CDATA[
            {% if image_back_status %}
              <div class="table-responsive">
                {# HoverOver Image #}
                <table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<td class="text-left">{{ entry_image_back }}</td>
</tr>
</thead>
<tbody>
<tr>
<td class="text-left"><a href="" id="thumb-image-back" data-toggle="image" class="img-thumbnail"><img src="{{ thumb_back }}" alt="" title="" data-placeholder="{{ placeholder }}" /></a><input type="hidden" name="image_back" value="{{ image_back }}" id="input-image-back" /></td>
</tr>
</tbody>
</table>
</div>
            {% endif %}
            ]]>
      </add>
    </operation>
  </file>

  <file path="admin/controller/catalog/product.php">
    <operation>
      <search><![CDATA[if (isset($this->request->post['product_image'])) {]]></search>
      <add position="before"><![CDATA[
                // Hoverover Image
    $data['image_back_status'] = $this->config->get('module_hoveroverimage_status');
    if ($this->config->get('module_hoveroverimage_status')) {
		if (isset($this->request->post['image_back'])) {
			$data['image_back'] = $this->request->post['image_back'];
		} elseif (isset($this->request->get['product_id'])) {
			$image_back= $this->model_catalog_product->getBackProductImage($this->request->get['product_id']);
			if (!empty($image_back['image_back'])){
				$data['image_back'] = $image_back['image_back'];
			}	
		} else {
			$data['image_back'] = array();
		}

		if (isset($this->request->post['image_back']) && is_file(DIR_IMAGE . $this->request->post['image_back'])) {
			$data['thumb_back'] = $this->model_tool_image->resize($this->request->post['image_back'], 100, 100);
		} elseif (!empty($image_back) && is_file(DIR_IMAGE . $image_back['image_back'])) {
			$data['thumb_back'] = $this->model_tool_image->resize($image_back['image_back'], 100, 100);
		} else {
			$data['thumb_back'] = $this->model_tool_image->resize('no_image.png', 100, 100);
		}
    }
            ]]>      </add>
    </operation>
  </file>

  <file path="admin/language/en-gb/catalog/product.php">
    <operation>
      <search><![CDATA[ $_['error_unique'] ]]></search>
      <add position="before"><![CDATA[ $_['entry_image_back'] = 'Upload Hoverover Image'; ]]></add>
    </operation>
  </file>

  <file path="admin/model/catalog/product.php">
    <operation>
      <search><![CDATA[if (isset($data['image'])) {]]></search>
      <add position="before"><![CDATA[ 
        //Hoverover Image
        if ($this->config->get('module_hoveroverimage_status')) {
        if (isset($data['image_back'])) {
          $this->db->query("DELETE FROM " . DB_PREFIX . "product_to_image_back WHERE product_id = '" . (int)$product_id . "'");
          $this->db->query("INSERT INTO " . DB_PREFIX . "product_to_image_back SET product_id = '" . (int)$product_id . "', image_back = '" . $this->db->escape($data['image_back']) . "'");
        }
        }
         ]]>      </add>
    </operation>
  </file>
  <file path="admin/model/catalog/product.php">
    <operation>
      <search><![CDATA[public function getProductRelated($product_id) {]]></search>
      <add position="before"><![CDATA[ 
          //Hoverover Image
          public function getBackProductImage($product_id) {
            $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "product_to_image_back WHERE product_id = '" . (int)$product_id . "'");

            return $query->row;
          }
        ]]>      </add>
    </operation>
  </file>

  <file path="catalog/model/catalog/product.php">
    <operation>
      <search><![CDATA[public function getProductRelated($product_id) {]]></search>
      <add position="before"><![CDATA[ 
          //Hoverover Image
          public function getBackProductImage($product_id) {
            $query = $this->db->query("SELECT * FROM " . DB_PREFIX . "product_to_image_back WHERE product_id = '" . (int)$product_id . "'");

            return $query->row;
          }
        ]]>      </add>
    </operation>
  </file>

  <file path="catalog/controller/product/category.php">
    <operation>
      <search><![CDATA[$data['products'][] = array(]]></search>
      <add position="before"><![CDATA[ 
        //Hoverover Image
        $image_back_final = "";
        $data['image_back_status'] = $this->config->get('module_hoveroverimage_status');
        if ($this->config->get('module_hoveroverimage_status')) {
            $image_back = $this->model_catalog_product->getBackProductImage($result['product_id']);
            if (isset($image_back['image_back'])) {
              $image_back_final = $this->model_tool_image->resize($image_back['image_back'], $this->config->get('theme_' . $this->config->get('config_theme') . '_image_product_width'), $this->config->get('theme_' . $this->config->get('config_theme') . '_image_product_height'));
            } else {
              $image_back_final = "";
            }
        }
      ]]>      </add>
    </operation>
  </file>

  <file path="catalog/controller/product/category.php">
    <operation>
      <search><![CDATA[$data['products'][] = array(]]></search>
      <add position="after"><![CDATA['thumb_back'       => $image_back_final, //Hoverover Image]]></add>
    </operation>
  </file>

  <file path="catalog/view/theme/default/template/product/category.twig">
    <operation>
      <search><![CDATA[<img src="{{ product.thumb }}" alt="{{ product.name }}" title="{{ product.name }}" class="img-responsive"/>]]></search>
      <add position="replace"><![CDATA[ 
        {% if product.thumb_back %}
            <img src="{{ product.thumb }}" alt="{{ product.name }}" title="{{ product.name }}" onmouseover="this.src='{{ product.thumb_back }}'" onmouseout="this.src='{{ product.thumb }}'" class="img-responsive"/>
        {% else %}
            <img src="{{ product.thumb }}" alt="{{ product.name }}" title="{{ product.name }}" class="img-responsive"/>
        {% endif %}
        ]]>      </add>
    </operation>
  </file>

</modification>

In this way, you can set up the swapping the image when hovering over in products on the category page. Let us know if need any support. Please subscribe to our YouTube Channel for Opencart video tutorials and get lots to other Opencart free modules. You can also find us on Twitter and Facebook. Merry Christmas and Happy New Year 2020.

Free checkout all orders are going to missing orders in Opencart 3.0.2.0

We activate the free checkout payment module in Opencart 3.0.2.0, then we start ordering but all orders go to the Missing Orders in Sales, so debugging we found out that somehow the free checkout files were not updated properly in the Opencart version 3.0.2.0. Here are the changes that we did to make it work:

Open the file admin/controller/extension/payment/free_checkout.php and find the following lines of code:

if (isset($this->request->post['free_checkout_order_status_id'])) {
	$data['free_checkout_order_status_id'] = $this->request->post['free_checkout_order_status_id'];
} else {
	$data['free_checkout_order_status_id'] = $this->config->get('free_checkout_order_status_id');
}

Replace it with the following:

if (isset($this->request->post['payment_free_checkout_order_status_id'])) {
	$data['payment_free_checkout_order_status_id'] = $this->request->post['payment_free_checkout_order_status_id'];
} else {
	$data['payment_free_checkout_order_status_id'] = $this->config->get('payment_free_checkout_order_status_id');
}

See the “payment_”, as we mentioned in Single instance Opencart module development tutorial all single instance payment module fields should start with “payment_”, it was missing in the above code that is why it was not working.

Similarly, open admin/view/template/extension/payment/free_checkout.twig and find the following lines of code:

<select name="free_checkout_order_status_id" id="input-order-status" class="form-control">

Replace with the following code:

<select name="payment_free_checkout_order_status_id" id="input-order-status" class="form-control">

You can download the module below which was developed before we find the above solution, next time we need to search more.

Download and upload the files to the respective folder which will override the files and you are good to go. With these changes, our orders are showing in Sales.

In this way, you can solve the issues of free checkout sales going to the missing order’s sales. Please don’t forget to post your questions or comments so that we can add extra topics. You can follow us at our twitter account @rupaknpl. Subscribe to our YouTube channel for Opencart tutorials, and click to see all Opencart free module.

Opencart 3 module development tutorial, multi-instance testimonial free

In this opencart tutorial, we are showing you how to create a multi-instance opencart 3 module step by step, we are making a testimonial module as an example, click to download this module for free. In our last two posts, we show you how to make the single instance module: Single Instance module, Form creation, validation, and submission to the database and Single Instance module frontend login module. Check the difference between the single instance and multi-instance opencart module. Similarly, one of our blog posts describes how to perform Opencart CRUD functionalities of testimonials, create an admin custom listing page, form, validate it and save in the database, now we will use those testimonials to show in the multi-instance module in the admin section and in next post we will show you the frontend section code.

Here are the files and folders structure of testimonial module:

  • admin/controller/extension/module/testimonial.php
  • admin/language/en-gb/extension/module/testimonial.php
  • admin/view/template/extension/module/testimonial.twig
  • catalog/controller/extension/module/testimonial.php
  • catalog/model/extension/module/testimonial.php
  • catalog/language/en-gb/extension/module/testimonial.php
  • catalog/view/theme/default/template/extension/module/testimonial.twig

In this post, we will show only admin code, in the next post we will show you the catalog or frontend code.

The code in admin/controller/extension/module/testimonial.php describes the controller code of the multi-instance opencart module. We describe the code in the comment:

<?php
/*** As our file is testimonial.php so Class name is ControllerExtensionModuleTestimonial which extends the Controller base class ***/
class ControllerExtensionModuleTestimonial extends Controller
{
	/*** Declaration of the private property 'error' so that we can get check if any error occurs in the whole class**/
	private $error = array();
	/*** Index method is called automatically or by default, if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4 . ***/
	public function index()
	{
		/*** Loads the language file admin/language/en-gb/extension/module/testimonial.php by which the varaibles of the language file are accessible in twig file. ***/
		$this->load->language('extension/module/testimonial');
		/*** Set the Document title ***/
		$this->document->setTitle($this->language->get('heading_title'));
		/*** Loads the model admin/model/setting/module.php so that we can use the methods defined there. The difference in single instance and multi-instance is here, in single instance we used to load  $this->load->model('setting/setting'); but here we load setting/module ***/
		$this->load->model('setting/module');
		/*** This is how we check if it is form submitted or not. When we submit the form then this block of code also run. Then it also validate the modify permission and other validation. ***/
		if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
			/*** Look at these lines of code, this code section which distinguishes from Single Instance to Multi-instance. If it is single instance then it will be like 	$this->model_setting_setting->editSetting('***', $this->request->post); This editSetting save the data to  oc_setting database table. But in multi-instance the code is like below it checks if the module_id is set if it is set then it get edit module data by editModule method, but if someone click Add button then no module_id is set and it adds new module data to the module database table. ***/
			if (!isset($this->request->get['module_id'])) {
				$this->model_setting_module->addModule('testimonial', $this->request->post);
			} else {
				$this->model_setting_module->editModule($this->request->get['module_id'], $this->request->post);
			}
			/*** This is to delete the cache of the testimonial. ***/
			$this->cache->delete('testimonial');
			/*** This set the success message in the session. ***/
			$this->session->data['success'] = $this->language->get('text_success');
			/*** This is to redirect to the extensions page. ***/
			$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
		}
		/*** This is to check if there are any warnings	 ***/	
		if (isset($this->error['warning'])) {
			$data['error_warning'] = $this->error['warning'];
		} else {
			$data['error_warning'] = '';
		}
		/*** As we are validating 'Module Name' in the validate method so if validate method returns name error then it is assigned here and the error is shown in the form field. ***/
		if (isset($this->error['name'])) {
			$data['error_name'] = $this->error['name'];
		} else {
			$data['error_name'] = '';
		}
		/*** This is also to set the error for the form field width ***/
		if (isset($this->error['width'])) {
			$data['error_width'] = $this->error['width'];
		} else {
			$data['error_width'] = '';
		}
		/*** This is also to set the error for the form field height ***/
		if (isset($this->error['height'])) {
			$data['error_height'] = $this->error['height'];
		} else {
			$data['error_height'] = '';
		}

		/*** Following are for breadcrumbs ***/
		$data['breadcrumbs'] = array();
		$data['breadcrumbs'][] = array(
			'text' => $this->language->get('text_home'),
			'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
		);
		$data['breadcrumbs'][] = array(
			'text' => $this->language->get('text_extension'),
			'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
		);
		/*** Check this section of if else statement, we see the difference in single instance and multi-instance. Here it is checking if module_id is set then it has different href URl and if not set different URL. ***/
		if (!isset($this->request->get['module_id'])) {
			$data['breadcrumbs'][] = array(
				'text' => $this->language->get('heading_title'),
				'href' => $this->url->link('extension/module/testimonial', 'user_token=' . $this->session->data['user_token'], true)
			);
		} else {
			$data['breadcrumbs'][] = array(
				'text' => $this->language->get('heading_title'),
				'href' => $this->url->link('extension/module/testimonial', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true)
			);
		}
		/*** Form action URL as per the module edited or for new module ***/
		if (!isset($this->request->get['module_id'])) {
			$data['action'] = $this->url->link('extension/module/testimonial', 'user_token=' . $this->session->data['user_token'], true);
		} else {
			$data['action'] = $this->url->link('extension/module/testimonial', 'user_token=' . $this->session->data['user_token'] . '&module_id=' . $this->request->get['module_id'], true);
		}
		/*** Form cancel URL ***/
		$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
		/*** This is to get the module data, if module_id is set then it gets module data from database and set it to $module_info variable. ***/
		if (isset($this->request->get['module_id']) && ($this->request->server['REQUEST_METHOD'] != 'POST')) {
			$module_info = $this->model_setting_module->getModule($this->request->get['module_id']);
		}
		/*** This will check if it form filled and clicked save button and occurs some errors then it will set the name with POST name value. If it is edit then it will set the name value with the module detail. If it is add then empty is set. ***/
		if (isset($this->request->post['name'])) {
			$data['name'] = $this->request->post['name'];
		} elseif (!empty($module_info)) {
			$data['name'] = $module_info['name'];
		} else {
			$data['name'] = '';
		}
		/*** Same as above this will check if it form filled and clicked save button and occurs some errors then it will set the limit with POST limit value. If it is edit then it will set the limit value with the module detail. If it is add then empty is set. ***/
		if (isset($this->request->post['limit'])) {
			$data['limit'] = $this->request->post['limit'];
		} elseif (!empty($module_info)) {
			$data['limit'] = $module_info['limit'];
		} else {
			$data['limit'] = 5;
		}
		/*** Same as above this will check if it form filled and clicked save button and occurs some errors then it will set the width with POST width value. If it is edit then it will set the width value with the module detail. If it is add then default value is set to 200. ***/
		if (isset($this->request->post['width'])) {
			$data['width'] = $this->request->post['width'];
		} elseif (!empty($module_info)) {
			$data['width'] = $module_info['width'];
		} else {
			$data['width'] = 200;
		}
		/*** Same as above this will check if it form filled and clicked save button and occurs some errors then it will set the height with POST height value. If it is edit then it will set the height value with the module detail. If it is add then default value is set to 200. ***/
		if (isset($this->request->post['height'])) {
			$data['height'] = $this->request->post['height'];
		} elseif (!empty($module_info)) {
			$data['height'] = $module_info['height'];
		} else {
			$data['height'] = 200;
		}
		/*** Same as above this will check if it form filled and clicked save button and occurs some errors then it will set the status with POST status value. If it is edit then it will set the status value with the module detail. If it is add then default value is set to empty. ***/
		if (isset($this->request->post['status'])) {
			$data['status'] = $this->request->post['status'];
		} elseif (!empty($module_info)) {
			$data['status'] = $module_info['status'];
		} else {
			$data['status'] = '';
		}
		/*** This is how we load the header, column left and footer ***/
		$data['header'] = $this->load->controller('common/header');
		$data['column_left'] = $this->load->controller('common/column_left');
		$data['footer'] = $this->load->controller('common/footer');
		/*** This is to set output data variables to the view or twig files and twig file is loaded and html rendering is done with it. ***/
		$this->response->setOutput($this->load->view('extension/module/testimonial', $data));
	}
	/*** This is how validation is done, we check whether the user has permission to modify or not.***/
	/*** Here we validate name, width and height ***/
	/*** If there is error then $this->error['warning'] is set and warning are shown.***/
	protected function validate()
	{
		if (!$this->user->hasPermission('modify', 'extension/module/testimonial')) {
			$this->error['warning'] = $this->language->get('error_permission');
		}
		if ((utf8_strlen($this->request->post['name']) < 3) || (utf8_strlen($this->request->post['name']) > 64)) {
			$this->error['name'] = $this->language->get('error_name');
		}
		if (!$this->request->post['width']) {
			$this->error['width'] = $this->language->get('error_width');
		}
		if (!$this->request->post['height']) {
			$this->error['height'] = $this->language->get('error_height');
		}
		return !$this->error;
	}
}

For language we create testimonail.php file at admin/language/en-gb/extension/module/ and paste the following code. We defined required language variables and assign them the text of that language.

<?php
// Heading
$_['heading_title']    = 'Testimonial';
// Text
$_['text_extension']   = 'Extensions';
$_['text_success']     = 'Success: You have modified Testimonial module!';
$_['text_edit']        = 'Edit Testimonial Module';
// Entry
$_['entry_name']       = 'Module Name';
$_['entry_limit']      = 'Limit';
$_['entry_width']      = 'Width';
$_['entry_height']     = 'Height';
$_['entry_status']     = 'Status';
// Error
$_['error_permission'] = 'Warning: You do not have permission to modify Testimonial module!';
$_['error_name']       = 'Module Name must be between 3 and 64 characters!';
$_['error_width']      = 'Width required!';
$_['error_height']     = 'Height required!';

For the form page, we create testimonial.twig at admin/view/template/extension/module/ and add the following lines of code, they are self-explanatory, let us know in the comment is you didn’t understand anything.

{{ header }}{{ column_left }}
<div id="content">
  <div class="page-header">
    <div class="container-fluid">
      <div class="pull-right">
        <button type="submit" form="form-module" 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-module" class="form-horizontal">
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-name">{{ entry_name }}</label>
            <div class="col-sm-10">
              <input type="text" name="name" value="{{ name }}" placeholder="{{ entry_name }}" id="input-name" class="form-control" />
              {% if error_name %}
              <div class="text-danger">{{ error_name }}</div>
              {% endif %}
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-limit">{{ entry_limit }}</label>
            <div class="col-sm-10">
              <input type="text" name="limit" value="{{ limit }}" placeholder="{{ entry_limit }}" id="input-limit" class="form-control" />
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-width">{{ entry_width }}</label>
            <div class="col-sm-10">
              <input type="text" name="width" value="{{ width }}" placeholder="{{ entry_width }}" id="input-width" class="form-control" />
              {% if error_width %}
              <div class="text-danger">{{ error_width }}</div>
              {% endif %}
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-height">{{ entry_height }}</label>
            <div class="col-sm-10">
              <input type="text" name="height" value="{{ height }}" placeholder="{{ entry_height }}" id="input-height" class="form-control" />
              {% if error_height %}
              <div class="text-danger">{{ error_height }}</div>
              {% endif %}
            </div>
          </div>
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-status">{{ entry_status }}</label>
            <div class="col-sm-10">
              <select name="status" id="input-status" class="form-control">
                {% if 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>
        </form>
      </div>
    </div>
  </div>
</div>
{{ footer }}

In our next post, we will create the frontend section of the multi-instance testimonial module and show it in different layouts.

Like this way we create a testimonial module setting form in Opencart admin, validate the form, submit the form data to the database, list out the multiple instances of the module in the module listing page, edit each module and update it, and we can perform the delete from the module listing page, like this we create admin section of the multi-instance module in Opencart.

Hope you liked this post, let us know if you have any questions or suggestions, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook. Enjoy!

Single Instance module frontend login module, Opencart 3 documentation

In our previous post, we show how to create an admin section of a module in Opencart 3, today in this Opencart documentation we will show you the frontend code of the single instance module, download the login module and install it, and show it in the frontend page.

How to install the login module?

Steps to install Opencart 3 module:

  1. Download the login module

    Once you download the login module, you will see zip file named login-module.ocmod.zip

  2. Upload the zip file from extension installer, refresh in modification and install it

    Go to Admin >> Extensions >> Installer and upload the login-module.ocmod.zip. Then go to Admin >> Extensions >> Modifications and click Refresh button. Then go to Admin >> Extensions >> Extensions >> Choose Module >> find Login module then click the install blue button. Then click the edit button and select status to Enabled and click Save button.

  3. Add to the layout to show in the frontend

    Go to Admin >> Design >> Layouts >> edit one of the layouts, in our case let’s edit the Contact layout. Then add the login module in the left column. Then go to the contact us page and you can see like below:
    Opencart 3 login module

Following are the frontend files for the login module:

  • catalog/language/en-gb/extension/module/login.php
  • catalog/controller/extension/module/login.php
  • catalog/view/theme/default/template/extension/module/login.twig

Let’s start with the Language file admin/language/en-gb/extension/module/login.php, let’s define some variables which are useful for module: https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/catalog/language/en-gb/extension/module/login.php

<?php
// Text
$_['text_login']                   = 'Login';
$_['text_register']                = 'Register Account';
$_['text_forgotten']               = 'Forgotten Password';
// Entry
$_['entry_email']                  = 'E-Mail Address';
$_['entry_password']               = 'Password';

Now we work with the controller, open catalog/controller/extension/module/login.php, the code is in the GitHub link https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/catalog/controller/extension/module/login.php

<?php
As our file is login.php so Class name is ControllerExtensionModuleLogin which extends the Controller base class
class ControllerExtensionModuleLogin extends Controller {

Create the index method. The Index method is called automatically if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4. The difference between the single instance and multi-instance module is in the index method, multi-instance module’s index method is like “public function index($setting) {
public function index() {

Loads the language file by which the variables of language file are accessible in twig files
$this->load->language('extension/module/login');

This is to set the value if the customer is logged in or not if the customer is logged in then the value is 1
$data['logged'] = $this->customer->isLogged();

This is to set the form action URL, register link, and forgotten password URL 
$data['action'] = $this->url->link('account/login', '', true);
$data['register'] = $this->url->link('account/register', '', true);
$data['forgotten'] = $this->url->link('account/forgotten', '', true);


This is to set redirect so that when someone is logged in then they will get redirected to that page.
if (isset($this->request->post['redirect']) && (strpos($this->request->post['redirect'], $this->config->get('config_url')) !== false || strpos($this->request->post['redirect'], $this->config->get('config_ssl')) !== false)) {    $data['redirect'] = $this->request->post['redirect'];} elseif (isset($this->session->data['redirect'])) {    $data['redirect'] = $this->session->data['redirect'];    unset($this->session->data['redirect']);} else {    $data['redirect'] = '';}

This is to set the success message
if (isset($this->session->data['success'])) {    $data['success'] = $this->session->data['success'];    unset($this->session->data['success']);} else {    $data['success'] = '';}

To check is there is post value of an email is set, if not then it is empty
if (isset($this->request->post['email'])) {    $data['email'] = $this->request->post['email'];} else {    $data['email'] = '';}

To check is there is post value of an email is set, if not then it is empty
if (isset($this->request->post['password'])) {    $data['password'] = $this->request->post['password'];} else {    $data['password'] = '';}

To load the login view
return $this->load->view('extension/module/login', $data);}
}

With this the controller code is done, now let’s make the catalog/view/theme/default/template/extension/module/login.twig with the following code, GitHub link for the code is https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/catalog/view/theme/default/template/extension/module/login.twig

{% if not logged %}
    <div class="well">
        <form action="{{ action }}" method="post" enctype="multipart/form-data">
            <h2>Log in</h2>
            <div class="form-group">
                <label class="control-label" for="input-email">{{ entry_email }}</label>
                <input type="text" name="email" value="{{ email }}" placeholder="{{ entry_email }}" id="input-email" class="form-control"/>
            </div>
            <div class="form-group">
                <label class="control-label" for="input-password">{{ entry_password }}</label>
                <input type="password" name="password" value="{{ password }}" placeholder="{{ entry_password }}" id="input-password" class="form-control"/>
                <a href="{{ forgotten }}">{{ text_forgotten }}</a>
                /
                <a href="{{register}}">{{text_register}}</a>
            </div>
            <input type="submit" value="{{ button_login }}" class="btn btn-primary"/>
            {% if redirect %}
                <input type="hidden" name="redirect" value="{{ redirect }}"/>
            {% endif %}
        </form>
    </div>
{% endif %}

The above code is to show the form, with this login module is done.

Let’s list out the difference between a single instance module and multi-instance module

 

In our next post, we will start to create the admin section of the multi-instance module.

In this way, you can create a module in the frontend. Let us know if need any support. Hope you liked this post, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook.

Opencart Module Development: Single Instance module, Form creation, validation, and submission to the database

This Opencart tutorial is to learn about the single instance and multi-instance module and create admin section for single instance by creating the form in the admin, validate the form data and submit, edit or add data to the database. In our last two posts, we show the hello world module workflow and the basic hello world module in OpenCart, now we are showing how to create the form, validate it and submit it to the database.

We show you the way to show the simple text in our previous hello world module in Opencart. Now, let’s start with the normal flow that happens from frontend to backend:

  1. User logged into the Admin section
  2. They go to Admin >> Extensions >> Extensions >> Choose Modules >> Then edit the module
  3. After clicking the edit they will see a form
  4. They enter the data and click save or cancel button.
  5. If clicked cancel button then returns to the module listing page
  6. If clicked save button then first it will validate the permission of the user if they can have modified permission
  7. Then we check the data if it is POST or not and if it is POST then we save into database.
  8. While saving the module data in the database, there are two ways as Opencart support two modules way:
    1. One way is a single instance
    2. Another way is multi-instance

      Single vs Multi instance opencart module
  9. Single instance module means it has one module only, install, edit and save the same module and use the same module in different layouts. It will not create another instance of modules. Example of core available only one instance Opencart module are: Account module, Category module, Information module, etc
  10. Multi-instance module created multiple instances. Each is taken as a different module instance and can be used different in layouts. Example of core available multi-instance Opencart modules is: Banner module, Bestsellers module, Carousel module, Featured module, latest module, a slideshow module, special module, etc.
  11. Once we save the data to the database and then add them to the layout and they are ready to show in the frontend.

In this blog post, we show you how to create a single instance Opencart module admin section and in the upcoming post, we will show you the frontend or catalog code of login module. Let’s create a module that shows the login form in the layout of the frontend.

Following are the files that we need to create:

  • admin/language/en-gb/extension/module/login.php
  • admin/controller/extension/module/login.php
  • admin/view/template/extension/module/login.twig
  • catalog/controller/extension/module/login.php
  • catalog/language/en-gb/extension/module/login.php
  • catalog/view/theme/default/template/extension/module/login.twig

First, let’s work on Language file admin/language/en-gb/extension/module/login.php, let’s define some variables which are useful for module: https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/admin/language/en-gb/extension/module/login.php

<?php
// Heading
$_['heading_title']    = 'Login';
// Text
$_['text_extension']   = 'Extensions';
$_['text_success']     = 'Success: You have modified Login module!';
$_['text_edit']        = 'Edit Login Module';
// Entry
$_['entry_status']     = 'Status';
// Error
$_['error_permission'] = 'Warning: You do not have permission to modify Login module!';

These are normal variables we defined, you can define as much as you can as per your need.

Second, open admin/controller/extension/module/login.php, you can see the code at this GitHub link: https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/admin/controller/extension/module/login.php We are defining the code below:

<?php
//As our file is login.php so Class name is ControllerExtensionModuleLogin which extends the Controller base class
class ControllerExtensionModuleLogin extends Controller {

//Declaration of the private property ‘error’ so that we can get check if any error occurs in the whole class.
private $error = array();

//Create an index method. The index method is called automatically if no parameters are passed, check this video tutorial for details https://www.youtube.com/watch?v=X6bsMmReT-4
public function index() {

//Loads the language file by which the variables of language file are accessible in twig files
$this->load->language('extension/module/login');

//Set the Document title
$this->document->setTitle($this->language->get('heading_title'));

//Loads the model admin/model/setting/setting.php so that we can use the methods defined there.
$this->load->model('setting/setting');

//This is how we check if it is form submit. When we submit the form then this block of code also runs. Then it also validates the modify permission and other validation.
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {

//Look at this line of code, this is the code section which distinguish from Single Instance to Multi Instance. If it is multi instance then it will look like below instead of editSetting it will be addModule and editModule and setting module model is called above.
//
if (!isset($this->request->get[‘module_id’])) {
//
$this->model_setting_module->addModule(‘bestseller’, $this->request->post);
//
} else {
//
$this->model_setting_module->editModule($this->request->get[‘module_id’], $this->request->post);
//
}
//This editSetting save the data to oc_setting database table, see module_ is important else it will not be saved. If you are //creating shipping extension then it should be shipping_, for payment extension it should be payment_

$this->model_setting_setting->editSetting('module_login', $this->request->post);
//This set the success message in the session.
$this->session->data['success'] = $this->language->get('text_success');
//This is to redirect to the extensions page.
$this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
}

//This is to check if there are any warnings
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}

//Following are for breadcrumbs
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_home'),
'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('text_extension'),
'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
);
$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/module/login', 'user_token=' . $this->session->data['user_token'], true)
);

//Form action URL
$data['action'] = $this->url->link('extension/module/login', 'user_token=' . $this->session->data['user_token'], true);

//Form cancel URL
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);

//This is to check what we fill out in the form, whether the status is Enabled or Disabled. If it is the loading time then it gets the //config value which we store in the oc_setting database table.
if (isset($this->request->post['module_login_status'])) {
$data['module_login_status'] = $this->request->post['module_login_status'];
} else {
$data['module_login_status'] = $this->config->get('module_login_status');
}

//This is how we load the header, column left and footer
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');

//This is to set output data variables to the view or twig files and twig file is loaded and HTML rendering is done with it.
$this->response->setOutput($this->load->view('extension/module/login', $data));
}

//This is how validation is done, we check whether the user has permission to modify or not.
//If you want to validate the form data then you can check it here as well.
//If there is error then $this->error[‘warning’] is set and warning are shown.
protected function validate() {
if (!$this->user->hasPermission('modify', 'extension/module/login')) {
$this->error['warning'] = $this->language->get('error_permission');
}
return !$this->error;
}

//Closing of the Class
}

Like this way we write the code in the controller, so distinguishing difference for single instance and multi-instance is how it saves data in the database, for single instance it uses model setting setting and editSetting method and save data at oc_setting database table.

Third, open admin/view/template/extension/module/login.twig and have a look at the following code, here we create the form and other layouts. https://github.com/rupaknepali/Opencart-free-modules/blob/master/login-module/upload/admin/view/template/extension/module/login.twig

{{ header }}{{ column_left }}
<div id="content">
    <div class="page-header">
        <div class="container-fluid">
            <div class="pull-right">
                <button type="submit" form="form-module" 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-module" 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="module_login_status" id="input-status" class="form-control">
                                {% if module_login_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>
                </form>
            </div>
        </div>
    </div>
</div>
{{ footer }}

All others are similar, check <select name="module_login_status" the name you are using for the field should start with module_ for the single instance module.

Like this way, you can create the form in the admin section, validate the form data, validate the permission, and save the data to the database in the setting database table. Hope you liked this article, please subscribe to our YouTube Channel for Opencart video tutorials. You can also find us on Twitter and Facebook.

Duplicate Products are added in Opencart – submitting the same form twice

We are seeing issues in the Chrome browser for Opencart that it is adding duplicate products while adding the products, it looks like it is submitting the same form twice. We just commented on the form submission for the IE browser and it is not submitting duplicate products.

Duplicate products issues on Opencart

Go to admin/view/javascript/common.js and find the following lines of code:

$(document).ready(function() {
	//Form Submit for IE Browser
	$('button[type=\'submit\']').on('click', function() {
		$("form[id*='form-']").submit();
	});

Then comment out like below:

$(document).ready(function() {
	//Form Submit for IE Browser
	// $('button[type=\'submit\']').on('click', function() {
	// 	$("form[id*='form-']").submit();
	// });

After commenting on those lines of code it is not submitting the product twice, so there are no duplicate products inserted. We tested with test 3 products as in the above image.

Please don’t forget to post your questions or comments so that we can add extra topics. You can follow us at our twitter account @rupaknpl and subscribe to our YouTube channel for opencart tutorials.

How to setup Cloudflare easily for eCommerce websites like Opencart?

In this tutorial, we set up Cloudflare CDN for eCommerce websites like Opencart, three steps are: first create a Cloudflare account, add a website domain in the Cloudflare dashboard, and change the DNS records of your domain. We show you how to fix the SSL issues that we face in Cloudflare, how to connect FTP after Cloudflare is set up, and finally how to log the real visitors’ IP than the Cloudflare IP.

Let’s take an example of https://dpsignadvertising.com where we install Opencart, register this domain at onlydomains.com, and delegate to name servers:

Delegate to your name servers

It is similar in your DNS settings also, mostly we point to servers ns1 and ns2.

Let’s create a Cloudflare account

Cloudflare email to setup

Now time to add a domain to Cloudflare

If you were following the steps while registering an account then it will directly take you to add the site.

  • Log in to your Cloudflare account.
  • Click on Add Site from the top navigation bar.
    Add new site in cloudflare
  • Enter your website URL and then click “Add Site”.
  • Then you need to select the plan as per your need, we are using a free plan, select the free plan and click “Confirm plan”
    Cloudflare free plan
  • Now Cloudflare attempts to automatically identify your DNS records and shows lists of DNS results, for dpsignadvertising.com it is showing as in the image:
    DNS record cloudflare
    Be careful and check for missing DNS records, mostly MX records if you have setup. Then, click Continue.
  • Now you will see like NS records of Cloudflare:
  • Now login to the domain registrar, in our case is onlydomains.com and we change the NS1 and NS2, now it looks like in the image below:
    NS changed as cloudflare
  • Once you click “Delegate to your Name Servers”, sometimes it takes up to 24hrs to 48hrs, in our case, it was done within 10mins and we receive an email like below:
    Setup complete cloudflare Opencart
  • Once you got an email your Cloudflare is active and you can see the active status in the Cloudflare admin. Similarly, you can see the analytics that it is serving.
    Cloudflare analytics in dashboard

How to solve the SSL issue of Cloudflare?

Go to Cloudflare Dashboard >> Click SSL/TLS >> Edge Certificates >> Then toggle to ON for “Always Use HTTPS”

SSL issue fix in Cloudflare

Then the SSL issues are resolved.

Cloudflare FTP issues?

The solution to FTP Cloudflare issues: With Cloudflare set up, you will not be able to connect to your FTP with the domain name so you need to use the IP of your server. You can find the Cloudflare Dashboard >> DNS >> Then find the FTP IP or your website URL IP.

Solution to FTP cloudflare issues

Get the actual visitors’ IP address than the Cloudflare IP address in Opencart

If you are using Google Analytics then you may see the same Cloudflare IP address that the visitor’s IP address as all IPs are proxied by Cloudflare. Sometimes, the Payment gateway may block all the payments as well as it will see the same IP ordering the products which may create suspicious activities, so to fix that you need to add the following code at top of index.php

if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
  $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}

Cloudflare can be used on any website so it does not stick to Opencart, with the above you can enable Cloudflare on any website. It is the same case for the Opencart, we also installed Cloudflare in our webocreation.com blog and see a good improvement in site speed. You can hire us directly or can hire the top 3% of Freelance Cloudflare Experts.

We hope this helps someone to set up Cloudflare properly and easily in Opencart. Please don’t forget to post your questions or comments so that we can add extra topics. You can follow us at our Twitter account @rupaknpl and subscribe to our YouTube channel for opencart tutorials.

Full webpage screenshot chrome with two commands – easiest way

The easiest and fastest way to take the full webpage screenshot in chrome without the extension and just two commands is below:

  • Click “Option + Command + J”
  • Then click “Command + Shift + P”
  • Type “Capture full-size screenshot” and click it, your full screenshot will be captured and downloaded

Above is the Mac command, in Windows, it is similar just use Ctrl instead of Command and Alt instead of Option.

When you click the “Option + Command + J” it opens developer console:

open chrome console developer

Then click “Command + Shift + P” to open the input box and start to type full size to run command and you will see like below:

Capture full size screenshot

Click it and your fullscreen webpage is captured and downloaded.

We hope this help someone to take the full page screen capture in chrome easily. Please don’t forget to post your questions or comments so that we can add extra topics. You can follow us at our twitter account @rupaknpl and subscribe to our YouTube channel for opencart tutorials.

Show top popular posts in categories wise by widgets in WordPress

This WordPress plugin “Category Popular Posts” is to show category wise top popular posts in a widget. The widget has options to add a description, limit the number of posts, an option to show date or not, an option to show the author or not, and an option to show the featured image or not.

Installation:

  • Download from the above link and got to WordPress admin >> Plugins >> Add Plugin and upload the downloaded zip file “category-popular-posts.zip“.
  • Then go to Appearance >> Widgets >> Add the “Category wise Popular posts” widgets to the widgets places like in the sidebar.

Settings:

Enter title, if needed description, number of posts to show here in our example it is 6, choose an option Yes or no to show date, author and featured image, click save and done.

Category wise popular posts wordpress

Frontend view:

It displays popular posts of the category in the categories pages, and on the posts page, it displays the popular posts of the active post’s category posts.

frontend view category wise popular posts

CSS change:

You can make the CSS changes for the following CSS classes to match your theme design:

  1. .post-image: for the image
  2. .post-content: for the content
  3. For others just target as per your need.

The code is written as below:

<?php
/**
 * Plugin Name: Category Popular Posts
 * Description: Categories wise Popular Posts.
 * Plugin URI: https://webocreation.com
 * Author: Rupak Nepali
 * Author URI: https://webocreation.com
 * Version: 1.0
 * License: GPLv2 or later
 *
 */

// Adds widget: Categories Wise Popular Posts
class Categorywisepopularp_Widget extends WP_Widget
{
    public function __construct()
    {
        parent::__construct(
            'categorywisepopularp_widget',
            esc_html__('Category wise Popular posts', 'textdomain')
        );
    }
    private $widget_fields = array(
        array(
            'label' => 'Description',
            'id' => 'description_textarea',
            'type' => 'textarea',
        ),
        array(
            'label' => 'Limit',
            'id' => 'limit_number',
            'type' => 'number',
        ),
        array(
            'label' => 'Show date',
            'id' => 'showdate_select',
            'type' => 'select',
            'options' => array(
                'Yes',
                'No',
            ),
        ),
        array(
            'label' => 'Show Author',
            'id' => 'showauthor_select',
            'type' => 'select',
            'options' => array(
                'Yes',
                'No',
            ),
        ),
        array(
            'label' => 'Show featured image',
            'id' => 'showfeaturedima_select',
            'type' => 'select',
            'options' => array(
                'Yes',
                'No',
            ),
        ),
    );
    public function widget($args, $instance)
    {
        if (!isset($args['widget_id'])) {
            $args['widget_id'] = $this->id;
        }
        $title = (!empty($instance['title'])) ? $instance['title'] : __('Recent Posts');
        /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
        $title = apply_filters('widget_title', $title, $instance, $this->id_base);
        $number = (!empty($instance['limit_number'])) ? absint($instance['limit_number']) : 5;
        if (!$number) {
            $number = 5;
        }
        $show_date = isset($instance['showdate_select']) ? $instance['showdate_select'] : false;
        $r = new WP_Query(
            apply_filters(
                'widget_posts_args',
                array(
                    'meta_key' => 'post_views_count',
                    'cat' => get_query_var('cat'),
                    'orderby' => 'meta_value_num',
                    'posts_per_page' => $number,
                    'no_found_rows' => true,
                    'post_status' => 'publish',
                    'ignore_sticky_posts' => true,
                ),
                $instance
            )
        );
        if (!$r->have_posts()) {
            return;
        }
        echo $args['before_widget'];
        if ($title) {
            echo $args['before_title'] . $title . $args['after_title'];
        }
        echo '<p>' . $instance['description_textarea'] . '</p>';
        echo "<style>.post-image{ float:left; width:50%;}.post-content{float:right; width:50%;}</style>";
        echo '<ul>';
        foreach ($r->posts as $recent_post):
            $post_title = get_the_title($recent_post->ID);
            $p_title = (!empty($post_title)) ? $post_title : __('(no title)');
            echo "<li>";
            if ($instance['showfeaturedima_select'] == "Yes") {
                echo "<div class='post-image'><a href='" . esc_url(get_permalink($recent_post->ID)) . "' rel='bookmark'  title='" . $recent_post->post_title . "'><img class='category-popular-posts-image' src='" . get_the_post_thumbnail_url($recent_post->ID, 'thumbnail', 'full') . "' alt='" . $recent_post->post_title . "'></a></div>";
            }
            echo "<div class='post-content'><a href='" . esc_url(get_permalink($recent_post->ID)) . "' rel='bookmark' title='" . $recent_post->post_title . "'>" . $recent_post->post_title . "</a>";
            if ($instance['showauthor_select'] == "Yes") {
                echo "<br> - <a href='" . esc_url(get_author_posts_url($recent_post->post_author)) . "'>" .
                get_the_author_meta('display_name', $recent_post->post_author) . "</a>";
            }
            if ($instance['showdate_select'] == "Yes") {
                echo "<br><span class='post-date'>" . get_the_date('', $recent_post->ID) . "</span>";
            }
            echo "</div>";
            echo "<div style='clear:both;'></div>";
            echo "</li>";
        endforeach;
        echo "</ul>";
        echo $args['after_widget'];
    }
    public function field_generator($instance)
    {
        $output = '';
        foreach ($this->widget_fields as $widget_field) {
            $default = '';
            if (isset($widget_field['default'])) {
                $default = $widget_field['default'];
            }
            $widget_value = !empty($instance[$widget_field['id']]) ? $instance[$widget_field['id']] : esc_html__($default, 'textdomain');
            switch ($widget_field['type']) {
                case 'textarea':
                    $output .= '<p>';
                    $output .= '<label for="' . esc_attr($this->get_field_id($widget_field['id'])) . '">' . esc_attr($widget_field['label'], 'textdomain') . ':</label> ';
                    $output .= '<textarea class="widefat" id="' . esc_attr($this->get_field_id($widget_field['id'])) . '" name="' . esc_attr($this->get_field_name($widget_field['id'])) . '" rows="6" cols="6" value="' . esc_attr($widget_value) . '">' . $widget_value . '</textarea>';
                    $output .= '</p>';
                    break;
                case 'select':
                    $output .= '<p>';
                    $output .= '<label for="' . esc_attr($this->get_field_id($widget_field['id'])) . '">' . esc_attr($widget_field['label'], 'textdomain') . ':</label> ';
                    $output .= '<select id="' . esc_attr($this->get_field_id($widget_field['id'])) . '" name="' . esc_attr($this->get_field_name($widget_field['id'])) . '">';
                    foreach ($widget_field['options'] as $option) {
                        if ($widget_value == $option) {
                            $output .= '<option value="' . $option . '" selected>' . $option . '</option>';
                        } else {
                            $output .= '<option value="' . $option . '">' . $option . '</option>';
                        }
                    }
                    $output .= '</select>';
                    $output .= '</p>';
                    break;
                default:
                    $output .= '<p>';
                    $output .= '<label for="' . esc_attr($this->get_field_id($widget_field['id'])) . '">' . esc_attr($widget_field['label'], 'textdomain') . ':</label> ';
                    $output .= '<input class="widefat" id="' . esc_attr($this->get_field_id($widget_field['id'])) . '" name="' . esc_attr($this->get_field_name($widget_field['id'])) . '" type="' . $widget_field['type'] . '" value="' . esc_attr($widget_value) . '">';
                    $output .= '</p>';
            }
        }
        echo $output;
    }
    public function form($instance)
    {
        $title = !empty($instance['title']) ? $instance['title'] : esc_html__('', 'textdomain');
        ?>
<p>
    <label
        for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_attr_e('Title:', 'textdomain');?></label>
    <input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
        name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
        value="<?php echo esc_attr($title); ?>">
</p>
<?php
$this->field_generator($instance);
    }
    public function update($new_instance, $old_instance)
    {
        $instance = array();
        $instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
        foreach ($this->widget_fields as $widget_field) {
            switch ($widget_field['type']) {
                default:
                    $instance[$widget_field['id']] = (!empty($new_instance[$widget_field['id']])) ? strip_tags($new_instance[$widget_field['id']]) : '';
            }
        }
        return $instance;
    }
}
function register_categorywisepopularp_widget()
{
    register_widget('Categorywisepopularp_Widget');
}
add_action('widgets_init', 'register_categorywisepopularp_widget');

Please let us know if you have any suggestions or requirements, you can also find us on Twitter and Facebook. Enjoy!

10 ways to speed up the Opencart 3 – website speed optimization