Skip to content

Add Payment Plugin

Add Payment Plugin

WARNING

With Shopware 6.6.5.0, the payment handling was refactored. Most of this documentation is deprecated and obsolete with 6.7.0.0.

The new payment handling is done via a single AbstractPaymentHandler. Check out the new documentation here: Add Payment Plugin (>6.7).

Overview

Payments are an essential part of the checkout process. That's why Shopware 6 offers an easy platform on which you can build payment plugins.

Prerequisites

The examples mentioned in this guide are built upon our plugin base guide.

Plugin base guide

If you want to understand the payment process in detail, head to our Payment Concept.

Payment Concept

Creating a custom payment handler

To create a payment method with your plugin, you have to add a custom payment handler.

Shopware provides you with a handy Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AbstractPaymentHandler abstract class for you to extend to get you started quickly.

Registering the service

Before we're going to have a look at some examples, we need to register our new service to the Dependency Injection container. Please make sure to add the shopware.payment.method tag to your service definition, otherwise Shopware won't recognize your service as a payment handler.

We'll use a class called MyCustomPaymentHandler here.

xml
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Swag\PaymentPlugin\Service\MyCustomPaymentHandler">
            <tag name="shopware.payment.method"/>
        </service>
    </services>
</container>

Now, let's start with the actual examples.

Example payment handlers

Setting up the new payment method

The handler itself is not used yet, since there is no payment method actually using the handler created above. In short: Your handler is not handling any payment method so far. The payment method can be added to the system while installing your plugin.

An example for your plugin could look like this:

php
<?php declare(strict_types=1);

namespace Swag\BasicExample;

use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\ActivateContext;
use Shopware\Core\Framework\Plugin\Context\DeactivateContext;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Shopware\Core\Framework\Plugin\Util\PluginIdProvider;
use Swag\BasicExample\Service\ExamplePayment;

class SwagBasicExample extends Plugin
{
    public function install(InstallContext $context): void
    {
        $this->addPaymentMethod($context->getContext());
    }

    public function uninstall(UninstallContext $context): void
    {
        // Only set the payment method to inactive when uninstalling. Removing the payment method would
        // cause data consistency issues, since the payment method might have been used in several orders
        $this->setPaymentMethodIsActive(false, $context->getContext());
    }

    public function activate(ActivateContext $context): void
    {
        $this->setPaymentMethodIsActive(true, $context->getContext());
        parent::activate($context);
    }

    public function deactivate(DeactivateContext $context): void
    {
        $this->setPaymentMethodIsActive(false, $context->getContext());
        parent::deactivate($context);
    }

    private function addPaymentMethod(Context $context): void
    {
        $paymentMethodExists = $this->getPaymentMethodId();

        // Payment method exists already, no need to continue here
        if ($paymentMethodExists) {
            return;
        }

        $pluginIdProvider = $this->container->get(PluginIdProvider::class);
        $pluginId = $pluginIdProvider->getPluginIdByBaseClass(get_class($this), $context);

        $examplePaymentData = [
            // payment handler will be selected by the identifier
            'handlerIdentifier' => MyCustomPaymentHandler::class,
            'name' => 'Example payment',
            'description' => 'Example payment description',
            'pluginId' => $pluginId,
            // if true, payment method will also be available after the order 
            // is created, e.g. if payment fails and the user wants to try again
            'afterOrderEnabled' => true,
            // the technicalName helps you to identify the payment method uniquely
            // it is best practice to use a plugin specific prefix to avoid conflicts
            'technicalName' => 'swag_example-example_payment',
        ];

        $paymentRepository = $this->container->get('payment_method.repository');
        $paymentRepository->create([$examplePaymentData], $context);
    }

    private function setPaymentMethodIsActive(bool $active, Context $context): void
    {
        $paymentRepository = $this->container->get('payment_method.repository');

        $paymentMethodId = $this->getPaymentMethodId();

        // Payment does not even exist, so nothing to (de-)activate here
        if (!$paymentMethodId) {
            return;
        }

        $paymentMethod = [
            'id' => $paymentMethodId,
            'active' => $active,
        ];

        $paymentRepository->update([$paymentMethod], $context);
    }

    private function getPaymentMethodId(): ?string
    {
        $paymentRepository = $this->container->get('payment_method.repository');

        // Fetch ID for update
        $paymentCriteria = (new Criteria())->addFilter(new EqualsFilter('handlerIdentifier', ExamplePayment::class));
        return $paymentRepository->searchIds($paymentCriteria, Context::createDefaultContext())->firstId();
    }
}

In the install method, you start by creating a new payment method, if it doesn't exist yet. If you need to know what's happening in there, you might want to have a look at our guide regarding Writing data.

DANGER

Do not do the opposite in the uninstall method and remove the payment method. This might lead to data inconsistency if the payment method was used in some orders. Instead, only deactivate the method!

The activate method and deactivate method just do that, activating and deactivating the payment method, respectively.

Identify your payment

You can identify your payment by the entity property formattedHandlerIdentifier. It shortens the original handler identifier (php class reference): Custom/Payment/SEPAPayment to handler_custom_sepapayment. The syntax for the shortening can be looked up in Shopware\Core\Checkout\Payment\DataAbstractionLayer\PaymentHandlerIdentifierSubscriber.

Otherwise, you can use your given technical name to uniquely identify your payment method.

Migrating payment handlers from 6.6

If you are migrating a payment handler from a version before 6.7, you need to move from the existing interfaces to the new abstract class and add your own order data loading..

Payment handler interfaces removed

Instead of implementing multiple interfaces of Shopware\Core\Checkout\Payment\Cart\PaymentHandler\PaymentHandlerInterface in your payment handler, extend the Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AbstractPaymentHandler class and implement the necessary methods.

Old interfaceMethod used in payment handlerChecks for supports method
SynchronousPaymentHandlerInterfacepay: always called during checkout-
AsynchronousPaymentHandlerInterfacefinalize: only called, if pay returns a RedirectResponse-
PreparedPaymentHandlerInterfacevalidate: be aware that this method is always called and can be used to validate a cart during checkout-
RecurringPaymentHandlerInterfacerecurringPaymentHandlerType::RECURRING
RefundPaymentHandlerInterfacerefundPaymentHandlerType::REFUND

New single tag for payment handlers

Your payment handler should now have a single tag in the service definition: shopware.payment.method. Remove any other occurrences of the following tags: shopware.payment.method.sync, shopware.payment.method.async, shopware.payment.method.prepared, shopware.payment.method.recurring, shopware.payment.method.refund.

xml
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="Swag\PaymentPlugin\Service\MyCustomPaymentHandler">
            <!-- this is the new tag for payment handlers -->
            <tag name="shopware.payment.method"/>

            <!-- remove any of these other tags -->
            <tag name="shopware.payment.method.sync"/>
            <tag name="shopware.payment.method.async"/>
            <tag name="shopware.payment.method.prepared"/>
            <tag name="shopware.payment.method.recurring"/>
            <tag name="shopware.payment.method.refund"/>
        </service>
    </services>
</container>

Prepared payments

In the past, you would have to implement the validate and capture methods when dealing with the PreparedPaymentHandlerInterface.

Now, you only have to implement the validate method. Instead of the capture method, the streamlined pay method is used and has to be implemented.