DEVELOP A CUSTOM ONLINE PAYMENT METHOD IN MAGENTO 2

Payment Method plays an essential role in running online businesses. The payment Method acts as the heart of the online portal. If there is no Payment Method, then the customer will not feel comfortable, and it will affect the whole online business. Magento 2 has several built-in payment gateways such as Paypal, Braintree, etc. Sometimes, it is required to integrate a custom payment gateway. Following are the steps for creating a custom payment method:

  1. Creating and registering a new Custom Payment Method Module
  2. Defining Custom Payment Method Details
  3. Adding a new section of Custom Payment Method in Admin Panel
  4. Creating Custom Payment Model
  5. Accessing Payment Method on the Checkout Page on the Front-End
  6. Registering Payment Method on the Checkout Layout
  7. Running Magento Commands

Create And Register A New Custom Payment Method Module

To create a custom module, the very first step is to create a file, module.xml in folder, <Magento Root Folder>/app/code/<Vendor Name>/<Module Name>/etc/ folder. Let’s take the path –

/app/code/Bizspice/Payment/etc/module.xml

and paste the following code into this file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Bizspice_Payment" setup_version="1.0.1"></module>
</config> 

Now, create a file registration.php in /app/code/Bizspice/Payment/ folder with the following code:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Bizspice_Payment',
     __DIR__
 );

Define Custom Payment Method Details

Create a file config.xml in /app/code/Bizspice/Payment/etc/ folder with the following code:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
  <default>
      <payment>
          <bizspicepayment>
              <active>1</active>
              <model>Bizspice\Payment\Model\Payment</model>
              <payment_action>authorize_capture</payment_action> 
              <title>BIZSPICE Payment Gateway</title>
              <api_key backend_model="Magento\Config\Model\Config\Backend\Encrypted" />
              <cctypes>AE,VI,MC,DI,JCB</cctypes>
              <allowspecific>0</allowspecific>
              <min_order_total>1</min_order_total>
         </bizspicepayment>
     </payment>
  </default>
</config>

Here, in the config.xml file, the parent tag is the default, and the child tag is the payment which has the details of our custom module. bizspicepayment tag is added under the payment tag. bizspicepayment is a code of our custom payment method. This will be used in the model file (defined under the model tag in config.xml)

Add A New Section Of Custom Payment Method In Admin Panel

There will be some configuration options for our custom payment module in the admin panel. To manage these options, create a file system.xml in /app/code/Bizspice/Payment/etc/adminhtml/ folder with the following code:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Config/etc/system_file.xsd">
<system>
   <section id="payment">
        <group id="bizspicepayment" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>New Payment Method</label>
            <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Enabled</label>
                <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
            </field>
            <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
              <label>Title</label>
            </field>
            <field id="api_key" translate="label" type="obscure" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Api Key</label>
               <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
            </field> <field id="debug" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Debug</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
            </field> <field id="cctypes" translate="label" type="multiselect" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Credit Card Types</label>
               <source_model>Magento\Payment\Model\Source\Cctype</source_model>
            </field>
            <field id="payment_action" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Payment Action</label>
               <source_model>Bizspice\Payment\Model\Config\Source\Order\Action\Paymentaction</source_model>
            </field>
            <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Sort Order</label>
            </field>
            <field id="allowspecific" translate="label" type="allowspecific" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0">
               <label>Payment from Applicable Countries</label>
                <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model>
            </field> 
            <field id="specificcountry" translate="label" type="multiselect" sortOrder="51" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Payment from Specific Countries</label>
                <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
            </field>
            <field id="min_order_total" translate="label" type="text" sortOrder="98" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Minimum Order Total</label>
            </field>
            <field id="max_order_total" translate="label" type="text" sortOrder="99" showInDefault="1" showInWebsite="1" showInStore="0">
                <label>Maximum Order Total</label>
                <comment>Leave empty to disable limit</comment>
            </field>
      </group>
    </section> 
  </system> 
</config> 

Here, in system.xml –

  • There is a field API Key. I am assuming that you are already having the API key of the online Payment Gateway which you want to integrate.
  • Payment Action: There will be 2 action types –
  • Authorize
  • Authorize and Capture
  • You can also specify Minimum Order Total and Maximum Order Total to specify the range of Order Amount for which this Payment Method will be available.

To populate Payment Actions in the Admin Configuration page, create Paymentaction.php in Bizspice\Payment\Model\Config\Source\Order\Action folder with the following code:

<?php
namespace Bizspice\Payment\Model\Config\Source\Order\Action;
 /** * Order Status source model */
class Paymentaction 
{
    /**
     * @var string[] 
     */      public function toOptionArray(){
       return [ ['value' => 'authorize', 'label' => __('Authorize Only')], ['value' => 'authorize_capture', 'label' => __('Authorize and Capture')], ];
     }
}

Access Payment Method In Checkout Page On The Front-End

