Magento 2 – Custom Payment Method

Custom Payment Methods

Sometimes, clients can request payment methods that need more than the standard out-of-the-box Magento payment method solutions. Building a custom payment method allows you to customize all the fields. Moreover, you can customize the checkout logic and the API logic used for implementing the payment method.

Additional payment methods can bring the diversity of customer choices when they buy on your site. On the other hand, multiple payment methods are a great strategy to reach out to the global marketplace.

Today, we will create our own Payment Gateway integration in Magento 2 stores. After launching the new payment method, you will be able to find and configure it in the following path: Admin Panel > Stores > Settings > Configuration > Sales > Payment Methods.

Payment Method Configuration

First, you’ll need to add a couple of configuration settings to your new module. For a custom payment method, you will surely have many additional settings. However, we will add only a few very basic settings.

Here is what we need:

  • an “enabled” setting
  • a title to display for it
  • a setting for accepted credit card types

All of these will be added to the etc/adminhtml/system.xml file.
 
To start with, add configuration settings for your payment method – registration.php and module.xml files. 

file: app/code/Vendor/Module/registration.php

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

file: app/code/Vendor/Module/etc/module.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="SyncIt_Payment" setup_version="0.0.1">
       <sequence>
           <module name="Magento_Sales"/>
           <module name="Magento_Payment"/>
           <module name="Magento_Checkout"/>
       </sequence>
   </module>
</config>

Also, in the system.xml file, you can allow the merchant to configure:

  • a title to display for your payment method
  • a setting for which credit card types you will accept
  • password and other necessary stuff from the admin panel

file: app/code/Vendor/Module/etc/adminhtml/system.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="payment">
            <group id="method" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1">
                <label> Payment Gateway</label>
                <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Title</label>
                </field>
                <field id="merchant_shop_id" translate="label"  sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Merchant Shop ID</label>
                </field>
                <field id="merchant_gateway_store_key" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Merchant Gateway Store Key</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
                <field id="shop_username" translate="label"  sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Merchant API Username</label>
                </field>

                <field id="shop_password" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Merchant API Password</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
                <field id="test_mode" translate="label" type="select" sortOrder="85" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Test mode</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="payment_action" translate="label" type="select" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Payment Action</label>
                    <source_model>SyncIt\Payment\Model\Adminhtml\Source\PaymentAction</source_model>
                </field>
                <field id="allowspecific" translate="label" type="allowspecific" sortOrder="130" showInDefault="9"
                       showInWebsite="1" showInStore="1">
                    <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="140" showInDefault="1"
                       showInWebsite="1" showInStore="1">
                    <label>Payment From Specific Countries</label>
                    <source_model>Magento\Directory\Model\Config\Source\Country</source_model>
                </field>
                <field id="sort_order" translate="label" type="text" sortOrder="160" showInDefault="1" showInWebsite="1"
                       showInStore="1">
                    <label>Sort Order</label>
                    <frontend_class>validate-number</frontend_class>
                </field>
                <field id="min_order_total" translate="label" type="text" sortOrder="260" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Minimum Order Total</label>
                </field>
                <field id="max_order_total" translate="label" type="text" sortOrder="270" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Maximum Order Total</label>
                </field>
                <field id="failed_payment_email" translate="label comment" type="select" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Failed Payment Email</label>
                    <comment>Email sent when there is ann error while processing payment.</comment>
                    <source_model>Magento\Config\Model\Config\Source\Email\Template</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

