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

Payment API

Previous Article

How to Create Custom Shipping Method in Magento 2

Next Article

Magento Year in Review – 2016

Write a Comment
  1. I tried to apply this solution but it didnt show up eighter admin module nor front end. Can you check or recommend me to look for some thing.

    1. Please confirm you have run these command successfully after above code implementation.
      php bin/magento setup:upgrade
      php bin/magento setup:static-content:deploy
      php bin/magento cache:clean

  2. tout d’abord, merci pour le cours 😉
    qd j’arrive sur ce ligne de code
    => php bin/magento setup:upgrade
    il m’envoye des erreurs ….

    **********************CMD***********************************

    C:\wamp64\www\magento>php bin/magento setup:upgrade

    Parse error: syntax error, unexpected ‘.’ in C:\wamp64\www\magento\vendor\magento\framework\ObjectManager\Factory\AbstractFactory.php on line 93

    Call Stack:
    0.0003 126688 1. {main}() C:\wamp64\www\magento\bin\magento:0
    0.0512 924032 2. Magento\Framework\Console\Cli->__construct() C:\wamp64\www\magento\bin\magento:22
    1.3110 13318024 3. Symfony\Component\Console\Application->__construct() C:\wamp64\www\magento\vendor\magento\framework\Console\Cli.php:83
    1.3196 13779456 4. Magento\Framework\Console\Cli->getDefaultCommands() C:\wamp64\www\magento\vendor\symfony\console\Symfony\Component\Console\Application.php:91
    1.3209 13831960 5. Magento\Framework\Console\Cli->getApplicationCommands() C:\wamp64\www\magento\vendor\magento\framework\Console\Cli.php:112
    1.3243 13898632 6. Magento\Framework\App\Bootstrap->getObjectManager() C:\wamp64\www\magento\vendor\magento\framework\Console\Cli.php:128
    1.3243 13898704 7. Magento\Framework\App\Bootstrap->initObjectManager() C:\wamp64\www\magento\vendor\magento\framework\App\Bootstrap.php:362
    1.3243 13899184 8. Magento\Framework\App\ObjectManagerFactory->create() C:\wamp64\www\magento\vendor\magento\framework\App\Bootstrap.php:385
    1.4149 15151288 9. Magento\Framework\App\ObjectManager\Environment\AbstractEnvironment->getObjectManagerFactory() C:\wamp64\www\magento\vendor\magento\framework\App\ObjectManagerFactory.php:177
    1.4149 15151480 10. Magento\Framework\App\ObjectManager\Environment\AbstractEnvironment->createFactory() C:\wamp64\www\magento\vendor\magento\framework\App\ObjectManager\Environment\AbstractEnvironment.php:61
    1.4149 15151808 11. spl_autoload_call() C:\wamp64\www\magento\vendor\magento\framework\App\ObjectManager\Environment\AbstractEnvironment.php:103
    1.4149 15151880 12. Composer\Autoload\ClassLoader->loadClass() C:\wamp64\www\magento\vendor\magento\framework\App\ObjectManager\Environment\AbstractEnvironment.php:0
    1.4153 15152016 13. Composer\Autoload\includeFile() C:\wamp64\www\magento\vendor\composer\ClassLoader.php:301
    1.4157 15166064 14. include(‘C:\wamp64\www\magento\vendor\magento\framework\ObjectManager\Factory\Dynamic\Developer.php’) C:\wamp64\www\magento\vendor\composer\ClassLoader.php:412
    1.4157 15166320 15. spl_autoload_call() C:\wamp64\www\magento\vendor\composer\ClassLoader.php:9
    1.4157 15166384 16. Composer\Autoload\ClassLoader->loadClass() C:\wamp64\www\magento\vendor\composer\ClassLoader.php:0
    1.4160 15166520 17. Composer\Autoload\includeFile() C:\wamp64\www\magento\vendor\composer\ClassLoader.php:301

    ********************************** fin **************************************

    Pouvez vous m’aider s’il vous plaît!
    Cordialement,

  3. does this module store CC info into the database? what if I want to get this information and process those CC manually, instead of running them through payment gateway?

    1. This method don’t store card detail into the database,
      For the manual payment, you need to store card info like saved cc payment method in magento1.

  4. Hi

    Appreciated.

    But in case I need a sample payment module to do cards payments using bank API with magento version 1.9 (1.9.1.0).

    This Bank API is based on php-javaBridge. I do not know the difficulties in this extension. Can you suggest a way to do this

    I would appreciate if you share any sample module for this

  5. Hie, I am working on Magento 2.2 and I want to make two payment methods in single module, I followed your steps and some articles on StackExchange, I successfully able to create module and it is visible in config. But however The second payment method is not showing on checkout page. I tried with several things, but i am not able to show both the payment methods at a time. Can you please guide me on this???

  6. Hello,

    Thank you for posting this blog it is very helpful for me.

    The major stuck for me is that card holder name field is missing, so can you please let me know where to add card holder field in custom payment gateway.

  7. after running your code i have a error in my cmd like “Constant name is expected.”
    while using upgrade command, Do you have a any solution?

  8. Vinod Dhochak

    I’m getting below error while at the time of di:compile.
    impossible to process constructor argument Parameter #9 [ Extension\Extension $Extension ] of Vendor\Extension\Model\Payment c
    lass

  9. The above code will give a compilation error.

    Class Extension\Extension does not exist

Leave a Comment

Your email address will not be published. Required fields are marked *

Get Connect With Us

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