Create a new file di.xml in Bizspice/Payment/etc/frontend/ folder with the following code:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <type name="Magento\Payment\Model\CcGenericConfigProvider">
       <arguments>
          <argument name="methodCodes" xsi:type="array">
                 <item name="bizspicepayment" xsi:type="const">
           Bizspice\Payment\Model\Payment::CODE</item>
          </argument> 
       </arguments>
    </type> 
</config>

After creating di.xml, create following 2 JS files for displaying Payment Method in Checkout Page in front-end:

payment.js in Bizspice/Payment/view/frontend/web/js/view/payment/ folder with the following code:

define([
         'uiComponent',
         'Magento_Checkout/js/model/payment/renderer-list'
       ], function (Component, rendererList) { 
              'use strict';
               rendererList.push({ type: 'bizspicepayment', component: 'Bizspice_Payment/js/view/payment/method-renderer/payment' });
                 return Component.extend({});
           });

payment.js in Bizspice/Payment/view/frontend/web/js/view/payment/method-renderer/ folder with the following code:

define([
  'Magento_Payment/js/view/payment/cc-form',
  'jquery',
  'Magento_Payment/js/model/credit-card-validation/validator'
 ], function (Component, $) {
     'use strict';
        return Component.extend({
             defaults: {
                template: 'Bizspice_Payment/payment/payment'
             },
            getCode: function() {  	           return 'bizspicepayment';
	           },
 	       isActive: function() {
 	          return true;
 	       },
 	       validate: function() { 		          var $form = $('#' + this.getCode() + '-form');
 		     return $form.validation() && $form.validation('isValid');
             }
       });
});

JS renderer uses a template to render payment’s details. Create payment.html in

Bizspice/Payment/view/frontend/web/template/payment/ folder with the following code:

<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
   <div class="payment-method-title field choice"> 
      <input type="radio" name="payment[method]" class="radio" data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()" />
     <label data-bind="attr: {'for': getCode()}" class="label">
        <span data-bind="text: getTitle()"></span>
     </label>
  </div>
  <div class="payment-method-content">
  <!-- ko foreach: getRegion('messages') -->
  <!-- ko template: getTemplate() -->
  <!-- /ko -->
  <!--/ko--> 
<div class="payment-method-billing-address"> 
<!-- ko foreach: $parent.getRegion(getBillingAddressFormName()) -->
 <!-- ko template: getTemplate() -->
 <!-- /ko --> 
<!--/ko-->
</div>
 <!-- Render the native credit card form. -->
 <form class="form" data-bind="attr: {'id': getCode() + '-form'}">
 <!-- ko template: 'Magento_Payment/payment/cc-form' --> 
 <!-- /ko --> 
 </form>
 <div class="checkout-agreements-block"> 
 <!-- ko foreach: $parent.getRegion('before-place-order') -->
 <!-- ko template: getTemplate() -->
 <!-- /ko --> 
 <!--/ko-->
 </div>
 <div class="actions-toolbar">
 <div class="primary">
 <button data-role="review-save" type="submit" data-bind=" attr: {title: $t('Place Order')}, enable: (getCode() == isChecked()), click: placeOrder, css: {disabled: !isPlaceOrderActionAllowed()} " class="action primary checkout" disabled> 
<span data-bind="i18n: 'Place Order'">
</span> 
</button>
 </div>
 </div> 
</div>
 </div> 

Create Custom Payment Model

As you can see under the model tag in the config.xml file, the following path has been specified:

Bizspice/Payment\Model\PaymentSo, now, a model file Payment.php will be created in Bizspice/Payment/Model/ folder with the following code:

