In this article we will explore how to create a module that allows you to configure and pre-select any payment method in Magento 2 checkout. Often times it is important to make sure a method is selected as soon as checkout is loaded, for example, a preferable choice or simply selecting the only available method - the use cases are endless. It is also useful to allow changing the default method for store administrators.

In order to achieve this we will create new system configuration where you will be able to select the default payment method from admin. In addition we will extend the default checkout functionality to select the configured payment method by passing in data using checkout config provider.

Lets get started. We are going to be working on Magento 2.4 however the code in this article would work with other Magento 2 versions as well.

Firstly you will need to create a custom module but we assume you are already familiar with that. Once you have that lets start by defining our system configuration. We will create a new tab and have a single field which will allow us to select the payment method we want to set as default. We need to declare the configuration file in etc/adminhtml/system.xml:

<?xml version="1.0" ?>
<!--
  ~ * @category	Techflarestudio
  ~ * @author		Wasalu Duckworth
  ~ * @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
  ~ * @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
  ~
  -->

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="techflarestudio" translate="label" class="techflarestudio_checkout_configuration" sortOrder="10">
            <label>Custom Checkout Configuration</label>
        </tab>
        <section id="techflarestudio_checkout" showInDefault="1" showInWebsite="0" showInStore="1" sortOrder="10" translate="label">
            <label>Checkout</label>
            <tab>techflarestudio</tab>
            <resource>Magento_Checkout::checkout</resource>
            <group id="payment" translate="label comment" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Payment step</label>
                <comment>Custom configuration regarding payment step.</comment>
                <field id="default_method" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Default Payment Method</label>
                    <source_model>\Techflarestudio\DefaultPayment\Model\Config\Source\PaymentOptions</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

A few things to mention here:

  • We define a new tab techflarestudio which will appear in the configuration sidebar
  • We define a new section techflarestudio_checkout which will appear as a subsection in the sidebar
  • We define a new group for payment configuration and add a single select field default_method
  • We define a custom source model for the field - \Techflarestudio\DefaultPayment\Model\Config\Source\PaymentOptions. This will allow us to pass in the available payment methods to the field options.

The next step is to create the source model we have already defined for our field. Here is the code for it:

<?php
/*
 *  @category	Techflarestudio
 *  @author		Wasalu Duckworth
 *  @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
 *  @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
 *
 */

namespace Techflarestudio\DefaultPayment\Model\Config\Source;

use \Magento\Framework\Data\OptionSourceInterface;
use \Magento\Payment\Api\PaymentMethodListInterface;
use \Magento\Framework\App\RequestInterface;

/**
 * Class PaymentOptions
 * @package Techflarestudio\DefaultPayment\Model\Config\Source
 */
class PaymentOptions implements OptionSourceInterface
{
    /**
     * @var PaymentMethodListInterface
     */
    protected $paymentMethodList;

    /**
     * @var RequestInterface
     */
    protected $request;

    public function __construct(
        PaymentMethodListInterface $paymentMethodList,
        RequestInterface $request

    )
    {
        $this->paymentMethodList = $paymentMethodList;
        $this->request = $request;
    }

    /**
     * @return \Magento\Payment\Api\Data\PaymentMethodInterface[]
     */
    public function getAvailablePaymentMethods() {
        $storeId = (int) $this->request->getParam('store');
        return $this->paymentMethodList->getActiveList($storeId);
    }

    /**
     * Return array of options as value-label pairs
     *
     * @return array
     */
    public function toOptionArray()
    {
        $options = array();
        $paymentMethods = $this->getAvailablePaymentMethods();

        $options[] = array(
            'label' => '',
            'value' => ''
        );

        foreach($paymentMethods as $paymentMethod)
        {
            $options[] = array(
                'label' => $paymentMethod->getTitle(),
                'value' => $paymentMethod->getCode()
            );
        }

        return $options;
    }
}
\Techflarestudio\DefaultPayment\Model\Config\Source\PaymentOptions

We are extending \Magento\Framework\Data\OptionSourceInterface to define the source model list. This is the new preferred method as the Magento\Framework\Option\ArrayInterface is now deprecated. The new interface requires only a single method toOptionArray where we need to return the list of options in an array with each entry consisting of label -> value pair.

In order to fetch the available payment methods we are using the PaymentMethodListInterface which allows us to fetch the available payment methods per store view. To get the current configuration scope we are fetching the store parameter from the request. Once we have the list we prepare the data in the required format and that is that. Make sure to enable the module, clear the cache and you should be able to see the new configuration in the admin:

New default payment method configuration field

Once we have the ability to save our preferred default payment method we should make it possible to fetch this value in other components. The standard practice here is to create a help which allows to abstract from direct configuration calls. With a single configuration field the approach is a bit of an overkill however this does pay off once you start adding more fields and more logic.

In order to create the helper we need to define a new file in Helper/Data.php and make sure to extend the Magento\Framework\App\Helper\AbstractHelper. In addition we will need to fetch values from configuration for which we can use Magento\Store\Model\ScopeInterface which allows us to fetch configuration values per store view. Here is the helper:

<?php
/*
 *  @category	Techflarestudio
 *  @author		Wasalu Duckworth
 *  @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
 *  @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
 *
 */

namespace Techflarestudio\DefaultPayment\Helper;

use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Store\Model\ScopeInterface;

/**
 * Class Data
 * @package Techflarestudio\DefaultPayment\Helper
 */
class Data extends AbstractHelper
{
    const CONFIG_DEFAULT_PAYMENT = 'techflarestudio_checkout/payment/default_method';

