Magento 2
Läuft in einem Shop, den wir selbst betreiben, und die ganze Kette ist von aussen nachgemessen — vom Warenkorb bis zum signierten Anfrageobjekt an die Wallet. Wenn etwas klemmt, klemmt es an Ihrem Aufbau, nicht am Paket.
Als Datei: README.md · PRUEFPLAN.md
Einbau
EdelVerify für Magento 2 — Altersprüfung mit der Schweizer E-ID
Sperrt den Bestellabschluss für altersbeschränkte Artikel, bis über die staatliche Schweizer E-ID (swiyu) bestätigt ist, dass die Kundin oder der Kunde die verlangte Altersgrenze erfüllt. Der Shop erfährt genau diese eine Angabe — keinen Namen, kein Geburtsdatum, keine Ausweiskopie.
Die Grenze ist einstellbar: 16 oder 18, je Store-View als Vorgabe und je Kategorie als Ausnahme. Bei gemischtem Warenkorb gilt die strengste. Ein Store-View, der nur die Vorgabe setzt, verhält sich exakt wie vor der zweiten Schwelle.
Gegen den Quelltext von Magento Open Source 2.4.7-p3 abgeglichen — jede Klasse, jede Signatur und jeder Ereignisname ist dort nachgeschlagen. Gelaufen ist das Modul noch in keiner Magento-Instanz. Was das offenlässt, steht vollständig in PRUEFPLAN.md; arbeiten Sie diesen Plan ab, bevor Sie das Modul produktiv einsetzen.
Wie es funktioniert
Browser Magento-Server verify.edelbyte.ch
─────── ────────────── ──────────────────
Knopf «Alter bestätigen»
→ gate.js öffnet das Fenster ──────────────────────────→ POST /verifications
← QR-Code, Deeplink ←──────────────────────────────── (mit pk_…)
swiyu-App, Wallet bestätigt
→ verification_id ──→ POST /edelverify/confirm/index
holt den Nachweis ───────────→ GET /verifications/:id
(mit sk_…)
legt `av_…` in die Sitzung
Bestellung abschicken ──→ CartManagementInterface::placeOrder()
löst den Nachweis ein ───────→ POST /proofs/verify
mit min_age = was die WARE verlangt
valid ? weiter : CouldNotSaveExceptionDrei Punkte, auf die es ankommt:
- Der Browser sieht den geheimen Schlüssel nie. Er meldet nur die Kennung der Prüfung. Den Nachweis holt der Shop-Server selbst.
- Der Riegel sitzt serverseitig. Wer den Knopf mit den Entwicklerwerkzeugen entfernt, kommt trotzdem nicht durch.
- Fail-closed überall. Kein Nachweis, kein Netz, kein Schlüssel, unklare Antwort → Bestellung abgewiesen. Nie freigegeben. Bei einer Altersprüfung ist die sichere Richtung die unbequeme.
Was Sie vorher brauchen
Das Paket: https://verify.edelbyte.ch/v1/magento/edelverify-magento.zip — darin liegt der Baum EdelByte/EdelVerify samt dieser Anleitung und dem Prüfplan. Genau so gehört er nach app/code/.
Ein Schlüsselpaar von uns. Einen Selbstbedienungszugang gibt es heute nicht: Es gibt kein Anmeldeformular, in dem Sie sich ein Konto anlegen und Schlüssel abholen. Wir legen Ihren Shop von Hand an und schicken Ihnen den öffentlichen (pk_test_…) und den geheimen (sk_test_…). Im selben Zug hinterlegen wir die erlaubten Herkunftsadressen — auch das ist nichts, was Sie selbst einstellen können.
Der Weg dorthin ist ein kurzes Gespräch:
- Warteliste: https://verify.edelbyte.ch/#warteliste
- E-Mail: info@edelbyte.ch
- Telefon: 044 500 25 04
Sagen Sie uns dabei die Basis-URLs aller Store-Views (mit und ohne www, dazu das Testsystem) und die Altersgrenze, die Ihre Ware verlangt.
Was es kostet: heute nichts — weil heute nichts verkauft wird. Es gibt keinen Preis, kein Abonnement und keine Rechnung. Wir nennen bewusst auch keinen Betrag «ab», solange die produktive E-ID nicht steht. Was es gibt, ist ein Pilotgespräch, Testschlüssel zum Bauen und ein Platz auf der Warteliste. Über Konditionen sprechen wir, bevor Sie scharf schalten — nicht danach.
Die vollständige technische Anleitung — Schnittstelle, Fehlerschlüssel, Webhook, Testausgänge — steht unter https://verify.edelbyte.ch/entwickler
Installation
Variante A — Dateien kopieren
cp -r EdelByte <magento-root>/app/code/
cd <magento-root>
bin/magento module:enable EdelByte_EdelVerify
bin/magento setup:upgrade
bin/magento setup:di:compile # nur im Produktionsmodus nötig
bin/magento setup:static-content:deploy de_CH fr_CH it_CH en_US
bin/magento cache:flushVariante B — über Composer
composer config repositories.edelverify path ./EdelByte/EdelVerify
composer require edelbyte/module-edelverify:^1.0
bin/magento setup:upgradesetup:upgrade legt fünf Spalten an sales_order an (siehe *Beleg an der Bestellung*). Ohne diesen Schritt läuft der Riegel, aber der Beleg wird still verworfen.
Konfiguration
Stores → Konfiguration → Verkäufe → EdelVerify — Altersprüfung
| Feld | Bedeutung |
|---|---|
| Altersprüfung aktiv | Der Hauptschalter. Vorgabe: Nein. |
| Adresse des Dienstes | https://verify.edelbyte.ch. Nur ändern, wenn Sie EdelVerify selbst betreiben (siehe *Content-Security-Policy*). |
| Öffentlicher Schlüssel | pk_live_… / pk_test_…. Steht im Quelltext der Seite — das ist so vorgesehen. |
| Geheimer Schlüssel | sk_live_… / sk_test_…. Wird verschlüsselt abgelegt (type="obscure" + Magento\Config\Model\Config\Backend\Encrypted). Leer lassen behält den bestehenden Wert. |
| Altersgrenze (Vorgabe) | ab 16 oder ab 18. Vorgabe: ab 18. Gilt, wo keine Kategorie etwas anderes sagt. |
| Betroffene Kategorien | Kategorie-IDs, mit Komma getrennt, mit optionaler eigener Grenze. Leer heisst: der ganze Shop. |
Alle Felder gelten je Store-View. Ein zweisprachiger Shop mit zwei Sortimenten kann die Prüfung also in einem Store-View führen und im anderen nicht.
Beide Schlüssel bekommen Sie von uns (siehe *Was Sie vorher brauchen*); fangen Sie mit den Testschlüsseln an. Für die erlaubte Herkunft gibt es hier kein Feld: Die Liste liegt bei uns. Schicken Sie uns die Basis-URLs Ihrer Store-Views an info@edelbyte.ch — solange für Ihren Shop keine darin steht, darf jede beliebige Seite Prüfungen auf Ihren öffentlichen Schlüssel starten.
Zu den Kategorien
Es werden IDs eingetragen, keine URL-Schlüssel. Die ID einer Kategorie steht im Kategoriebaum hinter dem Namen. Grund: Die ID überlebt eine Umbenennung und einen Sprachwechsel, der URL-Schlüssel nicht — und eine Altersprüfung, die nach dem Umbenennen einer Kategorie stillschweigend aufhört zu greifen, ist schlimmer als keine.
Unterkategorien werden nicht mitgeprüft. Wer «Tabak» einträgt und die Ware in der Unterkategorie «Zigarren» führt, prüft nichts. Tragen Sie die Unterkategorien einzeln ein, oder lassen Sie das Feld leer.
Zur Altersgrenze
Die Vorgabe gilt überall dort, wo keine Kategorie etwas anderes sagt. Eine eigene Grenze hängen Sie mit Doppelpunkt an die ID:
17:16,18:16,12:18,15
Kategorie 17 und 18 verlangen 16, Kategorie 12 verlangt 18, Kategorie 15 die Vorgabe. Ein Eintrag ohne Zusatz verhält sich wie vor der zweiten Schwelle — jede bestehende Einstellung bleibt also Zeichen für Zeichen gültig.
Ein Zusatz, den der Dienst nicht kennt (:17), wird auf 18 angehoben, nicht auf 16 gesenkt. Dasselbe gilt für einen unbekannten Wert der Vorgabe: Im Zweifel prüft das Modul strenger, nicht milder.
Bei gemischtem Warenkorb gilt die strengste Grenze. Ein Sixpack neben einer Flasche Gin macht den ganzen Warenkorb zu einem 18er-Warenkorb. Liegt ein Artikel in zwei eingetragenen Kategorien mit verschiedenen Grenzen, gilt ebenfalls die strengere — eine Lockerung durch Doppelzuordnung fiele niemandem auf.
Ein Nachweis ab 18 löst auch Ware ab 16 ein; umgekehrt nicht. Wer sich für Bier ausgewiesen hat und dann Spirituosen dazulegt, wird erneut gefragt.
Content-Security-Policy
etc/csp_whitelist.xml gibt verify.edelbyte.ch für script-src, connect-src und img-src frei. Wer eine andere Adresse einträgt, muss sie dort ergänzen — eine CSP lässt sich nicht aus einer Einstellung ableiten, die erst zur Laufzeit gelesen wird. Ohne die Freigabe erscheint in einem Shop mit scharf gestellter CSP kein Prüffenster, und die Ursache steht nur in der Browser-Konsole.
Beleg an der Bestellung
Ein Händler kauft eine Altersprüfung wegen der Nachweispflicht. Steht an der Bestellung nichts, kann er bei einem Testkauf oder einer Beanstandung nicht belegen, dass diese Bestellung geprüft war.
Geschrieben wird beides:
- Ein Eintrag im Bestellverlauf — sichtbar unter *Verkäufe → Bestellungen → <Bestellung> → Kommentare*. Das ist der Teil, den ein Sachbearbeiter tatsächlich sieht.
- Fünf Spalten in `sales_order` für Auswertungen und Exporte:
edelverify_verified(ja/nicht-noetig),edelverify_checked_at,edelverify_min_age,edelverify_mode,edelverify_ref.
edelverify_min_age trägt die tatsächlich durchgesetzte Grenze — 16 bei einem Bier-Warenkorb, 18 bei Spirituosen. Nicht pauschal 18: Genau diese Zahl ist im Streitfall der Beleg. Lautete der vorgelegte Nachweis auf eine höhere Grenze, steht das zusätzlich im Verlaufseintrag.
Nie geschrieben werden: der Nachweis selbst, ein Geburtsdatum, ein Name. edelverify_ref trägt acht Zeichen der Prüfkennung — genug, um die Prüfung im EdelVerify-Admin wiederzufinden, zu wenig, um damit einen Nachweis abzuholen oder eine Person zu bestimmen.
Was das Modul bewusst nicht mitbringt: keine Spalte in der Bestellliste und keinen eigenen Kasten im Bestellformular. Beides hiesse, in sales_order_grid zu schreiben und sales_order_view.xml umzubauen — viel Fläche für einen Beleg, der im Verlauf ohnehin steht. Wer die Spalte in der Liste braucht, findet in PRUEFPLAN.md den Punkt dazu.
Themes
Luma
Läuft ohne Zutun. Das Modul hängt sich in checkout_cart_index und checkout_index_index in den content-Container.
Das eigene JavaScript des Moduls ist absichtlich winzig und benutzt kein RequireJS und kein Knockout: zwei Ereignis-Zuhörer in reinem Browser-JavaScript. gate.js selbst baut sein Fenster in einen Shadow DOM und ist von der Theme-Welt getrennt.
Die Farben liegen als CSS-Variablen auf .edelverify-banner. Ein Theme braucht zum Umfärben eine Zeile:
.edelverify-banner { --ev-knopf: #1E3B32; --ev-grund: #FFFDF9; }Hyvä
Das ViewModel EdelByte\EdelVerify\ViewModel\Gate ist theme-unabhängig und lässt sich unverändert weiterverwenden. Nötig sind:
- Ein Layout-XML im Hyvä-Theme, das denselben Block mit einem eigenen Template einhängt (Hyvä benutzt andere Container-Namen als Luma).
- Ein Template, das den Knopf mit
data-edelverifyausgibt und das Script-Tag setzt. Das mitgeliefertegate.phtmllässt sich fast unverändert übernehmen — es braucht nur andere Klassennamen (Tailwind statt der mitgelieferten CSS-Datei). - Kein Alpine-Code. Die zwei Zuhörer im Template reichen;
x-dataist hier nicht nötig.
Die CSS-Datei view/frontend/web/css/edelverify.css sollte ein Hyvä-Theme nicht laden — sie würde nur mit dem Tailwind-Build kollidieren.
PWA Studio / entkoppelte Frontends
Der Riegel greift, der Weg zum Nachweis nicht. Das ist Absicht in der sicheren Richtung, aber ein Frontend-Team muss zwei Dinge nachliefern:
- Das Fenster.
gate.jsin die React-Anwendung laden und den Erfolg abgreifen (Ereignisedelverify:verifiedamdocument). - Einen Weg, den Nachweis abzulegen.
/edelverify/confirm/indexsetzt eine PHP-Sitzung voraus — die gibt es in einem token-basierten Frontend nicht. Nötig ist eine eigene Schnittstelle, die statt der Sitzung den Warenkorb als Ablage benutzt: - eine Spalte
edelverify_proofan der Tabellequote(etc/db_schema.xml, genau wie es Magento_Persistent mitis_persistentmacht), - ein
Magento\Framework\Webapi-Endpunkt, dercart_idundverification_identgegennimmt, ProofStoreliest dann den Warenkorb statt der Sitzung.
Das ist überschaubar, aber es ist Arbeit, und dieses Modul erledigt sie nicht. Solange sie nicht erledigt ist, findet der Riegel in einem token-basierten Checkout nie einen Nachweis und weist jede betroffene Bestellung ab.
Was ehrlicherweise ungeprüft ist
Dieses Modul ist gegen den Quellbaum von Magento 2.4.7-p3 geschrieben: Jede Klasse, jede Methode, jeder Ereignisname und jeder Schemaverweis wurde dort nachgeschlagen. Alle XML-Dateien sind gegen die echten XSD-Dateien aus dem Quellbaum validiert, alle PHP-Dateien mit php -l geprüft.
Es lief noch nie in einer laufenden Magento-Instanz. Nicht geprüft sind deshalb unter anderem:
- ob
setup:di:compilesauber durchläuft, - ob der Plugin-Punkt in jeder Zahlungsart wirklich greift,
- ob die Layout-Einhängung in jedem Theme an der gedachten Stelle landet,
- ob die Sitzung in einem REST-Kassenaufruf tatsächlich verfügbar ist,
- ob die neuen Spalten in
sales_orderohne Konflikt angelegt werden.
PRUEFPLAN.md listet jeden dieser Punkte einzeln auf, mit dem Befehl, mit dem er sich in fünf Minuten klären lässt. Wer dieses Modul produktiv einsetzt, sollte diesen Plan zuerst abarbeiten.
Deinstallation
bin/magento module:disable EdelByte_EdelVerify
composer remove edelbyte/module-edelverify # oder rm -rf app/code/EdelByte
bin/magento setup:upgradesetup:upgrade entfernt die fünf Spalten aus sales_order wieder — dafür ist etc/db_schema_whitelist.json da. Der Beleg an bestehenden Bestellungen geht damit verloren. Wer ihn aufbewahren muss, exportiert vorher:
SELECT increment_id, edelverify_verified, edelverify_checked_at,
edelverify_min_age, edelverify_mode, edelverify_ref
FROM sales_order
WHERE edelverify_verified IS NOT NULL;Die Einträge im Bestellverlauf bleiben in jedem Fall erhalten — sie liegen in sales_order_status_history und gehören nicht diesem Modul.
Übersetzungen
i18n/de_CH.csv, fr_CH.csv, it_CH.csv, en_US.csv.
Abweichend von der Magento-Gewohnheit sind die Quelltexte deutsch, nicht englisch: Das Modul richtet sich an Schweizer Händler, und ein deutscher Quelltext ist hier der, den am ehesten jemand liest. en_US.csv ist damit eine Übersetzung wie jede andere.
Lizenz und Kontakt
Copyright © EdelByte — https://edelbyte.ch Dienst und Schlüssel: https://verify.edelbyte.ch
Prüfplan
Damit nehmen Sie den Einbau selbst ab, ohne uns zu fragen. Geht ein Punkt nicht durch, schicken Sie ihn uns mit der Nummer — dann wissen wir sofort, wo wir suchen.
Prüfplan
Was sich ohne laufende Magento-Instanz nicht klären lässt — je mit dem Befehl, mit dem es sich klären lässt.
Das Modul ist vollständig gegen den Quellbaum von Magento 2.4.7-p3 geschrieben. Alle XML-Dateien sind gegen die echten XSD-Dateien validiert, alle PHP-Dateien mit php -l geprüft. Beides sagt nichts darüber, ob es läuft.
Reihenfolge ist Absicht: 1–4 sind Abbruchkriterien, 5–12 sind Verhalten, 13–16 sind Feinheiten.
Vorbereitung
cd <magento-root>
bin/magento deploy:mode:set developer
cp -r <dieses-verzeichnis>/../../EdelByte app/code/
bin/magento module:enable EdelByte_EdelVerify1 — Lässt sich das Modul überhaupt kompilieren?
Unklar: Ob der Objektmanager alle Abhängigkeiten auflösen kann. Konstruktoren mit readonly in *promoted properties* sind PHP-8.1-Syntax; Magentos Code-Generator (Interceptor, Proxy, Factory) kommt damit erfahrungsgemäss zurecht, aber «erfahrungsgemäss» ist kein Beleg.
bin/magento setup:di:compileErwartet: Fehlerfrei. Insbesondere muss generated/code/EdelByte/EdelVerify/… entstehen und generated/code/Magento/Quote/Model/QuoteManagement/Interceptor.php unser Plugin enthalten:
grep -c "edelbyte_edelverify" generated/metadata/*.phpWenn es scheitert: Zuerst readonly aus den Konstruktoren nehmen und erneut kompilieren — das grenzt Syntax gegen Verdrahtung ab.
2 — Greift das Plugin am erwarteten Punkt?
Unklar: Ob Magento ein Plugin, das auf Magento\Quote\Api\CartManagementInterface angemeldet ist, tatsächlich auf Magento\Quote\Model\QuoteManagement überträgt. Der Kern macht das in app/code/Magento/Checkout/etc/webapi_rest/di.xml (GuestCartManagementInterface), also müsste es tragen — nachgewiesen ist es damit aber nur für den webapi_rest-Bereich, nicht global.
bin/magento dev:di:info 'Magento\Quote\Api\CartManagementInterface'Erwartet: Unter *Plugins* steht EdelByte\EdelVerify\Plugin\BlockOrderWithoutProof mit beforePlaceOrder.
Wenn es fehlt: Die Anmeldung von etc/di.xml nach etc/frontend/di.xml und etc/webapi_rest/di.xml und etc/graphql/di.xml duplizieren. Der Riegel muss in allen dreien stehen — in nur einem wäre er keiner.
3 — Werden die Spalten in `sales_order` angelegt?
Unklar: Ob sales_order in der jeweiligen Installation überhaupt schreibbar ist. Bei aufgeteilten Datenbanken (split database, Adobe Commerce) liegt sales auf einer eigenen Verbindung; resource="sales" in etc/db_schema.xml trägt dem Rechnung, ist hier aber nicht erprobt.
bin/magento setup:upgrade
bin/magento setup:db:status
mysql -e "SHOW COLUMNS FROM sales_order LIKE 'edelverify%'" <db>Erwartet: Fünf Zeilen.
4 — Ist der geheime Schlüssel wirklich verschlüsselt?
Unklar: Ob type="obscure" + Magento\Config\Model\Config\Backend\Encrypted in dieser Kombination greift. Der Kern nutzt sie (Magento_NewRelicReporting, Magento_Usps, Magento_Paypal), aber nur der Blick in die Datenbank beweist es.
Schlüssel im Backend eintragen, dann:
mysql -e "SELECT value FROM core_config_data WHERE path='edelverify/general/secret_key'" <db>Erwartet: Etwas wie 0:3:abc… — nicht sk_live_….
Und die Gegenprobe, dass das Entschlüsseln beim Lesen klappt:
bin/magento config:show edelverify/general/secret_keyErwartet: Magentos ValueProcessor (der Encrypted kennt) gibt hier ****** oder den Klartext aus — in keinem Fall den Geheimtext.
Wenn im Klartext gespeichert: Sofort stoppen. Ein Klartext-Schlüssel in core_config_data landet in jedem Datenbank-Backup.
5 — Blockiert der Riegel wirklich?
Unklar: Ob CouldNotSaveException in jeder Kasse als lesbare Meldung ankommt oder irgendwo zu einem 500er wird.
Vorbereitung: Prüfung aktiv, beide Schlüssel gesetzt, Kategorie eingetragen, altersbeschränkter Artikel im Warenkorb, keine Prüfung durchgeführt.
Dann in der Luma-Kasse auf «Bestellung aufgeben».
Erwartet: Die Meldung «Bitte bestätigen Sie zuerst Ihr Alter mit der E-ID …» erscheint, es entsteht keine Bestellung.
mysql -e "SELECT COUNT(*) FROM sales_order WHERE created_at > NOW() - INTERVAL 5 MINUTE" <db>Erwartet: 0.
6 — Blockiert er auch an der REST-Schnittstelle?
Das ist der Punkt, an dem sich entscheidet, ob der gewählte Plugin-Ort etwas taugt. Ein Frontend-Riegel liesse sich hier umgehen.
TOKEN=$(curl -s -X POST '<base-url>/rest/V1/integration/customer/token' \
-H 'Content-Type: application/json' \
-d '{"username":"kunde@example.ch","password":"…"}' | tr -d '"')
curl -i -X PUT '<base-url>/rest/V1/carts/mine/order' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"paymentMethod":{"method":"checkmo"}}'Erwartet: HTTP 400 mit unserer Meldung im Rumpf. Nicht 200 mit einer Bestellnummer.
7 — Und in der Gastkasse?
Die Gastkasse geht über GuestCartManagementInterface, das laut Quelltext an CartManagementInterface weiterreicht. Das ist belegt, aber Weiterreichen und Plugin-Auslösung sind zwei verschiedene Dinge — ein Plugin auf dem inneren Interface feuert nur, wenn der Aufruf wirklich durch den Objektmanager geht und nicht an einer Instanz vorbei.
CART=$(curl -s -X POST '<base-url>/rest/V1/guest-carts' | tr -d '"')
# … Artikel und Adressen setzen …
curl -i -X PUT "<base-url>/rest/V1/guest-carts/$CART/order" \
-H 'Content-Type: application/json' \
-d '{"paymentMethod":{"method":"checkmo"}}'Erwartet: HTTP 400 mit unserer Meldung.
Wenn 200: Ein zweites Plugin auf Magento\Quote\Api\GuestCartManagementInterface nachrüsten, das die maskierte Kennung über QuoteIdMaskFactory auflöst — genau wie Magento\Checkout\Plugin\Api\VerifyIsGuestCheckoutEnabledBeforePlaceOrder.
8 — Kommt der Nachweis in der Sitzung an?
Unklar (der grösste Punkt in dieser Liste): Ob Magento\Checkout\Model\Session in einem REST-Kassenaufruf dieselbe Sitzung sieht wie der Frontend-Controller, der den Nachweis abgelegt hat. Die Luma-Kasse schickt ihren Bestellabschluss als AJAX-Aufruf an rest/V1/carts/mine/payment-information — also in den Bereich webapi_rest. Dass dort PHP-Sitzungen grundsätzlich funktionieren, ist belegt (Magento\Customer\Model\Authorization\CustomerSessionUserContext liest die Kundensitzung). Dass die Checkout-Sitzung dort dieselben Daten trägt, ist es nicht.
Nach einer erfolgreichen Prüfung, vor dem Bestellabschluss:
tail -f var/log/debug.log &
# in ProofStore::getVerifiedState() voruebergehend ergaenzen:
# $this->logger->debug('EV proof', ['len' => strlen($proof), 'area' => …]);Erwartet: Beim Aufruf von payment-information steht dieselbe Länge im Log wie beim Aufruf von /edelverify/confirm/index.
Wenn der Nachweis dort fehlt: Das ist kein Sicherheitsproblem — die Bestellung wird abgewiesen, also fail-closed —, aber es macht das Modul unbrauchbar. Dann muss ProofStore auf den Warenkorb umgestellt werden: Spalte edelverify_proof an quote (etc/db_schema.xml, Vorbild Magento_Persistent mit is_persistent), Schreiben über CartRepositoryInterface::save(), Lesen über $quote->getData(). Der Skizze in der README (Abschnitt *PWA Studio*) folgen.
9 — Wird der Form Key angenommen?
Unklar: Ob Magento\Framework\Data\Form\FormKey\Validator::validate() den Schlüssel aus dem Abfrageteil der Adresse liest. Der Quelltext sagt $request->getParam('form_key'), und getParam() bedient laut Laminas sowohl Abfrageteil als auch Formularfelder — nachweisbar ist das aber nur am laufenden System (das vendor/-Verzeichnis im geprüften Quellbaum ist leer, Laminas liess sich also nicht direkt nachschlagen).
curl -i -X POST '<base-url>/edelverify/confirm/index?form_key=<aus-dem-Cookie>' \
-H 'Content-Type: application/json' \
-b 'PHPSESSID=…; form_key=…' \
-d '{"verification_id":"erfunden"}'Erwartet: HTTP 502 mit {"ok":false,"reason":"upstream_unavailable"} — also *durch* die Form-Key-Prüfung hindurch bis zum Aufruf des Dienstes.
Und die Gegenprobe ohne ?form_key=:
Erwartet: HTTP 403 mit {"ok":false,"reason":"invalid_form_key"}. Nicht eine 302-Umleitung.
Wenn 302 kommt: createCsrfValidationException() wird nicht aufgerufen — dann prüfen, ob CsrfAwareActionInterface an einer Klasse greift, die Magento\Framework\App\Action\Action nicht erweitert.
10 — Erscheint der Hinweis, und an der richtigen Stelle?
Unklar: Ob before="-" im content-Container den Block wirklich über die Kasse setzt. Bei checkout_index_index steht dort ein Knockout-Baum, den das Theme unter Umständen absolut positioniert.
bin/magento cache:flushDann Warenkorb und Kasse im Browser ansehen.
Erwartet: Der Streifen steht oben, der Knopf öffnet das Prüffenster.
bin/magento dev:template-hints:enablezeigt, in welchem Container er tatsächlich gelandet ist.
11 — Kommt gate.js an der CSP vorbei?
Unklar: Ob etc/csp_whitelist.xml in dieser Fassung von Magento_Csp gelesen wird, und ob der inline gerenderte Script-Block einen Nonce bekommt.
Kasse öffnen, Browser-Konsole ansehen.
Erwartet: Keine Content Security Policy-Meldungen.
curl -sI '<base-url>/checkout/cart/' | grep -i content-security-policyErwartet: verify.edelbyte.ch steht in script-src, connect-src und img-src.
12 — Landet der Beleg an der Bestellung?
Eine vollständige Bestellung mit bestandener Prüfung durchführen.
mysql -e "SELECT increment_id, edelverify_verified, edelverify_checked_at,
edelverify_min_age, edelverify_mode, edelverify_ref
FROM sales_order ORDER BY entity_id DESC LIMIT 1" <db>
mysql -e "SELECT comment FROM sales_order_status_history
ORDER BY entity_id DESC LIMIT 3" <db>Erwartet: ja, ein Zeitstempel, die durchgesetzte Grenze, den Modus, acht Zeichen Kennung — und einen Verlaufseintrag «Alter über EdelVerify bestätigt (ab 18) …».
Bei einem Warenkorb, der nur Ware ab 16 enthält, muss dort 16 stehen, nicht 18. Steht überall pauschal 18, hält der Beleg die falsche Grenze fest — dann läuft eine ältere Fassung des Moduls.
Unklar dabei: Ob sales_order_place_after bei jeder Zahlungsart feuert. Bei Weiterleitungs-Zahlungsarten (PayPal Express, Saferpay, Datatrans) entsteht die Bestellung an anderer Stelle. Diesen Punkt für jede eingesetzte Zahlungsart einzeln wiederholen.
13 — Bleibt der Nachweis nach der Bestellung liegen?
ClearProofAfterOrder hängt an checkout_submit_all_after. Dieses Ereignis feuert laut Quelltext in Magento\Quote\Model\QuoteManagement (Zeile 473) — aber nicht in jedem Weg zur Bestellung.
Nach einer Bestellung sofort eine zweite mit demselben Browser versuchen.
Erwartet: Der Hinweis erscheint erneut, die Prüfung wird verlangt.
Wenn nicht: Zusätzlich an sales_model_service_quote_submit_success anmelden. Das feuert in derselben Methode, aber früher, und ist der breitere Aufhänger.
14 — Verhält sich der Riegel bei einer Störung richtig?
Der wichtigste Punkt der ganzen Liste, und der, den man am ehesten vergisst.
# Den Dienst unerreichbar machen — nicht abschalten, sondern ins Leere zeigen:
bin/magento config:set edelverify/general/base_url https://127.0.0.1:1
bin/magento cache:flushDann bestellen.
Erwartet: Bestellung abgewiesen. Nicht durchgelassen, nicht mit einer Warnung durchgelassen.
Dasselbe mit einem falschen geheimen Schlüssel und mit einem geleerten Schlüssel wiederholen.
bin/magento config:set edelverify/general/base_url https://verify.edelbyte.ch15 — Trifft die Kategorie-Erkennung?
Unklar: Ob $item->getProduct()->getCategoryIds() auf einem Warenkorbposten die Kategorien liefert. Der Produkt-Datensatz am Warenkorbposten ist unter Umständen mit eingeschränktem Attributsatz geladen; getCategoryIds() geht zwar an das Resource-Modell, aber bei konfigurierbaren Produkten hängen die Kategorien am Elternteil, nicht an der Variante.
Vier Fälle prüfen:
| Fall | Erwartung |
|---|---|
| Einfaches Produkt in betroffener Kategorie | Prüfung verlangt |
| Einfaches Produkt in einer anderen Kategorie | keine Prüfung |
| Konfigurierbares Produkt, Kategorie am Elternteil | Prüfung verlangt |
| Bündel / Gruppe mit betroffenem Bestandteil | offen — hier ist am |
ehesten mit einer Lücke zu rechnen |
Der Code läuft über getAllItems() (Elternteil und Varianten), was den dritten Fall abdecken sollte. Der vierte ist ungeklärt.
15b — Die zweite Schwelle: Bier ab 16, Gin ab 18
Nur nötig, wenn Sie zwei Grenzen führen. Wer alles gegen 18 prüft, überspringt diesen Abschnitt — für ihn ist alles Bisherige bereits die vollständige Prüfung.
Vorbereitung: bin/magento config:set edelverify/general/category_ids "17:16,12:18" (17 = Bier, 12 = Spirituosen), Vorgabe auf 18 lassen.
| Fall | Erwartet |
|---|---|
| nur Bier im Warenkorb | Hinweis nennt 16, Prüffenster sagt «Über 16 bestätigt» |
| nur Gin | Hinweis nennt 18 |
| Bier und Gin | Hinweis nennt 18 — die strengste Grenze gewinnt |
| Bier geprüft, dann Gin dazulegen | Bestellung wird wieder abgewiesen |
| danach Gin entfernen | Bestellung geht durch, keine neue Prüfung |
category_ids auf 17:17 | gilt als 18, nicht als 16 |
Die vierte Zeile ist die wichtige. Sie misst, ob ein «ja» für 16 in einen Warenkorb ab 18 hinüberträgt. Legen Sie den Gin innerhalb von 180 Sekunden dazu (ProofStore::CACHE_TTL) — genau in diesem Fenster glaubt das Modul seiner letzten Antwort. Bliebe die Bestellung möglich, wäre der Speicher nicht an die Grenze gebunden, und das Loch wäre drei Minuten breit.
Gegen die Schnittstelle, ohne Magento:
curl -s -X POST https://verify.edelbyte.ch/api/v1/verifications \
-H "x-api-key: $SK" -H 'content-type: application/json' \
-d '{"reference":"pruefplan","min_age":16,"test_outcome":"success"}'
sleep 5
# ACHTUNG: over_18 ist hier FALSE, age_ok ist TRUE
curl -s "https://verify.edelbyte.ch/api/v1/verifications/<id>" -H "x-api-key: $SK"
curl -s -X POST https://verify.edelbyte.ch/api/v1/proofs/verify \
-H "x-api-key: $SK" -H 'content-type: application/json' \
-d '{"proof":"av_…","min_age":18}' # → valid: false`over_18` ist nicht `age_ok`. Bei einer Prüfung gegen 16 steht in
over_18auch dannfalse, wenn die Person alt genug ist. Ein Modul, das nur dieses Feld liest, weist jede bestandene 16er-Prüfung ab und merkt es nie, weil im 18er-Betrieb beide Felder dasselbe sagen. Dass dieses Modulage_okliest, ist belegt:python3 scripts/pakete-pruefen.py, Abschnitt J.
16 — Kleinigkeiten
- Mehrfachversand (
Magento_Multishipping) geht überMagento\Multishipping\Model\Checkout\Type\Multishipping, nicht überCartManagementInterface::placeOrder(). **Der Riegel greift dort vermutlich nicht.** Prüfen, und falls bestätigt, entweder ein zweites Plugin nachrüsten oder den Mehrfachversand für betroffene Warenkörbe abschalten. - Admin-Bestellungen (*Verkäufe → Bestellungen → Neu*) gehen über
Magento\Sales\Model\AdminOrder\Create. Der Riegel greift dort nicht — das ist vertretbar (ein Mitarbeiter prüft am Telefon selbst), sollte aber bewusst entschieden und dem Händler gesagt werden. - Spalte in der Bestellliste. Nicht mitgeliefert. Wer sie will, braucht eine
sales_order_grid-Spalte plus einen Eintrag inview/adminhtml/ui_component/sales_order_grid.xml. Vorbild:Magento\Sales\etc\di.xml, Abschnittsales_order_grid_data_source. - Zwischenspeicher-Laufzeit.
ProofStore::CACHE_TTLsteht auf 180 Sekunden. Ob das der richtige Kompromiss zwischen «zurückgezogener Nachweis fällt schnell auf» und «nicht bei jeder Seite über das Netz» ist, zeigt erst der Betrieb. - Verbindungstest im Backend. Die WooCommerce-Fassung hat einen Knopf, der in einem Durchgang Erreichbarkeit, geheimen Schlüssel und Herkunftsprüfung testet. Dieses Modul hat ihn nicht —
EdelVerifyClient::createVerification()ist dafür bereits vorhanden, es fehlt der Admin-Controller und der Block. Das ist die erste Ergänzung, die sich lohnt: «Es geht nicht» hat bei einer Altersprüfung fast immer eine dieser drei Ursachen.