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.
If you want to understand the payment process in detail, head to our 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 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 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 interface | Method used in payment handler | Checks for supports method |
---|---|---|
SynchronousPaymentHandlerInterface | pay : always called during checkout | - |
AsynchronousPaymentHandlerInterface | finalize : only called, if pay returns a RedirectResponse | - |
PreparedPaymentHandlerInterface | validate : be aware that this method is always called and can be used to validate a cart during checkout | - |
RecurringPaymentHandlerInterface | recurring | PaymentHandlerType::RECURRING |
RefundPaymentHandlerInterface | refund | PaymentHandlerType::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 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.