In addition, you should define additional configuration in the di.xml file. The di.xml configuration for CommandPool, GatewayCommand, and other classes might look something like this.

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- Payment Method Facade configuration -->
    <virtualType name="SamplePaymentGatewayFacade" type="Magento\Payment\Model\Method\Adapter">
        <arguments>
            <argument name="code" xsi:type="const">\SyncIt\Payment\Model\Ui\ConfigProvider::CODE</argument>
            <argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument>
            <argument name="infoBlockType" xsi:type="string">SyncIt\Payment\Block\Info</argument>
            <argument name="valueHandlerPool" xsi:type="object">SamplePaymentGatewayValueHandlerPool</argument>
            <argument name="commandPool" xsi:type="object">SamplePaymentGatewayCommandPool</argument>
        </arguments>
    </virtualType>

    <!-- Configuration reader -->
    <virtualType name="SamplePaymentGatewayConfig" type="Magento\Payment\Gateway\Config\Config">
        <arguments>
            <argument name="methodCode" xsi:type="const">\SyncIt\Payment\Model\Ui\ConfigProvider::CODE</argument>
        </arguments>
    </virtualType>

    <!-- Logger, initialized with SamplePaymentGatewayConfig -->
    <virtualType name="SamplePaymentGatewayLogger" type="Magento\Payment\Model\Method\Logger">
        <arguments>
            <argument name="config" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </virtualType>

    <type name="SyncIt\Payment\Gateway\Http\Client\ClientMock">
        <arguments>
            <argument name="logger" xsi:type="object">SamplePaymentGatewayLogger</argument>
        </arguments>
    </type>

    <!-- Commands infrastructure -->
    <virtualType name="SamplePaymentGatewayCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="authorize" xsi:type="string">SamplePaymentGatewayAuthorizeCommand</item>
                <item name="capture" xsi:type="string">SamplePaymentGatewayCaptureCommand</item>
                <item name="void" xsi:type="string">SamplePaymentGatewayVoidCommand</item>
            </argument>
        </arguments>
    </virtualType>

    <!-- Authorize command -->
    <virtualType name="SamplePaymentGatewayAuthorizeCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
        <arguments>
            <argument name="requestBuilder" xsi:type="object">SamplePaymentGatewayAuthorizationRequest</argument>
            <argument name="handler" xsi:type="object">SamplePaymentGatewayResponseHandlerComposite</argument>
            <argument name="transferFactory" xsi:type="object">SyncIt\Payment\Gateway\Http\TransferFactory</argument>
            <argument name="client" xsi:type="object">SyncIt\Payment\Gateway\Http\Client\ClientMock</argument>
        </arguments>
    </virtualType>

    <!-- Authorization Request -->
    <virtualType name="SamplePaymentGatewayAuthorizationRequest" type="Magento\Payment\Gateway\Request\BuilderComposite">
        <arguments>
            <argument name="builders" xsi:type="array">
                <item name="transaction" xsi:type="string">SyncIt\Payment\Gateway\Request\AuthorizationRequest</item>
                <item name="mockData" xsi:type="string">SyncIt\Payment\Gateway\Request\MockDataRequest</item>
            </argument>
        </arguments>
    </virtualType>
    <type name="SyncIt\Payment\Gateway\Request\AuthorizationRequest">
        <arguments>
            <argument name="config" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </type>

    <!-- Capture command -->
    <virtualType name="SamplePaymentGatewayCaptureCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
        <arguments>
            <argument name="requestBuilder" xsi:type="object">SyncIt\Payment\Gateway\Request\CaptureRequest</argument>
            <argument name="handler" xsi:type="object">SyncIt\Payment\Gateway\Response\TxnIdHandler</argument>
            <argument name="transferFactory" xsi:type="object">SyncIt\Payment\Gateway\Http\TransferFactory</argument>
            <argument name="validator" xsi:type="object">SyncIt\Payment\Gateway\Validator\ResponseCodeValidator</argument>
            <argument name="client" xsi:type="object">SyncIt\Payment\Gateway\Http\Client\ClientMock</argument>
        </arguments>
    </virtualType>

    <!-- Capture Request -->
    <type name="SyncIt\Payment\Gateway\Request\CaptureRequest">
        <arguments>
            <argument name="config" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </type>

    <!-- Void command -->
    <virtualType name="SamplePaymentGatewayVoidCommand" type="Magento\Payment\Gateway\Command\GatewayCommand">
        <arguments>
            <argument name="requestBuilder" xsi:type="object">SyncIt\Payment\Gateway\Request\VoidRequest</argument>
            <argument name="handler" xsi:type="object">SyncIt\Payment\Gateway\Response\TxnIdHandler</argument>
            <argument name="transferFactory" xsi:type="object">SyncIt\Payment\Gateway\Http\TransferFactory</argument>
            <argument name="validator" xsi:type="object">SyncIt\Payment\Gateway\Validator\ResponseCodeValidator</argument>
            <argument name="client" xsi:type="object">SyncIt\Payment\Gateway\Http\Client\ClientMock</argument>
        </arguments>
    </virtualType>

    <!-- Void Request -->
    <type name="SyncIt\Payment\Gateway\Request\VoidRequest">
        <arguments>
            <argument name="config" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </type>

    <!-- Response handlers -->
    <virtualType name="SamplePaymentGatewayResponseHandlerComposite" type="Magento\Payment\Gateway\Response\HandlerChain">
        <arguments>
            <argument name="handlers" xsi:type="array">
                <item name="txnid" xsi:type="string">SyncIt\Payment\Gateway\Response\TxnIdHandler</item>
                <item name="fraud" xsi:type="string">SyncIt\Payment\Gateway\Response\FraudHandler</item>
            </argument>
        </arguments>
    </virtualType>

    <!-- Value handlers infrastructure -->
    <virtualType name="SamplePaymentGatewayValueHandlerPool" type="Magento\Payment\Gateway\Config\ValueHandlerPool">
        <arguments>
            <argument name="handlers" xsi:type="array">
                <item name="default" xsi:type="string">SamplePaymentGatewayConfigValueHandler</item>
            </argument>
        </arguments>
    </virtualType>
    <virtualType name="SamplePaymentGatewayConfigValueHandler" type="Magento\Payment\Gateway\Config\ConfigValueHandler">
        <arguments>
            <argument name="configInterface" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </virtualType>

    <type name="SyncIt\Payment\Block\Info">
        <arguments>
            <argument name="config" xsi:type="object">SamplePaymentGatewayConfig</argument>
        </arguments>
    </type>

    <type name="SyncIt\Payment\Logger\Handler">
        <arguments>
            <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
        </arguments>
    </type>
    <type name="SyncIt\Payment\Logger\Logger">
        <arguments>
            <argument name="name" xsi:type="string">paymentIntegration</argument>
            <argument name="handlers"  xsi:type="array">
                <item name="system" xsi:type="object">SyncIt\Payment\Logger\Handler</item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\Sales\Model\Order\Email\Container\OrderIdentity">
        <plugin name="change_is_enable_method" type="SyncIt\Payment\Plugin\Sales\Order\Email\Container\OrderIdentityPlugin"/>
    </type>

    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
        <arguments>
            <argument name="collections" xsi:type="array">
                <item name="Payment_transaction_listing_data_source" xsi:type="string">SyncIt\Payment\Model\ResourceModel\PaymentTransaction\Grid\Collection</item>
            </argument>
        </arguments>
    </type>

    <type name="SyncIt\Payment\Model\ResourceModel\PaymentTransaction\Grid\Collection">
        <arguments>
            <argument name="mainTable" xsi:type="string">syncit_Payment_payment_transaction</argument>
            <argument name="eventPrefix" xsi:type="string">syncit_Payment_transaction_grid_collection</argument>
            <argument name="eventObject" xsi:type="string">syncit_Payment_transaction_grid_collection</argument>
            <argument name="resourceModel" xsi:type="string">\SyncIt\Payment\Model\ResourceModel\PaymentTransaction</argument>
        </arguments>
    </type>
</config>

In di.xml we also define a database model for storing results/responses from Payment Provider, Logger Class, etc.

Payment Adapter ClassCustom payment method class typically extends the Magento\Payment\Model\Method\AbstractMethod class. Well, great news – it is no longer needed!

file: app/code/Vendor/Module/registration.php

<?php
   namespace Magento\Payment\Model\Method;
 
use Magento\Framework\Event\ManagerInterface;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
use Magento\Payment\Gateway\Config\ValueHandlerPoolInterface;
use Magento\Payment\Gateway\Data\PaymentDataObjectFactory;
use Magento\Payment\Gateway\Validator\ValidatorPoolInterface;
use Magento\Payment\Model\MethodInterface;
/* more uses */
 
class Adapter implements MethodInterface
{
   
    public function __construct(
        ManagerInterface $eventManager,
        ValueHandlerPoolInterface $valueHandlerPool,
        ValidatorPoolInterface $validatorPool,
        CommandPoolInterface $commandPool,
        PaymentDataObjectFactory $paymentDataObjectFactory,
        $code,
        $formBlockType,
        $infoBlockType
    ) {
       /* CODE */
    }
 
    /* Your custom CODE */
}

You can find more great information here: https://www.maxpronko.com/magento-2-payment-adapter-configuration/

Displaying the New Payment During Checkout