<?php
namespace Bizspice\Payment\Model;
class Payment extends \Magento\Payment\Model\Method\Cc 
{
    const CODE = 'bizspicepayment';
    protected $_code = self::CODE;
    protected $_canAuthorize = true;
    protected $_canCapture = true;
    protected $_isGateway = true;
    protected $_countryFactory;
    protected $cart = null;
    public function __construct( \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry, 
    \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
    \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,
    \Magento\Payment\Helper\Data $paymentData, 
    \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, 
    \Magento\Payment\Model\Method\Logger $logger, 
    \Magento\Framework\Module\ModuleListInterface $moduleList,
    \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
    \Magento\Directory\Model\CountryFactory $countryFactory,
    \Magento\Checkout\Model\Cart $cart,
    array $data = array() 
   ) {
      parent::__construct( $context, $registry, $extensionFactory, $customAttributeFactory, $paymentData, $scopeConfig, $logger, $moduleList, $localeDate, null, null, $data );
        $this->cart = $cart; $this->_countryFactory = $countryFactory;
   }
   public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) {
      try { 
       //check if payment has not been authorized then authorize it      if(is_null($payment->getParentTransactionId()))
	     { 
	        $this->authorize($payment, $amount);
	     }
	        //build array of all necessary details to pass to your Payment Gateway.. 
	     $request = [ 'CardCVV2' => $payment->getCcCid(), ‘CardNumber’ => $payment->getCcNumber(), ‘CardExpiryDate’ => $this->getCardExpiryDate($payment), ‘Amount’ => $amount, ‘Currency’ => $this->cart->getQuote()->getBaseCurrencyCode(), ];
	       //make API request to credit card processor. $response = $this->captureRequest($request); 
	       //Handle Response accordingly. //transaction is completed.
	       $payment->setTransactionId($response['tid']) ->setIsTransactionClosed(0);
	     } catch (\Exception $e) {
	       $this->debug($payment->getData(), $e->getMessage());
	     }   return $this;
		  } 
	public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount) { 
	  try { 
	  //build array of all necessary details to pass to your Payment Gateway..
	 $request = [ 'CardCVV2' => $payment->getCcCid(), ‘CardNumber’ => $payment->getCcNumber(), ‘CardExpiryDate’ => $this->getCardExpiryDate($payment), ‘Amount’ => $amount, ‘Currency’ => $this->cart->getQuote()->getBaseCurrencyCode(), ];
	 //check if payment has been authorized
	   $response = $this->authRequest($request);
	 } catch (\Exception $e) {
	  $this->debug($payment->getData(), $e->getMessage());
	 }
	 if(isset($response['tid']))
	 { // Successful auth request.
	  // Set the transaction id on the payment so the capture request knows auth has happened.
	      $payment->setTransactionId($response['tid']);
	      $payment->setParentTransactionId($response['tid']);
	 } 
	   //processing is not done yet.
	      $payment->setIsTransactionClosed(0);
	        return $this;
	}
	    /*This function is defined to set the Payment Action Type that is - - Authorize - Authorize and Capture Whatever has been set under Configuration of this Payment Method in Admin Panel, that will be fetched and set for this Payment Method by passing that into getConfigPaymentAction() function. */
	public function getConfigPaymentAction() {
	   return $this->getConfigData('payment_action');
	}
	public function authRequest($request) {
	   //Process Request and receive the response from Payment Gateway---
	  $response = ['tid' => rand(100000, 99999999)];
	   //Here, check response and process accordingly---
	   if(!$response)
	   {
	     throw new \Magento\Framework\Exception\LocalizedException(__('Failed authorize request.'));
	  }    return $response;
	 } 
	   /**
	    * Test method to handle an API call for capture request. 
	    *
	    * @param $request 
	    * @return array 
	    * @throws \Magento\Framework\Exception\LocalizedException
	    */
	    public function captureRequest($request) {
	       //Process Request and receive the response from Payment Gateway---                    $response = ['tid' => rand(100000, 99999999)];
	        //Here, check response and process accordingly---
	       if(!$response)
	       {
	         throw new \Magento\Framework\Exception\LocalizedException(__('Failed capture request.'));
	       }
	         return $response;
	    }
	  }
 

Register Payment Method On Checkout Layout

Create a file checkout_index_index.xml in Bizspice/Payment/view/frontend/layout/ folder with the following code:

<?xml version="1.0"?>
  <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/View/Layout/etc/page_configuration.xsd">
     <body>
         <referenceBlock name="checkout.root">
              <arguments> 
                   <argument name="jsLayout" xsi:type="array">
                        <item name="components" xsi:type="array">
                           <item name="checkout" xsi:type="array">
                               <item name="children" xsi:type="array">
                                    <item name="steps" xsi:type="array">
                                         <item name="children" xsi:type="array">
                                              <item name="billing-step" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                       <item name="payment" xsi:type="array">
                                                             <item name="children" xsi:type="array">
                                                                  <item name="renders" xsi:type="array">
                                                                      <!-- merge payment method renders here --> 
                                                                            <item name="children" xsi:type="array">
                                                                                <item name="bizspicepayment-payments" xsi:type="array">
                                                                                       <item name="component" xsi:type="string">Bizspice_Payment/js/view/payment/payment</item>
                                                                                               <item name="methods" xsi:type="array">
                                                                                                   <item name="bizspicepayment" xsi:type="array">
                                                                                                           <item name="isBillingAddressRequired" xsi:type="boolean">true</item>
                                                                                                    </item>
                                                                                               </item>
                                                                                         </item> 
                                                                                    </item>
                                                                                 </item>
                                                                             </item>
                                                                       </item>
                                                                  </item> 
                                                             </item>
                                                  </item>
                                          </item> 
                                    </item>
                          </item>
                     </item>
               </argument>
            </arguments>
        </referenceBlock>
     </body>
</page> 

Run Magento Commands

Custom Payment Method has been created and now the last step is to install this module and clear the cache (if enabled in Magento) for using this module. Run the following commands:

rm -rf pub/static/* var/* generated/*
chmod 0777 var/ pub/ generated/
chmod 0777 -R var/ generated/ pub/
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f<
php bin/magento indexer:reindex
php bin/magento cache:clean
php bin/magento cache:flush

Now, Custom Payment Method is ready to configure and use. To enable and configure it, go to –

Magento 2 Admin panel > STORES > Configuration > SALES > Payment Methods. Here, you will see that our Custom Payment Method is listed there –

Now, go to the front end and add a product to the cart. Please check out, enter your Billing Address, choose a Shipping Method, and click the Submit button. You will be redirected to the Payment Page and see the BIZSPICE Payment Method as follows: