# 4.5.0

- Neu: „Bestellungen aus anderen Verkaufskanälen berücksichtigen" — das Widerrufsformular findet jetzt auf Wunsch auch Bestellungen, die in einem anderen Verkaufskanal derselben Installation aufgegeben wurden. Gedacht für Shops, deren Verkaufskanäle sich einen Kundenstamm teilen, etwa ein zweiter Kanal mit abweichenden Preisen. Wirkt sowohl auf die manuelle Eingabe der Bestellnummer als auch auf die Bestellauswahl für eingeloggte Kunden.
- Die Option ist standardmäßig deaktiviert; bestehende Installationen verhalten sich unverändert.
- Sicherheit: Für eingeloggte Kunden bleibt die Prüfung auf die Kunden-ID bestehen, fremde Bestellungen sind also weiterhin nicht erreichbar. Für Gäste wirkt die Option nur bei aktiver E-Mail-Validierung, damit eine bloße Bestellnummer keine kanalübergreifende Suche auslösen kann.
- Voraussetzung: Kunden dürfen nicht an einen Verkaufskanal gebunden sein (Einstellungen → Login und Registrierung), sonst existiert je Kanal ein eigener Kundendatensatz.
- Hinweis: Widerrufsfrist und Ausschlüsse werden weiterhin aus dem Verkaufskanal gelesen, in dem der Kunde gerade unterwegs ist. Existiert dieselbe Bestellnummer in zwei Kanälen, wird der Widerruf als mehrdeutig abgelehnt statt geraten.
- Keine Datenbank-Migration nötig.

# 4.4.14

- Neu: Karte „Widerrufsformular" in den Plugin-Einstellungen mit zwei Schaltern.
- Neu: „Feld ‚Grund des Widerrufs' ausblenden" — das optionale Freitextfeld kann jetzt komplett aus dem Formular entfernt werden. Ein trotzdem übermitteltes Feld wird serverseitig verworfen und erscheint weder in der Bestätigung noch in der internen Benachrichtigung.
- Neu: „Captcha im Widerrufsformular deaktivieren" — das shopweit konfigurierte Captcha (Honeypot, Bild-Captcha, reCAPTCHA v2/v3) kann für die Widerrufsseite ausgenommen werden, ohne es im ganzen Shop abzuschalten. Sinnvoll, wenn das Captcha auf dieser gesetzlich vorgeschriebenen Seite zu Barrierefreiheits- oder Einwilligungsproblemen führt. Achtung: Ohne Captcha können Bots Mailversand über den Shop auslösen — daher standardmäßig aus.
- Beide Optionen sind standardmäßig deaktiviert; bestehende Installationen verhalten sich unverändert.
- Keine Datenbank-Migration nötig.

# 4.4.13

- Fix: Stornierte bzw. abgebrochene Bestellungen (Status „Abgebrochen") zeigen im Kundenkonto keinen „Vertrag widerrufen"-Link mehr. Solche Bestellungen gelten im Plugin als bereits widerrufen und wurden beim Absenden ohnehin abgelehnt — der Link führte also in eine Sackgasse und fehlte konsequenterweise auch in der Bestellauswahl auf der Widerrufsseite. Konto-Link, Bestellauswahl und Widerruf-Prüfung verhalten sich jetzt einheitlich.
- Keine Datenbank-Migration, keine Konfigurationsänderung nötig.

# 4.4.12

- Fix: Der „Vertrag widerrufen"-Link in der Konto-Bestellübersicht (und auf der Konto-Startseite) wird jetzt anhand der **echten** Widerrufsfrist ein- bzw. ausgeblendet — mit derselben Logik, die auch beim Absenden des Widerrufs gilt. Bisher schätzte die Anzeige die Frist vereinfacht ab dem Bestelldatum; bei den Fristbeginn-Modi „Rechnungsdatum" und „Ab Lieferstatus" (z. B. „Zugestellt") fehlte der Link dadurch fälschlich bei Bestellungen, die älter als die Frist waren, aber erst kürzlich zugestellt bzw. berechnet wurden. Der Widerruf selbst (über die Widerrufsseite) war davon nicht betroffen und funktionierte immer korrekt.
- Keine Datenbank-Migration, keine Konfigurationsänderung nötig.

# 4.4.11