There are two important JS files to add: 

  1. the payment method renderer
  2. the component that registers the renderer

 file: view/frontend/web/js/view/payment/payment.js

/*global define*/
define(
   [
       'uiComponent',
       'Magento_Checkout/js/model/payment/renderer-list'
   ],
   function (
       Component,
       rendererList
   ) {
       'use strict';
       rendererList.push(
           {
               type: payment,
               component: 'SyncIt_Payment/js/view/payment/method-renderer/payway'
           }
       );
       /** Add view logic here if needed */
       return Component.extend({});
   }
);

file: view/frontend/web/js/view/payment/method-renderer/payment.js
define(
   [
     'jquery',
       'Magento_Checkout/js/view/payment/default',
     'Magento_Checkout/js/model/payment/additional-validators',
     'Magento_Paypal/js/action/set-payment-method',
     'Magento_Customer/js/customer-data'
   ],
   function ($, Component, additionalValidators, setPaymentMethodAction, customerData) {
       'use strict';

       return Component.extend({
           defaults: {
               template: 'SyncIt_Payment/payment/form',
               transactionResult: ''
           },

           initObservable: function () {

               this._super()
                   .observe([
                       'transactionResult'
                   ]);
               return this;
           },

           getCode: function() {
               return payment;
           },

           getData: function() {
               return {
                   'method': this.item.method,
                   'additional_data': {
                       'transaction_result': this.transactionResult()
                   }
               };
           },

           getTransactionResults: function() {
               return _.map(window.checkoutConfig.payment.payment.transactionResults, function(value, key) {
                   return {
                       'value': key,
                       'transaction_result': value
                   }
               });
           },

        /** Redirect to payment provider */
        continueToPayWay: function () {
           if (additionalValidators.validate()) {
              this.selectPaymentMethod();
              setPaymentMethodAction(this.messageContainer).done(
                 function () {
                    customerData.invalidate(['cart']);
                    $.mage.redirect(
                       window.checkoutConfig.payment.payment.redirectUrl
                    );
                 }
              );

              return false;
           }
        }
       });
   }
);

The JS renderer uses Knockout to render the uiComponent. 

file: /view/frontend/web/template/payment/form.html

/*global define*/
<!--
 ~ SyncIt Group
 ~
 ~ This source file is subject to the SyncIt Software License, which is available at https://syncitgroup.com/.
 ~ Do not edit or add to this file if you wish to upgrade to the newer versions in the future.
 ~ If you wish to customize this module for your needs.
 ~ Please refer to http://www.magentocommerce.com for more information.
 ~
 ~ @category  SyncIt
 ~ @package   SyncIt_PayWay
 ~ @author    Igor Stajić <[email protected]>
 ~ @link      https://syncitgroup.com/
 ~ @license   http://opensource.org/licenses/gpl-license.php GNU Public License
 ~ @copyright Copyright (C) 2019 SyncIt (https://syncitgroup.com/)
 ~
 -->
<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 class="label" data-bind="attr: {'for': getCode()}">
           <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>
       <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 class="action primary checkout"
                       type="submit"
                       data-bind="click: continueToPayment, enable: (getCode() == isChecked())"
                       disabled>
                   <span data-bind="i18n: 'Continue to Payment'"></span>
               </button>
           </div>
       </div>
   </div>
</div>

When a customer clicks on ‘Place Order’ they will be redirected to the previously defined custom URL. Of course, you can prepare the necessary data for the Payment Provider.

Finally, you need to tell Magento where to include these JavaScript files.

file: view/frontend/web/js/view/payment/payment.js

<?xml version="1.0"?>
<!--
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn: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="component" xsi:type="string">uiComponent</item>
                                           <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="payment" xsi:type="array">
                                                                   <item name="component" xsi:type="string">SyncIt_Payment/js/view/payment/payment</item>
                                                                   <item name="methods" xsi:type="array">
                                                                       <item name="payment" 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>

Conclusion

The fact is, every eCommerce store should add a Customized Payment Method in order to build a loyal customer base. What is more, it will surely boost your sales and conversion rate.

0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments