How To Create Magento 2 Payment Module?

Contact Us

×

Get a Free Consultation

Most merchants hit a wall when their payment gateway isn’t supported by an existing Magento extension. Whether you’re working with a regional processor, a B2B invoicing system, or a proprietary payment flow, the only real solution is to build a custom Magento 2 payment module yourself.

This guide covers exactly how to do that — from file structure and configuration to the JavaScript renderer and checkout template. By the end, you’ll have a working module and a clear understanding of where most developers go wrong.

Summary

  • What a Magento 2 payment module is and why building a custom one matters
  • The core file structure you need before writing a single line of code
  • A step-by-step walkthrough to create a Magento 2 payment module from scratch
  • How to configure the payment model, renderer, and checkout template
  • Common implementation mistakes and how to avoid them
  • How to test and validate your module before going live

What Is a Magento 2 Payment Module?

Custom payment requirements are one of the most common reasons merchants need to extend their Magento stores beyond off-the-shelf options. Whether you’re integrating a regional gateway, a B2B invoicing system, or a proprietary processor, the built-in payment options rarely cover every use case.

A Magento 2 payment module is the mechanism that connects your storefront to a payment service provider. It handles the full transaction lifecycle — from authorizing a charge to capturing funds, issuing refunds, and voiding orders. This guide walks through exactly how to create a Magento 2 payment module, covering every key file, configuration step, and common pitfall.

Understanding Payment Operations in Magento 2

What a Payment Module Controls

Before you start building, it helps to understand what operations a Magento 2 payment module can support.

Operation What It Does
Authorization Blocks funds on the customer’s account without withdrawing
Capture Withdraws the previously authorized amount
Sale (Auth + Capture) Authorizes and captures in a single step
Refund Returns funds to the customer’s account
Void Cancels a pending authorization before capture

Not every module needs to support all five. For example, a simple offline payment method like bank transfer only needs to handle order placement — no real-time gateway calls required.

When to Build Custom vs. Use an Extension

Most merchants can rely on Magento’s built-in gateways (Braintree, PayPal) or Marketplace extensions (Stripe, Authorize.Net). You need a custom Magento 2 payment module when your gateway has no existing extension, you need proprietary checkout logic, or you’re building for a region with local payment rails not covered by existing options.

Required File Structure to Create a Magento 2 Payment Module

Module Registration Files

Every Magento 2 module starts with two files. Without them, Magento won’t recognize the module at all.

app/code/Vendor/Payment/registration.php

php

<?php

\Magento\Framework\Component\ComponentRegistrar::register(

    \Magento\Framework\Component\ComponentRegistrar::MODULE,

    ‘Vendor_Payment’,

    __DIR__

);

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

xml

<?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_Payment setup_version=”1.0.0“>

        <sequence>

            <module name=”Magento_Sales“/>

            <module name=”Magento_Payment“/>

            <module name=”Magento_Checkout“/>

        </sequence>

    </module>

</config>

The sequence block ensures your module loads after Magento’s core payment and checkout modules — a step many developers skip that causes hard-to-debug errors.

Step-by-Step: How to Create a Magento 2 Payment Module

Step 1 — Configure Payment Settings in payment.xml

Create app/code/Vendor/Payment/etc/config.xml to define your method’s default configuration values:

xml

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

            <vendor_simple>

                <active>1</active>

                <model>Vendor\Payment\Model\Payment\Simple</model>

                <title>Simple Payment</title>

                <payment_action>authorize</payment_action>

                <order_status>pending</order_status>

            </vendor_simple>

        </payment>

    </default>

</config>

Next, add the method to etc/payment.xml so Magento’s payment system recognizes it:

xml

<?xml version=”1.0″?>

<payment xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance

         xsi:noNamespaceSchemaLocation=”urn:magento:module:Magento_Payment:etc/payment.xsd“>

    <groups>

        <group id=”offline“>

            <method name=”vendor_simple“/>

        </group>

    </groups>

</payment>

Step 2 — Build the Payment Model

The payment model is the core of your Magento payment module. Create Model/Payment/Simple.php:

php

<?php

namespace Vendor\Payment\Model\Payment;

use Magento\Payment\Model\Method\AbstractMethod;

class Simple extends AbstractMethod

{

    protected $_code = ‘vendor_simple’;

    protected $_isOffline = true;

    protected $_canAuthorize = true;

    protected $_canCapture = true;

    protected $_canRefund = false;

    protected $_canVoid = false;

}

For gateway-based methods that make live API calls, you’ll extend \Magento\Payment\Model\Method\Cc or use the newer GatewayCommand infrastructure instead.

Step 3 — Configure Dependency Injection

Magento 2 uses dependency injection extensively. Create etc/di.xml to wire your model into the payment adapter pool:

xml

<?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\Method\Factory“>

        <arguments>

            <argument name=”classMap xsi:type=”array“>

                <item name=”vendor_simple xsi:type=”string“>Vendor\Payment\Model\Payment\Simple</item>

            </argument>

        </arguments>

    </type>