    /**
     * @param $field
     * @param null $storeId
     * @return mixed
     */
    public function getConfigValue($field, $storeId = null)
    {
        return $this->scopeConfig->getValue(
            $field, ScopeInterface::SCOPE_STORE, $storeId
        );
    }

    /**
     * @param null $storeId
     * @return mixed
     */
    public function getDefaultPaymentMethod($storeId = null)
    {
        return $this->getConfigValue(self::CONFIG_DEFAULT_PAYMENT, $storeId);
    }
}
\Techflarestudio\DefaultPayment\Helper\Data

We define our payment configuration path as a constant. Once we start adding new fields we can partially automate the paths by making a separate function for each system.xml group and only specifying the field id. However since we only have a single field this will do just fine.

Ok, now we have the ability to define a default payment method in admin and we have a helper class which we can use to fetch the data whenever we need. Next thing we will need is to make sure the default payment method is available in the checkout config. It will be accessible in window.checkoutConfig object. To do this first we need to define a new configProvider. We can do this by injecting a new argument in CompositeConfigProvider.

<?xml version="1.0"?>
<!--
  ~ * @category	Techflarestudio
  ~ * @author		Wasalu Duckworth
  ~ * @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
  ~ * @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
  ~
  -->

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Model\CompositeConfigProvider">
        <arguments>
            <argument name="configProviders" xsi:type="array">
                <item name="techflarestudio_default_payment" xsi:type="object">Techflarestudio\DefaultPayment\Model\DefaultPaymentConfigProvider</item>
            </argument>
        </arguments>
    </type>
</config>
\Techflarestudio\DefaultPayment\etc\di.xml

We also need to define the custom config provider class.

<?php
/*
 *  @category	Techflarestudio
 *  @author		Wasalu Duckworth
 *  @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
 *  @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
 *
 */

namespace Techflarestudio\DefaultPayment\Model;

use Magento\Checkout\Model\ConfigProviderInterface;
use Magento\Store\Model\StoreManagerInterface;
use Techflarestudio\DefaultPayment\Helper\Data;

/**
 * Class DefaultPaymentConfigProvider
 * @package Techflarestudio\DefaultPayment\Model
 */
class DefaultPaymentConfigProvider implements ConfigProviderInterface
{
    /**
     * @var Data
     */
    protected $configHelper;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    public function __construct(
        Data $configHelper,
        StoreManagerInterface $storeManager
    )
    {
        $this->configHelper = $configHelper;
        $this->storeManager = $storeManager;
    }

    /**
     * @return array
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function getConfig()
    {
        $config = [];
        $store = $this->storeManager->getStore();
        if($store) {
            $config['default_payment_method'] = $this->configHelper->getDefaultPaymentMethod($store->getId());
        }
        return $config;
    }
}
\Techflarestudio\DefaultPayment\etc\di.xml

Two things we are doing here:

  • We are fetching the default payment configuration value for the current store view by using the helper class we defined earlier
  • We are passing in the payment method to a new config key default_payment_method. We will use this later to select the default payment method.

Final step is to create functionality that will allow us to select the payment method by default. For this we can create a mixin for Magento_Checkout/js/model/checkout-data-resolver. First declare the mixin in your requirejs-config.js file:

/*
 *  @category	Techflarestudio
 *  @author		Wasalu Duckworth
 *  @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
 *  @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
 *
 */

var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/model/checkout-data-resolver': {
                'Techflarestudio_DefaultPayment/js/model/checkout-data-resolver': true
            }
        }
    }
};
Techflarestudio/DefaultPayment/view/frontend/requirejs-config.js

Once we have defined our mixin we need to implement the class and add additional logic to resolvePaymentMethod function. Here is the code:

/*
 *  @category	Techflarestudio
 *  @author		Wasalu Duckworth
 *  @copyright  Copyright (c) 2021 Techflarestudio, Ltd. 			(https://techflarestudio.com)
 *  @license	http://opensource.org/licenses/OSL-3.0 The Open Software License 3.0 (OSL-3.0)
 *
 */

define([
    'Magento_Checkout/js/model/payment-service',
    'Magento_Checkout/js/checkout-data',
    'Magento_Checkout/js/action/select-payment-method'
], function(
    paymentService,
    checkoutData,
    selectPaymentMethodAction
) {
    'use strict';

    return function(checkoutDataResolver) {
        checkoutDataResolver.resolvePaymentMethod = function() {
            var availablePaymentMethods = paymentService.getAvailablePaymentMethods(),
                selectedPaymentMethod = checkoutData.getSelectedPaymentMethod(),
                paymentMethod = selectedPaymentMethod ? selectedPaymentMethod : window.checkoutConfig.default_payment_method;

            if (availablePaymentMethods.length > 0) {
                availablePaymentMethods.some(function (payment) {
                    if (payment.method == paymentMethod) {
                        selectPaymentMethodAction(payment);
                    }
                });
            }
        };

        return checkoutDataResolver;
    };
});
Techflarestudio/DefaultPayment/view/frontend/requirejs-config.js

The relevant code here has to do with paymentMethod variable. We want to make sure that an already selected payment method stays selected after reload or checkout step change - therefore we are first checking if a method has already been selected and if it has not we select the configured default method by accessing window.checkoutConfig.default_payment_method.

That is it. If you followed everything correctly, deploy the new code, clear cache and go to checkout. Once visiting payment step you should be able to see that the default method has been selected by default:

Pre-selected check/money order method.

Let us know if you have any comments or would like to request a tutorial about any other common feature you are wondering about.