- Feature: Neue Option **„Ab Lieferstatus"** für den „Fristbeginn" (Karte „Widerrufs-Einstellungen"). Zusätzlich zu den festen Daten (Bestell-, Versand-, Rechnungsdatum) lässt sich jetzt **jeder Lieferstatus deines Shops** als Fristbeginn wählen — auch eigene Status wie „Zugestellt", die z. B. ein Tracking-Dienst (Trackingmore o. Ä.) automatisch setzt. Die Widerrufsfrist läuft dann ab dem Zeitpunkt, zu dem die Lieferung der Bestellung diesen Status erreicht hat. Bei Teillieferungen zählt der **späteste** Zeitpunkt — passend zur gesetzlichen Formulierung „ab Erhalt der letzten Ware" (§ 356 Abs. 2 Nr. 1 BGB). Solange der gewählte Status nicht erreicht ist, bleibt die Bestellung widerrufbar.
- Das Auswahl-Dropdown für den Fristbeginn ist jetzt in **„Feste Daten"** und **„Ab Lieferstatus"** gruppiert, jeweils mit einem erklärenden Hilfetext (Fragezeichen).
- Hinweis: Der Fristbeginn wird aus der Lieferstatus-Historie (State-Machine) ermittelt. Wird ein Status ausnahmsweise ohne echte Statusänderung gesetzt (z. B. per direktem Datenbank-Update), lässt sich der Zeitpunkt nicht bestimmen — die Bestellung bleibt dann sicherheitshalber widerrufbar (kein Rückfall auf das Bestelldatum).
- Keine Datenbank-Migration, keine Konfigurationsänderung nötig. Bestehende Shops bleiben unverändert (Standard weiterhin „Versanddatum").

# 4.4.10

- Feature: Die interne Widerrufs-Benachrichtigung an den Händler trägt jetzt die E-Mail-Adresse des Widerrufenden als Antwort-Adresse (`Reply-To`) im Mail-Header. Der Händler kann damit direkt aus der Benachrichtigung heraus „Antworten" und erreicht sofort den widerrufenden Kunden. Die Absenderadresse (`From`) bleibt unverändert die Shop-Adresse, sodass SPF/DKIM und die Zustellbarkeit unberührt bleiben. Betrifft nur die interne Händler-Mail — die Kundenbestätigung erhält keinen Reply-To. Keine Konfiguration nötig.

# 4.4.9

- Feature: Zusätzlich zu Kundengruppen kann der Widerruf jetzt auch über **Rule-Builder-Regeln** deaktiviert werden (neue Auswahl „Widerruf für diese Regeln deaktivieren" in der Karte „Vom Widerruf ausgeschlossene Kundengruppen & Regeln"). Beide Kriterien sind frei kombinierbar (ODER-verknüpft) — ideal für Fälle, die sich nicht über Kundengruppen abbilden lassen (z. B. „Rechnungsland außerhalb der EU"). Bei eingeloggten Kunden wirkt die Regel direkt auf Anzeige und Formular; beim Gast-Widerruf (Bestellnummer + E-Mail) greift sie, sofern die Bestellnummern-Validierung aktiv ist (die Regel wird dann gegen die zugeordnete Bestellung ausgewertet).
- Hinweis: EU-Verbraucher nicht versehentlich ausschließen — die Online-Widerrufsfunktion ist für sie gesetzlich vorgeschrieben (§356a BGB).
- Keine Datenbank-Migration nötig. Bestehende Shops bleiben unverändert (es sind keine Regeln vorausgewählt).

# 4.4.8

- Feature: Neue Option **„Rechnungsdatum"** für den „Fristbeginn" (Karte „Widerrufs-Einstellungen"). Die Widerrufsfrist läuft dann ab dem Datum des Rechnungsdokuments der Bestellung — unabhängig von Bestell- oder Versanddatum. Existiert (noch) keine Rechnung, ist die Bestellung nicht widerrufbar.
- Klarstellung: Der Hilfetext zu „Fristbeginn" erläutert jetzt alle drei Optionen genauer. Insbesondere ist „Versanddatum" der Zeitpunkt, zu dem die Lieferung in Shopware auf den Status „Versand abgeschlossen" gesetzt wird (das ist NICHT das Lieferschein- oder Rechnungsdatum); solange eine Bestellung nicht als versandt markiert ist, bleibt sie widerrufbar.
- Keine Datenbank-Migration, keine Konfigurationsänderung nötig. Bestehende Shops bleiben unverändert (Standard weiterhin „Versanddatum").

# 4.4.7

- Fix: Kompatibilität mit Plugins, die Shopwares `product.repository` durch einen eigenen Decorator ersetzen (z. B. NetInventors „PurchaseBlocker"). Bisher schlug der Aufruf der Widerrufsseite in solchen Shops mit einem `TypeError` fehl („… must be an instance of EntityRepository, instance of …ProductRepositoryDecorator given") und es erschien „Leider ist etwas schief gelaufen". Der interne Dienst akzeptiert jetzt jedes Repository — unabhängig davon, ob es von `EntityRepository` erbt. Keine Datenbank-Migration, keine Konfigurationsänderung nötig.

# 4.4.6

- Feature: Neuer Schalter **„Widerrufsfrist prüfen"** (Karte „Widerrufs-Einstellungen", Standard **an**). Wenn deaktiviert, werden Widerrufe **unabhängig von der Frist** angenommen — für Händler, die das Lieferdatum vorab nicht prüfen können und die Gültigkeit im Nachgang manuell (z. B. anhand der Trackingdaten) beurteilen wollen. Bei deaktivierter Prüfung erscheinen für eingeloggte Kunden auch ältere Bestellungen in der Auswahlliste, und das Absenden wird nicht mehr mit „Frist abgelaufen" abgelehnt. Die Felder „Widerrufsfrist (Tage)" und „Fristbeginn" wirken dann nicht.
- Bestehende Shops bleiben unverändert: Der Schalter ist standardmäßig an (auch ohne gespeicherten Wert), die Fristprüfung läuft also wie bisher. Keine Datenbank-Migration nötig.

# 4.4.5

- Änderung: Der Standard-Link „Datenschutzhinweise" im Widerrufsformular öffnet die Datenschutzseite jetzt als **normale, vollständige Seite in einem neuen Tab** statt als Modal. Hintergrund: Ein einfacher Seiten-Link ist robuster, verhält sich in Storefront und E-Mail identisch und behält dank neuem Tab das halb ausgefüllte Formular. Gilt nur für **Neuinstallationen** — bestehende Shops bleiben unverändert.
- Feature: Für Consent-Labels stehen zwei Magic-Link-Schemata zur Wahl. `page://privacy`, `page://tos`, `page://imprint` öffnen die konfigurierte CMS-Seite als **volle Seite** (empfohlen; mit `target="_blank"` im neuen Tab). `cms://privacy` usw. öffnen sie weiterhin **im Modal**. Auswahl pro Eintrag.
- Fix: Das **Modal** ging nicht auf, weil der HTML-Sanitizer die nötigen `data-ajax-modal`-Attribute entfernt hatte. `cms://`-Links öffnen jetzt korrekt als Modal — wie Shopwares eigene Footer-Links (das Label wird serverseitig bereinigt und die Modal-Attribute werden danach wieder gesetzt).
- Fix: Der Consent-Link in der **Widerrufsbestätigungs-Mail** war relativ (ohne Domain) und zeigte auf das nackte CMS-Fragment — teils funktionierte er gar nicht. Er ist jetzt eine **absolute, vollständige Seiten-URL**.
- Bestehende Shops: Ein vorhandenes `cms://privacy` bleibt erhalten und öffnet weiterhin das (jetzt funktionierende) Modal; die Mail erhält automatisch den absoluten Vollseiten-Link. Keine Datenbank-Migration nötig.

# 4.4.4

- Feature: Bestätigungs-Einträge im Widerrufsformular können jetzt **als reiner Hinweistext ohne Checkbox** angezeigt werden. Neuer Schalter „Nur als Hinweistext anzeigen" je Eintrag (Karte „Bestätigungs-Checkboxen"). Ist er aktiv, erscheint der Text als schlichter Hinweis ohne Ankreuzfeld — es kann nichts angekreuzt werden und es wird nichts als Einwilligung gespeichert (rein informativ). „Pflichtfeld" ist für solche Einträge deaktiviert. Bestehende Einträge bleiben unverändert normale Checkboxen.

# 4.4.3

- Fix: Die Installation schlug auf **MySQL 8.4 (Oracle)** fehl mit „General error: 6125 Failed to add the foreign key constraint. Missing unique key for constraint 'fk.jga_revocation.order_id' in the referenced table 'order'". Ursache: Der Fremdschlüssel der Widerrufs-Tabelle verwies auf die Einzelspalte `order`(id), Shopwares `order`-Tabelle hat aber einen zusammengesetzten Primärschlüssel (id, version_id) — `order.id` ist allein nicht eindeutig. MySQL 8.4 erzwingt den SQL-Standard strenger als MySQL 8.0 / MariaDB und lehnt das ab. Der DB-Fremdschlüssel wird nun nicht mehr angelegt (Spalte `order_id` + Index bleiben; die Zuordnung läuft weiter über die Shopware-DAL).
- Bestehende Installationen werden dadurch nicht verändert (die Tabellen-Migration läuft nur bei der Erstinstallation). Eine zusätzliche, idempotente Migration entfernt den alten Fremdschlüssel, falls vorhanden — so bleiben Bestands-Shops konsistent und überstehen auch einen späteren Umzug auf MySQL 8.4 (z. B. Dump/Import). Es werden keine Daten verändert.

# 4.4.2

- Änderung: Der Schalter „Bestellnummer validieren" (Karte „Formular-Validierung", vormals „Vertragsnummer muss zu einer Bestellung führen") steuert jetzt zusätzlich, ob die Bestellnummer ein Pflichtfeld ist. Aktiv (Standard): Die Bestellnummer ist Pflicht und muss zu einer vorhandenen Bestellung passen (bisheriges Verhalten). Deaktiviert: Das Feld wird optional — Kunden dürfen es leer lassen, das Formular zeigt es als „(optional)", und die Eingabe wird nicht mehr abgeglichen.
- Verhaltensänderung: Bisher musste auch bei deaktiviertem Abgleich eine (ungeprüfte) Bestellnummer eingegeben werden. Ab jetzt ist das Feld bei deaktiviertem Schalter vollständig optional — betroffene Shops können dann Widerrufe ohne Bestellnummer erhalten.
- Umbenennung: Der Schalter „E-Mail muss zur Bestellung passen" heißt jetzt „E-Mail validieren".
- Technisch: Das Feld `orderNumber` der Widerrufs-Entität ist nicht mehr `Required` und erlaubt jetzt Leerstrings (`AllowEmptyString`), damit Widerrufe ohne Bestellnummer gespeichert werden können. Keine Datenbank-Migration nötig.

# 4.4.1

- Feature: Der Footer-Button „Bestellung widerrufen" wird jetzt standardmäßig auf allen Seiten angezeigt — auch im Warenkorb und Checkout. Bisher war er auf diesen Seiten fest ausgeblendet. Hintergrund: Mehrere Händler möchten den Widerruf dauerhaft sichtbar halten, statt ihn im Bestellvorgang zu verbergen.
- Neue Einstellung „Footer-Button im Checkout ausblenden" (Karte „Button-Platzierung", standardmäßig aus). Wer den Button im Bestellvorgang nicht zeigen möchte, aktiviert diese Option — dann verschwindet er auf den Seiten Warenkorb, Bestellbestätigung und Bestellabschluss. Wirkt nur, wenn „Button im Footer anzeigen" aktiv ist.

# 4.4.0

- Feature: Widerruf für Kundengruppen deaktivieren. In den Plugin-Einstellungen können Kundengruppen ausgewählt werden (neue Karte „Vom Widerruf ausgeschlossene Kundengruppen"), für die der Online-Widerruf vollständig deaktiviert ist: Eingeloggte Mitglieder sehen weder den Footer-Button noch den Konto-Menüpunkt, die Widerrufsseite zeigt ihnen einen Hinweis statt des Formulars, und Bestellungen von Kunden aus diesen Gruppen können auch über den Gastzugang (Bestellnummer + E-Mail) nicht widerrufen werden. Der Widerrufshinweis in der Bestellbestätigungs-E-Mail entfällt für diese Bestellungen ebenfalls. Gedacht für Geschäftskunden-Gruppen (B2B) ohne gesetzliches Widerrufsrecht.
- Wichtig: Anonyme Besucher sehen das Formular immer — Gäste laufen technisch unter der Standard-Kundengruppe des Verkaufskanals, und Verbrauchern muss die Widerrufsfunktion ab dem 19.06.2026 zur Verfügung stehen (§356a BGB). Die Absicherung für ausgeloggte B2B-Kunden erfolgt deshalb über die Kundengruppe des Bestellers beim Absenden. Der Hinweistext ist als Textbaustein anpassbar (jga-revocation.form.unavailableForCustomerGroup).

# 4.3.4

- Fix: Die Widerrufsbestätigung an den Kunden wurde auf Shops mit nicht laufendem Message-Queue-Worker nie versendet — still, ohne Fehlermeldung und ohne Logeintrag (die interne Benachrichtigung an den Shopbetreiber kam dagegen an). Hintergrund: Die Kundenmail läuft über den bei der Installation angelegten Flow-Builder-Flow. Dessen ausführbares Payload baut Shopware erst über den Flow-Indexer auf, der bisher über die Message-Queue angestoßen wurde; wird die Queue nicht abgearbeitet, bleibt das Payload leer und Shopware überspringt den Flow stumm. Behoben: Das Plugin führt den Flow-Indexer jetzt bei der Aktivierung und nach jedem Plugin-Update synchron aus — ohne Queue-Abhängigkeit. Bestehende betroffene Installationen werden durch dieses Update automatisch repariert, es ist kein manueller Eingriff nötig.
- Hinweis für Betreiber: Ein nicht laufender Message-Queue-Worker beeinträchtigt weit mehr als dieses Plugin (Thumbnails, Indexierung, geplante Aufgaben). Dieses Update entkoppelt den Widerrufs-Mailversand davon, ersetzt aber nicht die Einrichtung des Workers.

# 4.3.3

- Feature: Mehrere interne Benachrichtigungs-E-Mail-Adressen. Das Feld „Interne Benachrichtigungs-E-Mails" akzeptiert jetzt mehrere, durch Komma getrennte Adressen — jede erhält die interne Benachrichtigung über einen neuen Widerruf. Ungültige Einträge werden ignoriert (ein Tippfehler unterbindet nicht den Versand an die übrigen Adressen), und eine einzelne Adresse funktioniert unverändert weiter.

# 4.3.2

- Fix: Auf Seiten mit eingeblendetem Footer-Widerrufsbutton war die gesamte Seite horizontal scrollbar (~20px Overflow). Der Button war unnötig in eine Bootstrap-`.row`/`.col-12` verschachtelt; deren negative Gutter-Margin wurde vom Container (Theme-abhängig `padding: 0`) nicht ausgeglichen. Grid-Verschachtelung entfernt, Button direkt auf dem Container zentriert.

# 4.3.1

- Fix: Eine Bestellung konnte mehrfach widerrufen werden, sobald die automatische State-Machine-Transition auf „Widerrufen" ausblieb — sei es weil `autoTransitionToRevoked` deaktiviert war, der aktuelle Bestellstatus den Übergang nicht erlaubt (z. B. Connector-Setups wie plentyONE/Lenz, JTL, Pickware), oder weil die Bestellung ausgeschlossene Artikel enthielt. Hintergrund: der Schutz gegen Doppelwiderruf war ausschließlich an den Order-State gekoppelt; der Widerrufs-Tag wurde zwar gesetzt, aber nicht ausgewertet. Behoben: Der neue Helper `isOrderAlreadyRevoked()` prüft jetzt sowohl den State (`revoked`/`cancelled`) als auch das Vorhandensein des konfigurierten Widerrufs-Tags an der Bestellung. Mindestens eine der beiden Markierungen genügt, damit ein Folge-Widerruf mit „Für diese Bestellung wurde bereits ein Widerruf eingereicht" abgewiesen wird. Die Lösung respektiert manuelle Eingriffe des Shopbetreibers: nimmt der Admin Tag und Status zurück, ist die Bestellung wieder freigegeben.
- Help-Text der Plugin-Einstellung „Tag zur Bestellung hinzufügen" ergänzt: weist jetzt explizit darauf hin, dass mindestens eine der beiden Optionen (Statuswechsel oder Tag) aktiv sein sollte, damit das Plugin Doppelwiderrufe erkennen kann.
- Wichtig für Connector-Setups: das Plugin braucht keinen eigenen Status-Spalten-Workaround — die bestehenden Shopware-Mechanismen (State + Tag) genügen, sobald beide ausgewertet werden.

# 4.3.0

- Neue Konfigurations-Card „Formular-Validierung" mit zwei einzeln schaltbaren Prüfungen: „Vertragsnummer muss zu einer Bestellung führen" (Standard an) und „E-Mail muss zur Bestellung passen" (Standard an). Bei Deaktivierung der ersten Option werden Widerrufs-Einsendungen auch ohne zuordenbare Bestellung angenommen; das Plugin überspringt dann automatisch den Statuswechsel auf „Widerrufen" und das Setzen des Bestell-Tags, weil keine Bestellung zugeordnet werden kann. Die Mails (Kunde + Shopbetreiber) werden weiterhin verschickt, die „Widerrufene Artikel"-Sektion entfällt durch konditionales Rendering der Templates.
- Neue Konfigurations-Liste „Zusätzliche Suchfelder für die Vertragsnummer" in derselben Card. Der Shopbetreiber kann beliebige Order-Custom-Fields hinterlegen, gegen die die eingegebene Vertragsnummer zusätzlich zur nativen Shopware-Bestellnummer geprüft wird. Anwendungsfall: Connector-Setups (z. B. plentyONE/Lenz, JTL, Pickware, Xentral), bei denen der Endkunde nur die externe WaWi-/ERP-Bestell-ID kennt — diese landet typischerweise als JSON-Property in `order.custom_fields`. Jedes konfigurierte Suchfeld kann einzeln aktiviert/deaktiviert werden; der UI-Selektor zeigt alle im System registrierten Zusatzfelder zur Auswahl. Die Auswahl gleicher Felder mehrfach wird im Frontend wie im Backend verhindert.
- Eingabefeld im Storefront-Formular umbenannt von „Bestellnummer" zu „Vertragsnummer (Bestellnummer, Abonnentennummer, ...)" — passend zum Generalisierungsanspruch des Plugins (Verträge, Abonnements, Bestellungen).
- Backend-Lookup `findOrderForRevocation()` iteriert sequenziell: erst native `orderNumber`, dann jedes aktive Suchfeld via JSON-Path-Filter `customFields.<name>`. Bei numerischen Eingaben wird zusätzlich ein Integer-Cast probiert (`EqualsAnyFilter` mit String- und Int-Variante), damit Connector-IDs sowohl als String als auch als JSON-Integer matchen.
- Multi-Match-Schutz: wenn ein Suchfeld-Lookup mehr als eine Bestellung trifft (Connector-Bug, Migration-Duplikat), wirft das Plugin eine `AmbiguousRevocationLookupException` und zeigt dem Kunden eine klare Fehlermeldung statt stillschweigend die neueste Order zu widerrufen. Damit ist die Beweismittelfähigkeit auch bei Daten-Anomalien gewahrt.
- Defensive: wenn ein konfiguriertes Custom-Field aus der `custom_field`-Tabelle gelöscht wurde (z. B. Set umkonfiguriert), überspringt der Backend-Lookup den Eintrag still per try/catch — keine 500er. Die Admin-UI zeigt den verwaisten Eintrag rot als „Zusatzfeld nicht gefunden — bitte neu auswählen oder entfernen".
- Custom-Field-Werte als gewrappte Objekte (z. B. `{"value": "X"}`) werden bewusst nicht unterstützt — der Lookup erwartet skalare Werte direkt in der JSON-Spalte. Bei einem nicht-skalaren Wert fällt der Lookup graceful auf „nicht gefunden" zurück.

# 4.2.5

- Fix: Eingeloggte Kunden konnten ihre eigenen Bestellungen nicht widerrufen, wenn sie nach dem Bestellzeitpunkt ihre Account-E-Mail geändert hatten. Hintergrund: `order_customer` ist in Shopware ein Snapshot zum Bestellzeitpunkt — die dort gespeicherte E-Mail wird bei Account-E-Mail-Änderungen NICHT mit aktualisiert (bewusst, aus Beweismittel-Gründen). Das Plugin filterte in `findOrderByNumberAndEmail()` aber auf diese Snapshot-E-Mail. Folge: Der Kunde sah die Order zwar im Account in der Widerrufs-Liste (denn dort wurde via `customerId` gefiltert), beim Submit gab es jedoch „Bestellung nicht gefunden". Behoben: Bei eingeloggten Kunden wird auf `orderCustomer.customerId` gefiltert, bei Gästen weiterhin auf E-Mail.

# 4.2.4

- Fix: `UnmappedFieldException: Field "id" in entity "state_machine_history" was not found` beim Aufruf von `/revocation` als eingeloggter Kunde, sobald mindestens eine widerrufbare Bestellung versendete Lieferungen (Delivery-State `shipped`) hatte. In `getRevocationStartDate()` wurden zwei nicht existierende Filter-Pfade verwendet:
  - `entityId.id` → das Feld `entityId` existiert in `StateMachineHistoryDefinition` gar nicht. Korrekter Feldname: `referencedId` (DB-Spalte `referenced_id`).
  - `toStateMachineStateId` → existiert nicht. Korrekter Feldname: `toStateId` (DB-Spalte `to_state_id`); `toStateMachineState` ist nur der Name der ManyToOne-Association.
  - Zusätzlich wird jetzt auf `referencedVersionId = LIVE_VERSION` gefiltert, damit Versions-Drafts nicht versehentlich gematched werden.
- Defensive: Die History-Query ist jetzt in `try { … } catch (\Throwable)` gewrappt. Schlägt sie aus irgendeinem Grund künftig erneut fehl (z. B. Shopware-Feldumbenennung in einer späteren 6.x-Version), fällt das Plugin still auf `orderDate` zurück statt einen 500er zu produzieren — schlimmstenfalls ein konservativ engeres Widerrufsfenster, aber die Seite bleibt funktional.
- Magic-String `'order_delivery'` durch `OrderDeliveryDefinition::ENTITY_NAME` ersetzt — bricht automatisch beim Compile, falls Shopware den Entity-Namen je umbenennt.
- Bug existierte in der 4.x-Linie seit dem initialen SW-6.7-Release; blieb unentdeckt, weil er nur bei Default-Config `revocationPeriodStart=shippingDate` UND einer Order mit mindestens einer `shipped`-Delivery auftritt. Identischer Bug parallel in der 3.x-Linie (SW 6.6), dort in 3.3.4 gefixt.

# 4.2.3

- Fix: Der Widerrufsbutton im Footer wird jetzt auf allen Checkout-Seiten ausgeblendet, nicht nur auf Warenkorb, Bestellabschluss und Bestätigungsseite. Zuvor war der Button auf `/checkout/register` (Versandinformationen / Gastbestellung) sichtbar, wodurch ein versehentlicher Klick mitten in der laufenden Bestellung den Kunden aus dem Checkout warf, obwohl noch keine Bestellung existierte. Die Route-Prüfung im Footer-Template (`@Storefront/storefront/layout/footer/footer.html.twig`) wurde von einer Whitelist einzelner Checkout-Routen auf `activeRoute starts with 'frontend.checkout.'` umgestellt — damit sind alle aktuellen und zukünftigen Checkout-Subrouten automatisch abgedeckt. CMS-Element und Konto-Menü-Link sind nicht betroffen.

# 4.2.2

- Fix: Das Widerrufsformular nutzt jetzt das Shopware-Standard-Pattern für Captcha-Formulare. Das Form-Element trägt `data-form-handler="true"`, wodurch Shopwares eingebautes `FormHandler`-Plugin die native HTML5-Validierung deaktiviert (`<form novalidate>` zur Laufzeit) und die Validation selbst übernimmt. Damit entfallen Browser-Console-Errors wie „An invalid form control with name='shopware_basic_captcha_check' is not focusable" bzw. `_grecaptcha_v3`, die bei aktivem BasicCaptcha oder reCAPTCHA v3 das Form-Submit blockieren konnten. Das defensive JavaScript zum Entfernen des `required`-Attributs auf den versteckten Captcha-Eingaben sowie das `onsubmit`-Inline-Skript zum Disable des Submit-Buttons sind nicht mehr nötig — sauberere Form-Architektur, ohne Workaround.
- Fix: Bei BasicCaptcha-Failure produzierte der Shopware-Server eine `MethodNotAllowedException` (HTTP 500). Hintergrund: Shopwares `ErrorController::onCaptchaFailure()` ruft bei nicht-„breaking"-Captchas (BasicCaptcha ist der einzige Built-in mit `shouldBreak()=false`) ein `forwardToRoute($request->get('_route'))` auf, dessen Forward-Mechanismus die Ziel-Route mit method=GET matched — unsere Submit-Route ist aber POST-only. Der Plugin-eigene `CaptchaExceptionSubscriber` fängt jetzt zusätzlich `Symfony\Component\Routing\Exception\MethodNotAllowedException` ab und behandelt das identisch wie eine reguläre `CaptchaException::INVALID_CAPTCHA_ERROR`: Redirect zur Form mit Flash-Error und vorbefüllten Daten als Query-Params.
- Neue Plugin-Einstellungen-Card „Vom Widerruf ausgeschlossene Produkte": Shopbetreiber können Produkte vom Widerruf ausschließen (z. B. Maßanfertigungen, versiegelte Hygieneartikel, leicht verderbliche Waren).
  - **Custom Field am Produkt**: Plugin legt automatisch das Feld „Vom Widerruf ausgeschlossen" (Boolean) im neuen Custom-Field-Set „Widerruf" an. Direkt am Produkt setzbar, in der Produktliste filterbar, Bulk-Edit-tauglich.
  - **Dynamische Produktgruppen**: Multi-Select für Product Streams. Produkte, die unter mindestens einen ausgewählten Stream fallen, sind ausgeschlossen. Beide Mechanismen mit ODER-Verknüpfung kombinierbar.
- Widerrufsformular zeigt jetzt für die ausgewählte Bestellung an, welche Artikel widerrufbar und welche ausgeschlossen sind. Bei Bestellungen, die ausschließlich ausgeschlossene Artikel enthalten, wird das Formular durch einen Hinweistext mit Support-Kontakt ersetzt (mehrsprachig pflegbar in der Plugin-Konfiguration, Standardwert vorbefüllt).
- Bei Bestellungen mit ausgeschlossenen Artikeln (Teilwiderruf) wird der automatische Status-Übergang auf „Widerrufen" übersprungen — die Bestellung erhält stattdessen zwangsweise den Widerrufs-Tag „Widerruf-Prüfung", damit der Shopbetreiber sie in der Bestellliste filtern kann und manuell entscheidet, welche Positionen erstattet werden. Die Setting „autoTransitionToRevoked" greift nur noch bei Bestellungen ohne ausgeschlossene Artikel.
- Die Widerrufs-Eingangsmail an den Shopbetreiber listet jetzt widerrufene und ausgeschlossene Artikel getrennt auf und weist beim Teilwiderruf explizit auf den gesetzten Tag „Widerruf-Prüfung" hin. Eigene Mail-Template-Anpassungen werden respektiert (nur unveränderte System-Default-Templates werden migriert). Neue Variablen im Mail-Kontext: `templateData.revocableLineItems`, `templateData.excludedLineItems`, `templateData.hasExcludedLineItems`.
- Datenmodell-Erweiterung: `jga_revocation.revocable_line_items` und `jga_revocation.excluded_line_items` (JSON) speichern den Snapshot beider Listen zum Zeitpunkt des Widerrufs — beweismittelfähig auch bei späterer Änderung der Plugin-Konfiguration.
- Performance: `ExcludedLineItemDetector` führt bei N konfigurierten Streams nur eine einzige Datenbank-Abfrage durch (OR-verknüpfter `MultiFilter` statt Schleife).
- Hinweis: Der Ausschluss vom Widerruf muss zusätzlich in der Widerrufsbelehrung VOR Vertragsschluss kommuniziert werden (§ 312g BGB) — diese Pflicht liegt beim Shopbetreiber. Das Plugin stellt nur das Werkzeug bereit.

# 4.2.1

- Fix: Auf Shopware 6.7.x schlug der Aufruf der Widerrufsseite (`/revocation`) mit einem 500er-Fehler fehl (`Attempted to call an undefined method named "setTwig"`). Ursache: Die Service-Definition rief in `services.xml` `setTwig` als Setter-Injection auf, aber diese Methode wurde in Shopware 6.7 aus dem `StorefrontController` entfernt. Twig wird in 6.7 stattdessen über `getSubscribedServices` aus dem Service-Container geholt — kein Setter mehr nötig. Der `setTwig`-Aufruf wurde aus der Service-Definition entfernt; das Widerrufsformular funktioniert auf 6.7 wieder.
- Fix: `CaptchaExceptionSubscriber` referenzierte die nicht mehr existierende Klasse `Shopware\Storefront\Framework\Captcha\Exception\CaptchaInvalidException` (Static-Analysis-Warning `class.notFound`). Diese wurde in Shopware 6.7 durch die generische `Shopware\Storefront\Framework\Captcha\CaptchaException` mit `INVALID_CAPTCHA_ERROR`-Errorcode ersetzt. Der Subscriber prüft jetzt `instanceof CaptchaException && getErrorCode() === CaptchaException::INVALID_CAPTCHA_ERROR`. Verhalten unverändert: Captcha-Fehler auf der Widerrufs-Submit-Route führen weiterhin zu einer Flash-Fehlermeldung mit Redirect zurück zum Formular.
- Fix: Bei einem Update auf eine neuere Version erschienen die Plugin-Konfigurations-Cards (Bestätigungs-Checkboxen, Hinweis-Box) manchmal leer. Ursache: der Cache-Buster der Admin-Bundle-URL wurde durch den Update-Pfad nicht zuverlässig hochgezählt, der Browser lieferte das alte Bundle aus dem Cache. Das Plugin schreibt jetzt im `postUpdate`-Hook das Bundle-Manifest defensiv neu, wodurch der Cache-Buster erhöht und das aktuelle Bundle vom Browser geladen wird.

# 4.2.0

- Bestätigungs-Checkbox-Editor: beim Bearbeiten eines bestehenden Eintrags wird automatisch die Sprache angezeigt, in der der Eintrag bereits Inhalt hat (bevorzugt Deutsch, dann Englisch, sonst die erste gepflegte Locale). Beim Anlegen eines neuen Eintrags und im leeren Edit-Fall wird Deutsch als Standard-Sprache vorausgewählt, falls im Shop verfügbar.
- Konfigurierbare Bestätigungs-Checkboxen im Widerrufsformular (Plugin-Einstellungen → „Bestätigungs-Checkboxen im Widerrufsformular"): Mehrsprachig pro Locale, WYSIWYG-Editor mit Magic-CMS-Links (`cms://privacy`, `cms://tos`, `cms://imprint`), Pflicht-/Aktiv-Schalter pro Eintrag. Datenschutz-Hinweis als Default-Eintrag. Die zum Widerrufszeitpunkt akzeptierten Bestätigungen werden inklusive Text-Snapshot in `jga_revocation.accepted_consents` (JSON) persistiert und in der Bestätigungsmail aufgeführt — beweismittelfähig.
- Neue Plugin-Einstellungen-Card „Aktion bei Widerruf-Eingang": Toggle „Bestellstatus automatisch auf ‚Widerrufen' setzen" (Standard an) und Toggle „Tag zur Bestellung hinzufügen" (Standard aus, mit vorausgewähltem Default-Tag „Widerruf-Prüfung" — Admin aktiviert nach Bedarf). Beide Aktionen unabhängig kombinierbar — z. B. nur Tag setzen für manuelle Prüfung bei Maßanfertigungen.
- Hinweis-Box mit kopierbarer Direkt-URL der Widerrufsseite in der Plugin-Einstellungen-Card „Button-Platzierung". Klar getrennt von den Footer/Konto-Menü-Toggles, die nur als zusätzliche Platzierung dienen.
- Captcha-Fehler werden nicht mehr als 403-Page angezeigt: bei Captcha-Validierungsfehlern wird der Nutzer mit einer Flash-Fehlermeldung zur Form zurückgeleitet, Form-Eingaben bleiben erhalten.
- Frontend-Fix: Captcha-Hidden-Input (`_grecaptcha_v3`) blockiert den Submit nicht mehr durch das `required`-Attribut bei `display:none`-Element (HTML5-„not focusable"-Fehler).

# 4.1.0

- Captcha-Validierung auf der Widerrufs-Form auf Shopwares zentrale Pipeline (`_captcha`-Annotation + `CaptchaRouteListener`) umgestellt. Der manuelle Captcha-Loop im Controller (`iterable<AbstractCaptcha>`) entfällt; die Captcha-Liste wird jetzt direkt vom Framework verwaltet. Verhalten unverändert: alle aktiven Captcha-Typen (Honeypot, Basic Captcha, reCAPTCHA v2/v3) werden weiterhin geprüft.

# 4.0.2

- Fix: Header-Navigation und Footer-Spalten werden auf den Widerrufs-Seiten (`/revocation`, Bestätigung, Erfolg) wieder vollständig dargestellt. Custom-Themes (z. B. ATMOS, STRATUS, GRAVITY etc.), die auf `page.header.*` und `page.footer.*` zugreifen, hatten zuvor leere Werte erhalten, da der Controller kein `Page`-Objekt geladen hat.

# 4.0.1

- Kontrast des Widerrufen-Status-Badges in der Bestellübersicht verbessert
- Styling des Widerrufsbuttons im Kontextmenü der Bestellliste an natives Shopware-Dropdown angeglichen
- Aktualisierte Produktbeschreibung

# 4.0.0

- Initiale Version für Shopware 6.7
- Unterstützung für alle Shopware Captcha-Typen im Widerrufsformular (Honeypot, BasicCaptcha, Google reCAPTCHA v2/v3)
