Adding a new payment method

Out of the box, payment methods that are registered in the Magento core automatically popup in the Loki Checkout as well. You don't need to do anything for this. However, it might be that things are not working perfectly yet by default or that you need more customization. This document outlines the possibilities.

A custom module is needed:

  • When a custom redirect is needed;
  • When you want to add a custom logo for each payment method;
  • When you want to add a custom form within the checkout;
  • When your payment provider is driven by a JavaScript API;

Often, the redirect is required for a payment solution to work properly. Custom logos are nice to have. Custom forms are often added as an alternative to the redirect and they often require PSP compliance.

Note that the payment API of Hyvä Checkout does not apply to the Loki Checkout. Apples and pears.

Let's talk instead of hack

With Loki Checkout, we aim for an easy integration of payment methods, where the code of one solution is easily reused for another. Keep it simple stupid. However, if this does not apply to your own customization, do not hack the code randomly to get things working.

Instead, get in touch so we can head for the best technical solution with the lowest technical debt.

Assumptions on this page

We assume that there is already an existing Magento 2 extension available for the PSP of choice. And we assume that this extension has been installed already. If this existing module already supports the session property redirect_url, you are almost done.

If not, a LokiCheckout module is definitely needed.

Handling redirects via the session property redirect_url

If a payment method is supposed to redirect to another page, after leaving the checkout, the simplest way to do this is to set the redirect_url property in the checkout session.

$this->checkoutSession->setRedirectUrl('/example');

This assumes that the redirect_url variable is already set somewhere in the logic of the existing payment module. For instance, the module might be hooking into the following observable events:

  • checkout_type_onepage_save_order_after
  • checkout_controller_onepage_saveOrder

Our advice: Test things to see if it already works or not. If it does not, you need to build a LokiCheckout module.

Building a LokiCheckout module

A LokiCheckout module is a Magento module that depends on Yireo_LokiCheckout (both via the module.xml file and as a composer dependency). Likewise, we recommend you to have a README.md and CHANGELOG.md (based on Keep a Changelog).

You could create a new Magento module in the way you see fit. Alternatively, you might want to create a new module based upon the Yireo_LokiCheckoutEmptyPayment module. To use one module as a template for another new module, you could use our Yireo_ModuleDuplicator to rename sources quickly.

All specific payment steps are outlined below anyway.

Handling redirects via a redirect resolver

A more advanced alternative is to hook into the LokiCheckout redirect resolver listing. Create a class that implements the interface Yireo\LokiCheckout\Payment\Redirect\RedirectResolverInterface:

namespace Yireo\Example\Payment\Redirect;

use Magento\Framework\UrlFactory;
use Yireo\LokiCheckout\Payment\Redirect\RedirectResolverInterface;
use Yireo\LokiCheckout\Step\FinalStep\RedirectContext;

class RedirectResolver implements RedirectResolverInterface
{
    public function __construct(
        private UrlFactory $urlFactory,
    ) {
    }

    public function resolve(RedirectContext $redirectContext): false|string
    {
        return $this->urlFactory->create()->getUrl('/example');
    }
}

Your new class needs to be registered with the LokiCheckout mechanism by adding a etc/frontend/di.xml file that adds your class to the constructor argument redirectResolvers of the class Yireo\LokiCheckout\Payment\Redirect\RedirectResolverListing:

<?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="Yireo\LokiCheckout\Payment\Redirect\RedirectResolverListing">
        <arguments>
            <argument name="redirectResolvers" xsi:type="array">
                <item name="example" xsi:type="object">Yireo\Example\Payment\Redirect\RedirectResolver</item>
            </argument>
        </arguments>
    </type>
</config>

Adding a custom logo via an icon resolver

Within the payment method selection panel of the checkout (template Yireo_LokiCheckout::checkout/billing/payment-methods.phtml), payment methods are shown with a radiobox and a label. This can be extended with a logo which is displayed via the PHTML template Yireo_LokiCheckout::checkout/billing/payment-methods/icon.phtml. Within this template, a ViewModel Yireo\LokiCheckout\ViewModel\PaymentMethodIcon is being used to determine the output of an icon image (literally, the HTML of that icon).

For heaven sake, do not override this PHTML template and do not create a DI plugin on this ViewModel.

The ViewModel yet again makes use of an icon resolver logic to try to map the payment method to a relevant icon. For this, first create a new class:

<?php declare(strict_types=1);

namespace Yireo\Example\Payment\Icon;

use Magento\Framework\Module\Manager as ModuleManager;
use Yireo\LokiCheckout\Payment\Icon\IconResolverContext;
use Yireo\LokiCheckout\Payment\Icon\IconResolverInterface;

class IconResolver implements IconResolverInterface
{
    public function resolve(IconResolverContext $iconResolverContext): false|string
    {
        return '<img src="foobar.png" />';
    }
}

Your new class needs to be registered with the LokiCheckout mechanism by adding a etc/frontend/di.xml file that adds your class to the constructor argument iconResolvers of the class Yireo\LokiCheckout\Payment\Icon\IconResolverListing:

<?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="Yireo\LokiCheckout\Payment\Icon\IconResolverListing">
        <arguments>
            <argument name="iconResolvers" xsi:type="array">
                <item name="example" xsi:type="object">Yireo\Example\Payment\Icon\IconResolver</item>
            </argument>
        </arguments>
    </type>
</config>

Common icon patterns

In the example above, a simple image foobar.png is returned. Obviously, you'll want to return the actual static content URL for this image. You can do that for instance via the method Magento\Framework\View\Element\AbstractBlockgetViewFileUrl() (by injecting Magento\Framework\View\Element\Template into your resolver class) - a bit ugly but it works nicely.

When working with image URLs, you might want to take a look at the ViewModel class Yireo\LokiFieldComponents\ViewModel\ImageOutput.

In the case of an SVG, it might be better to output the SVG directly (bypassing an <img> tag):

$iconFilePath = $iconResolverContext->getIconPath(
    'Yireo_Example',
    'view/frontend/web/images/example.svg'
);
        
return $iconResolverContext->getIconOutput($iconFilePath, 'svg');

Adding a custom form

The payment templates also allow for child templates to be added for the purpose of forms and additional things (like scripts). In this example, we'll use a custom payment method with code foobar:

  • Block name loki.checkout.payment.payment-methods.foobar.form
  • Block name loki.checkout.payment.payment-methods.foobar.additional

These blocks do not exist yet, but you can easily add them yourselves. For instance, with a XML layout file loki_checkout_block_payment_methods.xml a block for the form can be created at the global level as follows:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:View/Layout:etc/page_configuration.xsd">
    <body>
        <block
            name="loki.checkout.payment.payment-methods.foobar.form"
            template="Yireo_Example::method/foobar.phtml"/>
    </body>
</page>

In this case, you can see that the block is just a plain PHTML template. How you want to build a HTML form in the PHTML template and link this to the PHTML template, is up to you. This could be custom logic, for instance a complete Loki Component.

A simple component

Integrating JavaScript APIs (asynchronously)

Every time a payment method is selected, a JavaScript event checkout:payment:method-activate is triggered. You can use this event to initialize your payment solution or to send data forth and back.

You can listen to the event as follows:

window.addEventListener('checkout:payment:method-activate', event => {
    if (event.detail.method !== 'foobar') {
        return;
    }

    // Do your magic
    window.exampleMagic();
});

Note that JavaScript events are asynchronous. If you want to full control, use the beforePostNextStep method instead.

Integrating JavaScript APIs (synchronously)

If you want to guarantee that the data in your payment method logic are 100% valid before proceeding, the right way to hook into the LokiCheckout logic is via the beforePostNextStep method:

Whenever the next-step button (LokiCheckoutStepForwardButton component) is used to move to the next step (with payments, this is often the last and final step), the button logic makes sure that all components in that step are valid. During this phase, the button logic also tries to call upon a component its beforePostNextStep method (if it exists). You can use this to synchronously call upon your logic (maybe by using async and await if needed) and set the valid flag of your component to false. Or you could proceed by posting your data to your own component repository in PHP.

The following example assumes that you have setup a LokiComponent with a custom Alpine component and component repository in PHP (to receive the data sent from the JavaScript side):

document.addEventListener('alpine:init', () => {
    Alpine.data('LokiCheckoutExamplePaymentComponent', () => ({
        ...LokiCheckoutComponentType,
        async beforePostNextStep() {
            const example = await window.exampleMagic();
            if (example.errors) {
                this.setValid(false);
                return false;
            }
            
            this.post(example);
            return true;
        }
    }));
});

A simple component Adding a `ComponentRepository` JavaScript compatibility

Last modified: June 2, 2025