</config>

Step 4 — Add the Frontend Layout Declaration

Magento 2 checkout is built on Knockout.js UI components. You need to inject your renderer into the checkout layout. Create view/frontend/layout/checkout_index_index.xml:

xml

<?xml version="1.0"?>

<page xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance

      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=”children xsi:type=”array“>

                                                <item name=”payment xsi:type=”array“>

                                                    <item name=”children xsi:type=”array“>

                                                        <item name=”renders xsi:type=”array“>

                                                            <item name=”children xsi:type=”array“>

                                                                <item name=”vendor-payment xsi:type=”array“>

                                                                    <item name=”component xsi:type=”string“>Vendor_Payment/js/view/payment/simple</item>

                                                                    <item name=”methods xsi:type=”array“>

                                                                        <item name=”vendor_simple 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>

Step 5 — Create the JavaScript View and Renderer

Create the parent JS view at view/frontend/web/js/view/payment/simple.js:

javascript

define([

    ‘uiComponent’,

    ‘Magento_Checkout/js/model/payment/renderer-list’

], function (Component, rendererList) {

    ‘use strict’;

    rendererList.push({

        type: ‘vendor_simple’,

        component: ‘Vendor_Payment/js/view/payment/method-renderer/simple-method’

    });

    return Component.extend({});

});

Then create the renderer at view/frontend/web/js/view/payment/method-renderer/simple-method.js:

javascript

define([

    ‘Magento_Checkout/js/view/payment/default’

], function (Component) {

    ‘use strict’;

    return Component.extend({

        defaults: {

            template: ‘Vendor_Payment/payment/simple’

        },

        getCode: function () {

            return ‘vendor_simple’;

        }

    });

});

Step 6 — Create the Checkout Template

The HTML template controls what the customer sees on the payment step. Create view/frontend/web/template/payment/simple.html:

html

<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">

    <div class=”payment-method-title field choice“>

        <input type=”radio

               name=”paymentMethod

               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“>

        <div class=”payment-method-billing-address“></div>

        <div class=”checkout-agreements-block“></div>

        <div class=”actions-toolbar“>

            <div class=”primary“>

                <button class=”action primary checkout

                        type=”submit

                        data-bind=”click: placeOrder, attr: {title: $t(Place Order)}“>

                    <span data-bind=”text: $t(Place Order)“></span>

                </button>

            </div>

        </div>

    </div>

</div>

Enabling and Testing Your Module

Module Activation Commands

Run these commands after creating all files:

bash

php bin/magento module:enable Vendor_Payment

php bin/magento setup:upgrade

php bin/magento setup:di:compile

php bin/magento cache:clean

Common Errors and Fixes

Error Likely Cause Fix
Payment method doesn’t appear at checkout Missing layout XML or JS renderer Check checkout_index_index.xml and clear cache
Module not recognized Missing registration.php Verify file path and namespace
DI compilation fails Incorrect class references in di.xml Check namespaces match actual file paths
JS errors in browser console Template path mismatch Confirm template path in simple-method.js matches actual file

Cache Management During Development

A critical workflow tip: always disable the Full Page Cache and Block HTML Output caches during development. You can do this from Admin > System > Cache Management. Also delete the var/cache, var/page_cache, and pub/static/frontend directories when JS or layout changes aren’t reflecting.

Implementation Tips to Avoid Common Mistakes

What Developers Get Wrong

Most problems when you create a Magento 2 payment module come from three areas: module sequencing, UI component inheritance, and cache issues.

Your module must declare Magento_Checkout in its sequence in module.xml. Skipping this causes intermittent checkout failures that are difficult to trace.

Payment renderers must extend Magento_Checkout/js/view/payment/default, not be written from scratch. This base component handles order placement, validation, and communication with the checkout model.

Never disable cache globally while working with Magento classes. Instead, selectively disable only Layout, Block HTML Output, and Full Page Cache. Keep the Config and DI caches active to ensure your dependency injection and model configurations load correctly.

Developer Mode vs. Production

Always build and test a custom Magento payment module in developer mode (php bin/magento deploy:mode:set developer). This disables caching of static files and enables detailed error reporting — both essential for catching JS and PHP errors during development.

Payment Module Configuration in the Admin Panel

System Configuration Setup

For your payment method to appear in Admin > Stores > Configuration > Sales > Payment Methods, add etc/adminhtml/system.xml:

xml

<?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=”vendor_simple translate=”label type=”text sortOrder=”100 showInDefault=”1 showInWebsite=”1 showInStore=”1“>

                <label>Simple Payment Method</label>

                <field id=”active translate=”label type=”select sortOrder=”1 showInDefault=”1 showInWebsite=”1 showInStore=”0“>

                    <label>Enabled</label>

                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>

                </field>

                <field id=”title translate=”label type=”text sortOrder=”2 showInDefault=”1 showInWebsite=”1 showInStore=”1“>

                    <label>Title</label>

                </field>

            </group>

        </section>

    </system>

