Shopware 6 Payment Plugin

In diesem Beitrag möchte ich zeigen, wie man in Shopware 6, ein Plugin entwickelt, für eine eigene Anbindung an eine Zahlungsanbieter. Ich werde hier lediglich auf die Grundlegende Struktur eingehen. Die Implementierung an sich variiert natürlich. Zuerst mal ein paar nützlich Links zu Shopware 6 Dokumentation.

Das Beispielplugin hat den Namen DemoPayment. Auf folgendem Bild ist die Struktur des Plugins abgebildet.

Zuerst gehe ich auf den Einstiegstpunkt des Plugins ein. Dies ist die Datei composer.json. Diese befindet sich im direkt im root Verzeichnis des Plugin Ordners. Es ist der Grundsätzliche Einstieg für alles Plugins in Shopware 6. Hier wird der Einstiegspunkt, interne Name, interne Beschreibung etc. festgelegt.

{
  "name": "demopayment/demopayment",
  "description": "DemoPayment Plugin",
  "version": "1.0.0",
  "type": "shopware-platform-plugin",
  "license": "MIT",
  "authors": [
    {
      "name": "Jens"
    }
  ],
  "extra": {
    "shopware-plugin-class": "DemoPayment\\DemoPayment",
    "label": {
      "de-DE": "DemoPayment",
      "en-GB": "DemoPayment"
    },
    "description": {
      "de-DE": "Beispiel DemoPayment",
      "en-GB": "Example DemoPayment"
    }
  },
  "autoload": {
    "psr-4": {
      "DemoPayment\\": "src/"
    }
  }
}

Die Hauptklasse des Plugins liegt direkt im src Ordner und heißt in diesem Fall DemoPayment.php. Hier werden werden die Methoden install, build, activate and deactivate überschrieben und für unseren Nutzen angepasst.

Install Method

Zuerst schauen wir uns die install Methode an. Zuerst wird hier mit der Hilfsfunktion getPaymentMethodId geprüft, ob unsere Bezahlmethode schon in der Datenbank existiert, um eine doppelten Eintrag zu verhindern. Nun wird mithilfe von Shopware Boardmitteln die Plugin-Id generiert. Jetzt wird es interessant, denn in dem Array $paymentData wird unsere Zahlungsart benamt und definiert. Shopware bietet zwei arten von Handlern, die implementiert werden können. Einmal synchron und Asynchron. Doch dazu später mehr. Weiterhin wird hier noch der Name, die Beschreibung mit Übersetzung, falls gewollt, sowie die Positions angegeben. Zuletzt werden die Daten mithilfe des passenden Repositories, auch von Shopware bereitgestellt, in die Datenbank geschrieben.

    /**
     * @inheritDoc
     */
    public function install(InstallContext $context): void
    {
        if ($this->getPaymentMethodId($context->getContext())) {
            return;
        }

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

        $paymentData = [
            'handlerIdentifier' => DemoPaymentHandler::class, //DemoPaymentAsyncHandler::class
            'name'              => self::PAYMENT_NAME,
            'position'          => -40,
            'translations'      => [
                'de-DE' => [
                    'description' => 'einfache Bezahlung mit DemoPayment',
                ],
                'en-GB' => [
                    'description' => 'easy payment with DemoPayment',
                ],
            ],

            'pluginId' => $pluginId,
        ];

        /** @var EntityRepositoryInterface $paymentRepository */
        $paymentRepository = $this->container->get('payment_method.repository');
        $paymentRepository->create([$paymentData], $context->getContext());
    }

Aktivieren/Deaktivieren

in diesem Teil geht es um das Aktivieren und Deaktivieren des Plugins. Denn sobald das Plugin im Backend von Shopware deaktiviert wird, soll auch die Zahlungsart, nicht mehr für Kunden verfügbar sein. Hierfür wird in beiden Methoden die Hilfsfunktion setPaymentActive aufgerufen. Sie ist dazu geschrieben worden, um die Zahlungsart, je nach übergeben von true oder false, in der Datenbank aktiv oder inaktiv zu setzen.

    /**
     * @inheritDoc
     */
    public function activate(ActivateContext $context): void
    {
        $this->setPaymentActive(true, $context->getContext());
        parent::activate($context);
    }

    /**
     * @inheritDoc
     */
    public function deactivate(DeactivateContext $context): void
    {
        $this->setPaymentActive(false, $context->getContext());
        parent::deactivate($context);
    }

    /**
     * @param bool    $active
     * @param Context $context
     *
     * @throws InconsistentCriteriaIdsException
     */
    private function setPaymentActive(bool $active, Context $context): void
    {
        $paymentMethodId = $this->getPaymentMethodId($context);

        if (!$paymentMethodId) {
            return;
        }

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

        $this->container->get('payment_method.repository')->update([$paymentMethod], $context);
    }

Payment Handler Synchron vs. Asynchron

Der unterschied zwischen beiden Methoden ist schnell erklärt. Der Synchrone Weg wird eingeschlagen, wenn man z.B. eine API anbindet, sprich eine anfrage absetzt und direkt eine Antwort bekommt. In Richtung Asynchron geht es, wenn der Zahlende Kunde auf Drittanbieter Website weitergeleitet wird, um nach dem erfolgreichen bezahlen zurück zum Shop geleitet wird. Im folgenden ein Beispiel der beiden Handler-Dateien.

Synchroner Handler

