Markus Michalski FAQ

SEPA-Lastschrift: Entwickler-API

Entwickler-Referenz der SEPA-Lastschrift für Shopware 6.7: MandateCreatedEvent, SepaMandateServiceInterface mit allen Methoden, Admin-API-Routen für PDF, Widerruf und maskierte IBAN sowie die Storefront-Routen.

English Version

Zurück zur Übersicht

SEPA-Lastschrift: Entwickler-API

Live Demo: Teste dieses und weitere Plugins live — ohne Installation, ohne Risiko: demo.markus-michalski.net

Das Plugin bietet anderen Plugins drei Anknüpfungspunkte: ein Event bei neuen Mandaten, einen Service für den Zugriff auf Mandate und HTTP-Routen für Admin und Storefront.

MandateCreatedEvent

Eigenschaft Wert
Event-Name mmd.sepa_payment.mandate.created
Klasse Mmd\SepaPayment\Event\MandateCreatedEvent
Konstante MandateCreatedEvent::EVENT_NAME

Das Event wird ausgelöst, nachdem ein neues Mandat beim Abschluss einer Bestellung gespeichert wurde. Es wird nicht ausgelöst, wenn ein bestehendes Mandat wiederverwendet wird, und nicht, wenn ein Kunde sein Mandat auf der Kundenkonto-Seite anlegt.

Das Event wird unter seinem Namen ausgelöst. Abonniere deshalb den String mmd.sepa_payment.mandate.created, ein Abonnement über den Klassennamen wird nicht ausgelöst.

Methode Rückgabe Beschreibung
getMandateId() string UUID des neuen Mandats
getCustomerId() string UUID des Kunden
getOrderId() string UUID der Bestellung, bei der das Mandat entstanden ist
getContext() Context Shopware-Kontext
getMandate() ?SepaMandateEntity Das gespeicherte Mandat; die IBAN darin ist verschlüsselt

Beispiel: Event-Subscriber

<?php

namespace MyPlugin\Subscriber;

use Mmd\SepaPayment\Event\MandateCreatedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class SepaMandateSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'mmd.sepa_payment.mandate.created' => 'onMandateCreated',
        ];
    }

    public function onMandateCreated(MandateCreatedEvent $event): void
    {
        $mandate = $event->getMandate();

        if ($mandate === null) {
            return;
        }

        // Beispiel: Mandatsdaten an ein ERP-System übergeben
        $reference = $mandate->getMandateReference();
        $holder = $mandate->getAccountHolder();
        $orderId = $event->getOrderId();
    }
}

Optionale Abhängigkeit

Soll dein Plugin das SEPA-Plugin nur optional unterstützen, verwende den Event-Namen als String und tippe den Parameter als object, damit keine harte Abhängigkeit auf die Event-Klasse besteht:

public function onMandateCreated(object $event): void
{
    $mandateId = $event->getMandateId();
}

SepaMandateServiceInterface

Mmd\SepaPayment\Service\Contract\SepaMandateServiceInterface ist als Service registriert (Alias auf SepaMandateService) und kann per Konstruktor injiziert werden.

Methode Rückgabe Beschreibung
createMandate(string $customerId, array $data, Context $context) string Legt ein Mandat an und gibt dessen ID zurück. $data: iban, accountHolder, optional bic. Wirft InvalidArgumentException bei ungültiger IBAN, ungültigem Kontoinhaber oder BIC und RuntimeException, wenn der Kunde schon ein aktives Mandat hat. Löst kein Event aus.
updateMandate(string $mandateId, array $data, Context $context) void Ändert bic und accountHolder. Die IBAN lässt sich nicht ändern.
revokeMandate(string $mandateId, Context $context) void Setzt den Status auf revoked und trägt revokedAt ein. Das Mandat bleibt als Datensatz erhalten.
getMandateByCustomerId(string $customerId, Context $context, bool $activeOnly = true) ?SepaMandateEntity Mandat eines Kunden. Mit activeOnly = false auch widerrufene; bei mehreren Treffern ist die Reihenfolge nicht festgelegt.
getMandateById(string $mandateId, Context $context) ?SepaMandateEntity Mandat per UUID
hasActiveMandate(string $customerId, Context $context) bool Hat der Kunde ein aktives Mandat?
getDecryptedIban(SepaMandateEntity $mandate) string Entschlüsselte IBAN. Wirft RuntimeException, wenn die Entschlüsselung scheitert, etwa nach einem geänderten APP_SECRET.
getMaskedIban(SepaMandateEntity $mandate) string Maskierte IBAN im Format Ländercode, Prüfziffern, ****, letzte vier Zeichen, zum Beispiel DE89****3000

Die Entität SepaMandateEntity bietet unter anderem getMandateReference(), getAccountHolder(), getBic(), getStatus(), getSignedAt(), getRevokedAt(), getCustomerId() sowie isActive() und isRevoked(). getIban() liefert nur die verschlüsselte IBAN. getMaskedIban() an der Entität wirft immer eine Exception, nutze stattdessen den Service.

Beispiel: Mandat eines Kunden lesen

use Mmd\SepaPayment\Service\Contract\SepaMandateServiceInterface;
use Shopware\Core\Framework\Context;

class MyService
{
    public function __construct(
        private readonly SepaMandateServiceInterface $mandateService,
    ) {
    }

    public function describe(string $customerId, Context $context): ?array
    {
        $mandate = $this->mandateService->getMandateByCustomerId($customerId, $context);

        if ($mandate === null) {
            return null;
        }

        return [
            'reference' => $mandate->getMandateReference(),
            'ibanMasked' => $this->mandateService->getMaskedIban($mandate),
            'accountHolder' => $mandate->getAccountHolder(),
            'signedAt' => $mandate->getSignedAt(),
        ];
    }
}

Die entschlüsselte IBAN ist eine personenbezogene Bankverbindung. Rufe getDecryptedIban() nur auf, wenn du sie wirklich brauchst, und schreibe sie nicht in Logs.

Admin-API-Routen

Alle Routen liegen im API-Scope und verlangen eine Admin-Anmeldung.

Methode Pfad Privileg Antwort
GET /api/_action/mmd-sepa-mandate/{mandateId}/pdf mmd_sepa_mandate.viewer PDF-Datei; 404 mit {"error": "Mandate not found"}
POST /api/_action/mmd-sepa-mandate/{mandateId}/revoke mmd_sepa_mandate.editor {"success": true}; 404 wenn unbekannt, 400 wenn das Mandat nicht aktiv ist
GET /api/_action/mmd-sepa-mandate/{mandateId}/masked-iban mmd_sepa_mandate.viewer {"maskedIban": "DE89****3000"}; 404 wenn unbekannt

Das Plugin registriert die Privilegien mmd_sepa_mandate.viewer und mmd_sepa_mandate.editor nicht in der Rollenverwaltung des Admins. Die Routen sind deshalb nur für Administratoren mit Vollzugriff nutzbar.

Zusätzlich ist die Entität mmd_sepa_mandate über die Standard-Entity-API des Admins erreichbar.

Storefront-Routen

Methode Pfad Routenname Zweck
GET /account/sepa-mandate frontend.account.sepa.mandate Mandatsseite (Login nötig)
POST /account/sepa-mandate/create frontend.account.sepa.mandate.create Mandat anlegen (Login nötig)
POST /account/sepa-mandate/revoke frontend.account.sepa.mandate.revoke Aktives Mandat widerrufen (Login nötig)
POST /account/sepa-mandate/validate-iban frontend.account.sepa.mandate.validate-iban Gibt {"valid": true} oder {"valid": false} zurück; Login nötig, XHR; höchstens 10 Anfragen pro Minute und IP, danach 429
GET /account/sepa-mandate/download frontend.account.sepa.mandate.download PDF des eigenen Mandats (Login nötig)
GET /sepa-mandate/download/{orderId} frontend.sepa.mandate.download.order PDF über die Bestellung; Besitzer oder deepLinkCode als Query-Parameter

Das mitgelieferte Checkout-Skript ruft die Route validate-iban nicht auf, es prüft die IBAN im Browser.

Bestellungen und Mandate

Das Plugin speichert keine Verknüpfung zwischen Bestellung und Mandat. Das Mandat gehört zum Kunden. Die Bestellung erfährst du nur im MandateCreatedEvent über getOrderId().

Beispiele für Integrationen

Das Event eignet sich, um Mandatsdaten an eigene Systeme zu melden, etwa an ein ERP, ein CRM oder eine Buchhaltung, oder um eine Benachrichtigung auszulösen. Solche Integrationen liefert das Plugin nicht mit, du baust sie über das Event selbst.