</config>

This gives merchants control over enabling the method and setting a display title without touching code.

File Structure Overview

File Path Purpose
etc/module.xml Declares module name and dependencies
etc/config.xml Sets default configuration values
etc/payment.xml Registers method with Magento’s payment system
etc/di.xml Wires model into the dependency injection container
Model/Payment/Simple.php Defines payment logic and supported operations
view/frontend/layout/checkout_index_index.xml Injects renderer into checkout layout
view/frontend/web/js/view/payment/simple.js Registers renderer with Magento’s renderer list
view/frontend/web/js/view/payment/method-renderer/simple-method.js Handles payment selection and order placement
view/frontend/web/template/payment/simple.html Renders the payment option at checkout
etc/adminhtml/system.xml Adds admin configuration fields

Key Takeaways

  • A Magento 2 payment module requires both backend (PHP model) and frontend (JS renderer + HTML template) components working together.
  • Module sequencing in module.xml must include Magento_Checkout — missing this is one of the most common causes of checkout failures.
  • Always extend Magento_Checkout/js/view/payment/default for your JS renderer; writing a renderer from scratch breaks core checkout functionality.
  • Disable only selective caches during development, not all caches — full cache disabling can mask real dependency injection issues.
  • Test each operation your module claims to support (authorize, capture, refund) in a staging environment before deploying.

Conclusion

Building a Magento 2 payment module from scratch takes careful attention to both the PHP backend and the Knockout.js frontend — but the structure is consistent and repeatable once you understand it. The most common failures come from skipped sequencing, incorrect renderer inheritance, and poor cache management during development, not from the payment logic itself.

If your team needs support building or integrating a custom Magento payment module, Folio3’s Magento development team can help you get it done right. Talk to our experts to scope your project.

For related reading, check out our guides on Magento POS integration and how Magento compares to other enterprise platforms.

FAQs

What Files Do I Need to Create a Magento 2 Payment Module?

At minimum: registration.php, module.xml, config.xml, payment.xml, di.xml, a PHP payment model, a layout XML file, two JavaScript files, and an HTML template. Admin configuration requires system.xml as well.

How Do I Add a Custom Payment Method to Magento 2 Checkout?

Register the method in payment.xml, create a JS view and renderer that extend Magento’s default checkout components, and inject them via checkout_index_index.xml. Run setup:upgrade and clear cache after changes.

Can I Create a Magento 2 Payment Module Without Gateway API Calls?

Yes. Extend AbstractMethod with $_isOffline = true to create an offline payment method (like bank transfer or purchase order) that doesn’t make live API requests. This is simpler to build and test.

How Long Does It Take to Build a Custom Magento Payment Module?

A basic offline module can be built in a few hours. A fully integrated gateway module with authorization, capture, refund, and admin configuration typically takes 2–5 days depending on the gateway’s API complexity.

What Is the Difference Between Authorize and Capture in Magento 2?

Authorization blocks funds on the customer’s account without withdrawing them. Capture withdraws the previously authorized amount. Most online stores use “Authorize and Capture” (Sale) simultaneously, while B2B stores often separate them to authorize on order and capture on shipment.

About Author

Picture of Ahsan Horani

Ahsan Horani

"- Total of 8+ years of experience in the E-commerce industry - Experienced Software Engineer having great expertise in PHP, Magento, Docker & Linux - Having strong skills in Leadership, Communication & Client Handling - Worked with clients from different regions of the world including USA, Russia, Canada, U.K, India and more - Quick learner and always eager to get opportunities to learn, work with new technologies & new ideas"

Table of Contents

Related Blogs

Magento Dropshipping – A Complete Guide
Magento

Magento Dropshipping – A Complete Guide

You want to launch a dropshipping store without the overhead of managing inventory. Magento dropshipping makes that possible — but only if you understand how the platform works, which extensions to use, and where most new stores go wrong. This guide covers everything: how the dropshipping model works on Magento, how to set up your

Read More
Magento POS Integration: How to Integrate Magento with Your POS?
Magento

Magento POS Integration: How to Integrate Magento with Your POS?

Running both an online store and a physical retail location means you’re constantly juggling two separate systems — and every time inventory, pricing, or customer data gets out of sync, it costs you time, sales, and trust. A Magento POS integration solves exactly that by connecting your Magento store with your point of sale system

Read More
Magento 2 One Step Checkout: Simplify Your Checkout & Boost Conversions
Magento

Magento 2 One Step Checkout: Simplify Your Checkout & Boost Conversions

Checkout abandonment is costing your store real revenue. Studies show that 26% of shoppers leave at checkout specifically because the process is too long or complicated — not because they changed their mind. If your Magento store uses the default multi-step checkout, that friction is working against you every day. Magento one step checkout is

Read More