Das Problem
In einem Kundenprojekt mit Shopware 6, Mollie Payments und dem SwkwebProductSet-Plugin (Bundle-Plugin für Produktsets) stießen wir auf ein Problem, das sich erst beim Bezahlvorgang zeigte: Mollie meldete eine Preisdiskrepanz und lehnte die Bestellung ab.
Die Fehlermeldung war deutlich — der Gesamtbetrag der einzelnen Positionen stimmte nicht mit dem Bestellwert überein. Aber im Shopware-Backend sah alles korrekt aus. Was war passiert?
Die Ursache: Doppelte Buchungspositionen
Das SwkwebProductSet-Plugin erzeugt beim Hinzufügen eines Bundles zum Warenkorb eine verschachtelte Struktur von Line Items:
swkweb-product-set— der Eltern-Eintrag mit dem korrekten Gesamtpreis des Bundlesswkweb-product-set-slot— Slots, die die einzelnen Positionen im Bundle gruppierenswkweb-product-set-option— Optionen innerhalb der Slotsproduct— die tatsächlichen Produkte als Kind-Elemente
Das Problem: Der Mollie Line Item Builder flacht diese verschachtelte Struktur ab und sendet alle Positionen als separate Buchungsposten an Mollie — inklusive der Kind-Elemente, deren Preise bereits im Eltern-Element enthalten sind. Das Ergebnis: Der Gesamtbetrag wurde doppelt berechnet.
Beispiel: Ein Bundle kostet 49,90 €. Mollie erhält den Eltern-Eintrag (49,90 €) plus die einzelnen Produkte (z.B. 19,90 € + 15,00 € + 15,00 €). Summe bei Mollie: 99,80 € statt 49,90 €.
Die Lösung: Ein Decorator-Plugin
Statt den Code von Mollie oder SwkwebProductSet zu verändern, haben wir uns für das Decorator Pattern entschieden — ein bewährtes Architektur-Muster, das in Shopware 6 über den Symfony Service Container nativ unterstützt wird.
Die Idee: Wir wrappen den bestehenden MollieLineItemBuilder in einen eigenen Decorator, der vor dem Aufruf des Original-Builders die problematischen Kind-Elemente herausfiltert.
Der Decorator im Detail
Der MollieLineItemBuilderDecorator erbt vom originalen Builder und überschreibt die Methode buildLineItemPayload(). Vor dem Aufruf des inneren Builders passiert folgendes:
- Erkennung: Prüfen, ob
swkweb-product-setLine Items in der Bestellung vorhanden sind - Filterung: Rekursives Entfernen aller
swkweb-product-set-slotundswkweb-product-set-optionEinträge - Bereinigung: Beim Eltern-Set-Item werden die Kinder entfernt, damit der Original-Builder sie nicht erneut abflacht
- Delegation: Die bereinigten Line Items werden an den Original-Builder übergeben
- Wiederherstellung: Nach dem Aufruf werden die Original-Line-Items auf der Bestellung wiederhergestellt
Service-Registrierung
Die Einbindung erfolgt über Symfonys decorates-Attribut in der services.xml:
<service id="...MollieLineItemBuilderDecorator"
decorates="Kiener\MolliePayments\...\MollieLineItemBuilder"
decoration-priority="-1">
<argument type="service" id="...MollieLineItemBuilderDecorator.inner"/>
<argument type="service" id="logger"/>
</service>
Der Decorator wird mit Priorität -1 registriert und erhält automatisch den Original-Service als .inner-Referenz. Shopware und Symfony erledigen den Rest — kein einziger Eingriff in fremden Code nötig.
Warum Decorator statt Event-Subscriber?
Shopware 6 bietet verschiedene Erweiterungsmechanismen. Für diesen Fall war der Decorator die beste Wahl:
- Keine Abhängigkeit von Events: Der Mollie-Builder feuert kein geeignetes Event vor dem Aufbau der Line Items
- Vollständige Kontrolle: Wir können die Eingabedaten vor dem Original-Aufruf manipulieren und nach dem Aufruf wiederherstellen
- Saubere Trennung: Der Decorator ist ein eigenständiges Plugin — bei Deinstallation wird der Original-Service automatisch wiederhergestellt
- Zukunftssicher: Wenn Mollie oder SwkwebProductSet Updates liefern, funktioniert der Decorator weiterhin, solange sich die Methodensignatur nicht ändert
Logging für Transparenz
Das Plugin loggt jeden Filtervorgang mit der Bestell-ID, der Anzahl der originalen und gefilterten Line Items. Im Produktionsbetrieb hilft das bei der Fehlersuche, ohne den normalen Betrieb zu beeinflussen:
SwkwebMollieLineItemFix: Filtering SwkwebProductSet child line items
orderId: 018f...
orderNumber: 10042
originalCount: 7
SwkwebMollieLineItemFix: Filtered line items
filteredCount: 3
Ergebnis
Nach der Installation des Plugins werden Bestellungen mit SwkwebProductSet-Bundles korrekt an Mollie übermittelt. Die Preise stimmen, die Zahlung geht durch, und im Shopware-Backend bleibt alles unverändert.
Das Plugin ist mit Shopware 6.6 und 6.7 kompatibel und kann über Composer installiert werden. Der gesamte Quellcode umfasst eine einzige Klasse mit rund 120 Zeilen — klein, fokussiert und wartbar.
Fazit: Nicht jedes Problem braucht eine große Lösung. Manchmal reicht ein gezielter Decorator, der zur richtigen Zeit die richtigen Daten filtert. Das Decorator Pattern in Symfony/Shopware 6 ist dafür ein mächtiges Werkzeug — sauber, minimal-invasiv und rückstandsfrei entfernbar.