Categories: How ToMagento 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

Click to rate this post!
[Total: 28 Average: 4]
Dhiren Vasoya

Dhiren Vasoya is a Director and Co-founder at MageComp, Passionate 🎖️ Certified Magento Developer👨‍💻. He has more than 9 years of experience in Magento Development and completed 850+ projects to solve the most important E-commerce challenges. He is fond❤️ of coding and if he is not busy developing then you can find him at the cricket ground, hitting boundaries.🏏

View Comments

  • 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

  • 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?

  • 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.

  • 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???

  • 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

  • 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?

    • 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.

  • 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,

Recent Posts

What are Net Sales? How to Calculate Your Net Sales?

In the world of business, understanding financial metrics is crucial for making informed decisions and…

23 hours ago

Magento 2 Extensions Digest April 2024 (New Release & Updates)

Welcome to the MageComp Monthly Digest, where we bring you the latest updates, releases, and…

23 hours ago

The ABCs of Geofencing: Definition, Features and Uses

In this era, businesses are always on the lookout for ways to engage with their…

2 days ago

How to Delete Product Variant in a Shopify Remix App using GraphQL Mutations?

Managing a Shopify store efficiently involves keeping your product catalog organized. This includes removing outdated…

3 days ago

6 Innovative Tools Revolutionizing E-Commerce Operations

E-commerce has transformed the way consumers shop for products and services and interact with businesses.…

5 days ago

How Upcoming Cookie Changes Will Affect Your E-commerce Website?

The e-commerce world is constantly in flux. New tech and strategies emerge daily to help…

5 days ago