Site icon MageComp Blog

How to Create Custom Payment Method in Magento 2

How to Create Custom Payment Method in Magento 2

Hello Magento Friends,

Shopping is no more constrained by geographic borders as online Ecommerce stores are ruling the global market. Whenever online shopping is considered, payment methods prove to be the most vital factor. According to a survey, the lack of a preferred payment method is the most common cause of cart abandonment, which needs to be taken seriously. Hence, E-commerce needs a custom payment method to increase the user experience.

When it comes to Magento 2, being the most popular E-commerce platform, it provides many default payment methods. However, sometimes you may need to create a custom payment method in Magento 2 store to integrate with your choice of payment gateway if it’s not already available.

Steps to Create Custom Payment Method in Magento 2:

Step 1: Create app\code\Vendor\Extension\registration.php to register your payment gateway extension.

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

Step 2: Create app\code\Vendor\Extension\composer.json

{
  "name": "Vendor\module-Extension",
  "description": "Vendor Extension Payment Gateway",
  "require": {
    "php": "~5.5.0|~5.6.0|~7.0.0|~7.1.0|~7.2.0|~7.3.0|~7.4.0"
  },
  "type": "magento2-module",
  "version": "1.0.0",
  "license": [
    "OSL-3.0",
    "AFL-3.0"
  ],
   "autoload": {
    "files": [
      "registration.php"
    ],
    "psr-4": {
      "Vendor\\Extension\\": ""
    }
  }
}

Step 3: Create app\code\Vendor\Extension\etc\module.xml to define your module name.

<?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="Vendor_Extension" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Payment" />
            <module name="Magento_Checkout"/>
            <module name="Magento_Sales"/>
            <module name="Magento_Quote"/>
        </sequence>
    </module>
</config>

Step 4: Create app\code\Vendor\Extension\etc\config.xml to define your Extension payment method.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <payment>
            <Extension>
                <active>1</active>
                <model>Vendor\Extension\Model\Payment</model>
                <payment_action>authorize_capture</payment_action>
                <title>Vendor Extension 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>
            </Extension>
        </payment>
    </default>
</config>

Step 5: Create app\code\Vendor\Extension\etc\adminhtml\system.xml to display the payment method in the admin payment method section.

<?xml version="1.0"?>
<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="Extension" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Vendor Extension Payment Gateway</label>
                <field id="active" translate="label" type="select" sortOrder="10" 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="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Title</label>
                </field>
                <field id="api_key" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>API Secret Key</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                    <comment>Test/Live Secret Key</comment>
                </field>
                <field id="cctypes" translate="label" type="multiselect" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Credit Card Types</label>
                    <source_model>Vendor\Extension\Model\Source\Cctype</source_model>
                </field>
 
                <field id="allowspecific" translate="label" type="allowspecific" sortOrder="60" 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="70" 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="80" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Minimum Order Total</label>
                    <comment>$1 is the minimum amount allowed by Extension Payment</comment>
                </field>
                <field id="max_order_total" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Maximum Order Total</label>
                    <comment>If customer tries to checkout with basket value greater than the maximum allowed they will be prevented from completing the order.</comment>
                </field>
                <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Sort Order</label>
                </field>
            </group>
        </section>
    </system>
</config>

Step 6: Create app\code\Vendor\Extension\etc\frontend\di.xml for dependency Injection configuration.

<?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="Extension" xsi:type="const">Vendor\Extension\Model\Payment::METHOD_CODE</item>
            </argument>
        </arguments>
    </type>
</config>

Step 7: Create a model file to define payment method app\code\Vendor\Extension\Model\Payment.php

<?php
namespace Vendor\Extension\Model;
class Payment extends \Magento\Payment\Model\Method\Cc
{
    const METHOD_CODE                       = 'Extension';
 
    protected $_code                     = self::METHOD_CODE;
 
    protected $_Extension;
 
    protected $_isGateway                   = true;
    protected $_canCapture                  = true;
    protected $_canCapturePartial           = true;
    protected $_canRefund                   = true;
    protected $_minOrderTotal = 0;
    protected $_supportedCurrencyCodes = array('USD','GBP','EUR');
 
    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,
        \Extension\Extension $Extension,
        array $data = array()
    ) {
        parent::__construct(
            $context,
            $registry,
            $extensionFactory,
            $customAttributeFactory,
            $paymentData,
            $scopeConfig,
            $logger,
            $moduleList,
            $localeDate,
            null,
            null,
            $data
        );
 
        $this->_code = 'Extension';
        $this->_Extension = $Extension;
        $this->_Extension->setApiKey($this->getConfigData('api_key'));
 
        $this->_minOrderTotal = $this->getConfigData('min_order_total');
 
 
    }
 
    public function canUseForCurrency($currencyCode)
    {
        if (!in_array($currencyCode, $this->_supportedCurrencyCodes)) {
            return false;
        }
        return true;
    }
 
    public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
 {
        $order = $payment->getOrder();
        $billing = $order->getBillingAddress();
        try{
            $charge = \Extension\Charge::create(array(
                'amount' => $amount*100,
                'currency' => strtolower($order->getBaseCurrencyCode()),
                'card'      => array(
                    'number' => $payment->getCcNumber(),
                    'exp_month' => sprintf('%02d',$payment->getCcExpMonth()),
                    'exp_year' => $payment->getCcExpYear(),
                    'cvc' => $payment->getCcCid(),
                    'name' => $billing->getName(),
                    'address_line1' => $billing->getStreet(1),
                    'address_line2' => $billing->getStreet(2),
                    'address_zip' => $billing->getPostcode(),
                    'address_state' => $billing->getRegion(),
                    'address_country' => $billing->getCountry(),
                ),
                'description' => sprintf('#%s, %s', $order->getIncrementId(), $order->getCustomerEmail())
            ));
 
            $payment->setTransactionId($charge->id)->setIsTransactionClosed(0);
 
            return $this;
 
        }catch (\Exception $e){
            $this->debugData(['exception' => $e->getMessage()]);
            throw new \Magento\Framework\Validator\Exception(__('Payment capturing error.'));
        }
    }
 
    public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        $transactionId = $payment->getParentTransactionId();
 
        try {
            \Extension\Charge::retrieve($transactionId)->refund();
        } catch (\Exception $e) {
            $this->debugData(['exception' => $e->getMessage()]);
            throw new \Magento\Framework\Validator\Exception(__('Payment refunding error.'));
        }
 
        $payment
            ->setTransactionId($transactionId . '-' . \Magento\Sales\Model\Order\Payment\Transaction::TYPE_REFUND)
            ->setParentTransactionId($transactionId)
            ->setIsTransactionClosed(1)
            ->setShouldCloseParentTransaction(1);
 
        return $this;
    }
 
    public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null){
        $this->_minOrderTotal = $this->getConfigData('min_order_total');
        if($quote && $quote->getBaseGrandTotal() < $this->_minOrderTotal) {
            return false;
        }
        return $this->getConfigData('api_key', ($quote ? $quote->getStoreId() : null))
        && parent::isAvailable($quote);
    }
}

Step 8: Create a model file to define Allowed CC type app\code\Vendor\Extension\Model\Source\Cctype.php

<?php
namespace Vendor\Extension\Model\Source;
class Cctype extends \Magento\Payment\Model\Source\Cctype{
    public function getAllowedTypes()
    {
        return array('VI', 'MC', 'AE', 'DI', 'JCB', 'OT');
    }
}

Step 9: Create app\code\Vendor\Extension\view\frontend\web\js\view\payment\Extensionpayments.js to register our template or renderer file.

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

Step 10: Create app\code\Vendor\Extension\view\frontend\web\js\view\payment\method-renderer\Extensionmethod.js

define(
    [
        'Magento_Payment/js/view/payment/cc-form',
        'jquery',
        'Magento_Checkout/js/action/place-order',
        'Magento_Checkout/js/model/full-screen-loader',
        'Magento_Checkout/js/model/payment/additional-validators',
        'Magento_Payment/js/model/credit-card-validation/validator'
    ],
    function (Component, $) {
        'use strict';
 
        return Component.extend({
            defaults: {
                template: 'Vendor_Extension/payment/Extension'
            },
 
            getCode: function() {
                return 'Extension';
            },
 
            isActive: function() {
                return true;
            },
 
            validate: function() {
                var $form = $('#' + this.getCode() + '-form');
                return $form.validation() && $form.validation('isValid');
            }
        });
    }
);

Step 11: Create app\code\Vendor\Extension\view\frontend\web\template\payment\Extension.html template file.

<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>
 
        <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 class="action primary checkout"
                        type="submit"
                        data-bind="
                        click: placeOrder,
                        attr: {title: $t('Place Order')},
                        css: {disabled: !isPlaceOrderActionAllowed()},
                        enable: (getCode() == isChecked())
                        "
                        disabled>
                    <span data-bind="text: $t('Place Order')"></span>
                </button>
            </div>
        </div>
    </div>
</div

Step 12: Create app\code\Vendor\Extension\view\frontend\layout\checkout_index_index.xml to define payment method at checkout page.

<?xml version="1.0"?>
<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="Extension-payments" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Vendor_Extension/js/view/payment/Extensionpayments</item>
                                                                    <item name="methods" xsi:type="array">
                                                                        <item name="Extension" 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 the below commands to install the above extension:

Composer require Extension/Extension-php:3.11.0 and add as below sequence

php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f
php bin/magento cache:clean
php bin/magento cache:flush

Utilize our custom payment method service and integrate various payment methods with your Magento 2 store effortlessly.

Conclusion:

If you have followed the steps in a perfect manner, you can see your custom payment method on the checkout page. I hope this guide was helpful to serve your purpose. Comments and queries are always welcomed.

Happy Coding

Exit mobile version