class DemoPaymentHandler implements SynchronousPaymentHandlerInterface
{
    /**
     * @inheritDoc
     */
    public function pay(
        SyncPaymentTransactionStruct $transaction,
        RequestDataBag $dataBag,
        SalesChannelContext $salesChannelContext
    ): void {
        // TODO: Implement pay() method.
    }
}

Asynchroner Handler

class DemoPaymentAsyncHandler implements AsynchronousPaymentHandlerInterface
{

    public function pay(
        AsyncPaymentTransactionStruct $transaction,
        RequestDataBag $dataBag,
        SalesChannelContext $salesChannelContext
    ): RedirectResponse {
        // TODO: Implement pay() method.
    }

    public function finalize(
        AsyncPaymentTransactionStruct $transaction,
        Request $request,
        SalesChannelContext $salesChannelContext
    ): void {
        // TODO: Implement finalize() method.
    }
}

Registrierung

Natürlich muss auch der Handler für Shopware registriert werden. Dies passiert innerhalb der service.xml unterhalb des Resources Ordners. Auf das registrieren werde ich im nächsten Kapitel nochmals etwas genauer eingehen. Hier also der Ausschnitt der service.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="DemoPayment\Service\DemoPaymentHandler">
            <tag name="shopware.payment.method.sync" />
        </service>

        <!--<service id="DemoPayment\Service\DemoPaymentAsyncHandler">
            <tag name="shopware.payment.method.async" />
        </service>-->

    </services>
</container>

Subscriber Registrieren

Kommen wir zur Build Funktion. Essentiell wichtig für die Implementierung einer Zahlungsart ist das, was ich hiermit zeigen möchte nicht. Aber es kann behilflich sein ein Plugin gut zu strukturieren und das Event-Verfahren besser zu verstehen. Es gibt zwei Möglichkeiten Klassen zu registrieren, damit Shopware sie benutzen kann. Einmal geht das über die Datei services.xml im cofig Ordner unter Resources. Oder man nutzt die Build Methode im Einstiegspunkt eines Plugins, um manuell xml Dateien einzulesen und so zu registrieren. Zwei Beispiele für Subscriber werden hier gezeigt. Einmal für Events betreffend des Benutzers und einmal für den Checkout.

    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $subscriberList = [
            new XmlFileLoader($container, new FileLocator(__DIR__ . '/Checkout/')),
            new XmlFileLoader($container, new FileLocator(__DIR__ . '/Customer/')),
        ];

        /** @var XmlFileLoader $subscriber */
        foreach ($subscriberList AS $subscriber) {
            $subscriber->load('subscriber.xml');
        }
    }

Hier mal die Definition der xml Datei für den Checkout-Subscriber. Die id identifiziert den „Service“ eindeutig bzw. wird zum einbinden der Datei benutzt. Der Tag sagt Shopware, worum es sich bei der eingefügten Datei handelt. In diesem Fall um einen Subscriber.

<?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="DemoPayment\Checkout\CheckoutSubscriber">
            <tag name="kernel.event_subscriber"/>
        </service>
    </services>
</container>

Hier nun das Beispiel, wie der eigentlich Subscriber aussieht. Leider konnte ich in der Dokumentation noch keine Liste an Events finden. Hier musste ich selbst ein wenig im Core von Shopware suchen. Die Event-Dateien enden alle mit dem Wort „Event“. Ein guter Indikator, wenn man ein bestimmtes Event sucht. Was hier passiert ist ziemlich simpel. Sobald ein Kunde die Confirm-Seite betritt und diese geladen wurde, wird die von uns angegebene Methode checkoutConfirmPageLoaded aufgerufen.

class CheckoutSubscriber implements EventSubscriberInterface
{
    /**
     * @inheritDoc
     */
    public static function getSubscribedEvents()
    {
        return [
            CheckoutConfirmPageLoadedEvent::class => ['checkoutConfirmPageLoaded', 1],
        ];
    }

    public function checkoutConfirmPageLoaded(CheckoutConfirmPageLoadedEvent $event)
    {
    }
}

Das gleiche geschieht geschieht im Subscriber innerhalb des Verzeichnisses Customer.

Plugin Konfiguration

Im diesem letzten Punkt möchte ich noch kurz darauf eingehen, wie man das Plugin konfigurierbar macht. Die Konfigurationen werden im Backend des Shops unter den Einstellungen des Plugins zu finden sein. Dies ist nur ein kleines Beispiel, um eine zusätzliche Ein- und Ausschaltfunktion einzubauen. Hierzu legt man, falls noch nicht vorhanden die Datei /src/Resources/config/config.xml an. Hier kann man nach belieben Einstellungen hinzufügen, auf die man dann im Code zurückgreifen kann. Aufteilen bzw. gruppieren kann man diverse Einstellungen, in dem man sie in unterschiedliche Cards aufteilt. hier nun ein das kleine Beispiel.

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/System/SystemConfig/Schema/config.xsd">
    <card>
        <title>Basic Configuration</title>
        <title lang="de-DE">Grundeinstellungen</title>

        <input-field type="bool">
            <name>active</name>
            <label>Activate</label>
            <label lang="de-DE">Aktivieren</label>
            <helpText>Activate/Deactivate?</helpText>
            <helpText lang="de-DE">Soll DemoPayment für diesen Sales-Channel aktiviert werden?</helpText>
        </input-field>
    </card>
</config>

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert