everysize / checkout-oxid
everysize Checkout – Modul für OXID eShop 7
Package info
github.com/everysize/checkout-oxid
Type:oxideshop-module
pkg:composer/everysize/checkout-oxid
Requires
- php: >=8.0
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^9.6
Suggests
None
Provides
None
Conflicts
- oxid-esales/oxideshop-ce: <7.0 || >=8.0
Replaces
None
README
Bindet einen OXID-Shop an den everysize-Direktkauf an: Bestellungen, die Kundinnen auf everysize.com aufgeben, landen als Bestellung im Shop; Bestellnummer, Versand, Storno, Retoure und Erstattungen gehen zurück an everysize.
Composer-Paket everysize/checkout-oxid, Modul-ID everysize_checkout.
Voraussetzungen
- OXID eShop CE 7.0 bis 7.5 (getestet mit 7.0.5/PHP 8.1 und 7.5.1/PHP 8.4)
- PHP ≥ 8.0 mit
curl - ein Cronjob (OXID hat keinen eigenen Scheduler)
- Admin-Seiten nur mit dem Twig-Admin. OXID 7.0 mit dem optionalen Smarty-Admin zeigt die Seiten nicht an; der Abgleich läuft trotzdem.
Kein PayPal-Modul nötig: bezahlt wird bei everysize. Erstattet wird im Reiter „everysize“ der Bestellung über everysize (ab 1.1.0) oder direkt im PayPal-Konto des Händlers.
Ablauf
| Richtung | Was | Wann |
|---|---|---|
| everysize → Shop | neue Bestellungen (PROCESSING, auch bereits stornierte REVOKED) ohne Händler-Bestellnummer |
jeder Lauf |
| Shop → everysize | OXID-Bestellnummer | direkt nach dem Import |
| Shop → everysize | Import-Meldung: aktueller Shop-Preis und Bestand je Position (Information, ab 1.3.0) | einmal direkt nach dem Import, nicht bei stornierten Bestellungen |
| everysize → Shop | Hinweis, wenn eine importierte Bestellung bei everysize storniert wurde (ab 1.4.0, siehe „Hinweis bei Storno durch everysize“) | nach Import und Export, jeder Lauf |
| Shop → everysize | Versand mit Sendungsnummer und Versanddienstleister | Versanddatum gesetzt („Jetzt versenden“), auch wenn die Bestellung inzwischen storniert ist |
| Shop → everysize | Sendungsnummer nachgetragen oder korrigiert | bis 14 Tage nach der Versandmeldung, nur wenn everysize sie noch nicht hat |
| Shop → everysize | Erstattung (ganz oder teilweise) | Button im Reiter „everysize“ |
| Shop → everysize | volle Erstattung | automatisch nach einem Storno vor dem Versand (abschaltbar) |
| Shop → everysize | Storno MERCHANT_DECLINE |
Position oder Bestellung storniert, bevor der Versand gemeldet war |
| Shop → everysize | Retoure RETOUR |
Position oder Bestellung storniert, nachdem der Versand gemeldet war |
- Zuordnung: Bestellpositionen über
sku= OXID-Artikelnummer (OXARTNUM) der Variante. Exakter Treffer vor Groß-/Kleinschreibung, aktiver vor inaktivem Artikel; zwei gleich gute Treffer sind ein Fehler. - Preise: Bestellung mit den bei everysize bezahlten Bruttopreisen. Netto und Mehrwertsteuer rechnet das Modul mit dem Steuersatz aus OXID zurück (Artikel → Kategorie → Standard).
- Import-Meldung: Vor dem Schreiben liest das Modul je Position den Brutto-Shop-Preis (nur in Euro und nur, wenn OXID ihn als Brutto liefert) und den Bestand (nur wenn der Shop den Verkauf über den Bestand begrenzt). Weicht der Preis um mehr als 0,009 ab oder liegt der Bestand unter der Bestellmenge, steht eine Warnung im Protokoll; der Lauf bleibt erfolgreich. Die Meldung an everysize ist reine Information: sie ändert weder Bestellung noch Preis, ein Fehler dabei stoppt den Import nicht und wird nicht wiederholt.
- Bestand: wird beim Import reduziert, ein Storno in OXID bucht ihn wie gewohnt zurück.
- Kunde:
customer.emailist eine Weiterleitungsadresse von everysize, stabil je Kundin; ein Wiederkauf landet am selben (Gast-)Konto. Mails des Shops gehen dorthin und laufen über everysize. Die Original-Adresse (customer.originalEmail) steht nur zur Information im Reiter „everysize“ (everysize_checkout_order.ORIGINAL_EMAIL). - Zahlart: eigene Zahlart „everysize (PayPal)“, beim Aktivieren angelegt
und keiner Versandart zugeordnet, also im Shop nie wählbar. Die
PayPal-Capture-ID steht in
OXTRANSIDund im Reiter „everysize“. - Atomar: Kunde, Bestellung, Positionen, Bestand und Verknüpfung entstehen in einer Transaktion; ein Fehler hinterlässt nichts, die Bestellung bleibt bei everysize und wird im nächsten Lauf erneut versucht.
- Genau einmal: jede Meldung steht mit eindeutigem Schlüssel (Bestellung,
Art, SKU) in
everysize_checkout_report; ein zweiter Lauf meldet nichts doppelt. - Storno bei everysize: vor jeder Meldung fragt das Modul den Stand bei
everysize ab. Ist die Bestellung dort schon storniert (PayPal-Erstattung,
everysize Business), meldet es nichts: ein Versand würde sie auf
COMPLETEDzurücksetzen, ein Storno den Grund von everysize überschreiben. Beim Versand steht das als Fehler im Protokoll. - Hinweis bei Storno durch everysize (ab 1.4.0): Wird eine bereits importierte Bestellung
danach bei everysize storniert (z. B. von der Kundin widerrufen und erstattet), erscheint ein
Hinweis nur für den Händler: ein rotes Warnzeichen in der Spalte „everysize“ der
Bestellliste, die Meldung im Reiter „everysize“ der Bestellung und ein Eintrag (Warnung) im Protokoll:
„Bestellung wurde bei everysize storniert. Bitte nicht versenden und im Shop stornieren.“
(Deutsch oder Englisch, nach der Sprache der Bestellung). Zeigt der Shop die Bestellung schon als
versendet (Versanddatum, Sendungsnummer), lautet er: „… ist hier aber schon als versendet markiert.
Bitte prüfen.“ Ist sie im Shop schon storniert oder vollständig erstattet, gibt es keinen Hinweis (eine
Teilerstattung, z. B. aus Kulanz, zählt nicht); wird sie danach storniert oder vollständig erstattet,
verschwindet der Hinweis wieder. Der Hinweis
steht bewusst nicht im Feld „Anmerkung“ der Bestellung (das ist der Text der Kundin und für sie
sichtbar), es geht keine E-Mail raus, und er nennt nur die Bestellnummer. Das Modul ändert dabei
nichts: kein Storno, keine Bestandsbuchung, keine Erstattung, keine Meldung an everysize – das
entscheidet der Händler. Die Abfrage läuft nach Import und Export, betrachtet Bestellungen der
letzten 30 Tage (Anlegedatum), liest höchstens 500 Zeilen je Lauf (bei mehr die jüngsten); ein Fehler dabei (keine
Antwort, 401, 404, 5xx, ungültige Antwort) steht höchstens einmal je Tag und Fehlerart als Warnung im Protokoll (weitere gehen ins OXID-Log), Import und
Export laufen unabhängig davon. Jede Bestellung bekommt den Hinweis genau einmal (Rückmeldung
es-revokedineverysize_checkout_report, im Reiter als „Storniert bei everysize“). Grenzen: Eine Erstattung, die der Händler direkt in PayPal macht, ohne Storno bei everysize, kennt das Modul nicht. Stornos bei everysize für Bestellungen, die älter als 30 Tage sind, werden nicht mehr abgeglichen. - Sendungsnummer nachmelden: jede gemeldete Nummer steht als Rückmeldung
tracking(Nummer in der SKU-Spalte). Steht in OXID später eine andere Nummer, geht sie einmal per erneuter Versandmeldung raus — bis 14 Tage nach dem Versand, nur beiCOMPLETEDund nur, wenn everysize sie nicht schon hat (dann nur gemerkt, „known“). everysize nimmt die wiederholte Meldung als Aktualisierung; die Kundin bekommt keine Mail. - Versand und Storno im selben Lauf: Ist eine Bestellung versendet
(Versanddatum gesetzt) und vor dem nächsten Lauf schon storniert, meldet das
Modul erst den Versand mit Sendungsnummer und im selben Lauf die Retoure
(
RETOUR) — nichtMERCHANT_DECLINE, und die Sendungsnummer geht nicht verloren. - Reiter „everysize“: drei Abschnitte — die everysize-Bestellung (Bestellnummer, Import, Meldung der Bestellnummer, Capture-ID, Original-E-Mail), die Erstattung über everysize (Bezahlt / erstattet / noch offen, Liste der Erstattungen, Formular) und die Rückmeldungen an everysize als Tabelle. Zeiten in der Zeitzone des Shops (PHP-Standardzeitzone), auch im Protokoll.
- Erstattung: Reiter „everysize“ → Betrag (vorbelegt mit dem noch offenen
Betrag) → „Erstatten“. everysize erstattet die PayPal-Zahlung im Namen des
Händlers (External API
POST …/orders/{id}/refunds); jedes Formular trägt einen eigenenIdempotency-Key, ein doppelt abgeschicktes Formular erstattet einmal. Nur eine klare Ablehnung der Erstattungsanfrage selbst gilt als „kein Geld geflossen“. Bei jeder anderen Antwort (Timeout, Serverfehler, auch 401/403/404/429) und bei einer Antwort ohne Refund-ID meldet der Reiter „Ergebnis unklar“: Das Geld kann ausgezahlt worden sein. Bitte in PayPal prüfen. Solange der Fall offen ist, geht keine andere Erstattung raus (auch keine automatische), und innerhalb von 2,5 Minuten nach dem Senden wird nichts wiederholt. „Wiederholen“ liest zuerst die Erstattungen bei PayPal: steht dort eine seit dem Anlegen neue, passende Erstattung, die der Shop nicht kennt, wird sie als Erfolg gebucht und nichts gesendet; ist die Liste nicht lesbar, wird nichts gesendet. Sonst gilt: Ging die erste Anfrage nicht durch, wird der Betrag jetzt ausgezahlt. Lehnt der Checkout die Wiederholung ab, bleibt der Fall offen. „Als geklärt markieren“ sendet nichts und beendet den Fall (frühestens 2,5 Minuten nach dem Senden); die automatische Erstattung bleibt dann für diese Bestellung aus. „Bezahlt“ ist der Betrag, den everysize abgebucht hat; ein Positionsstorno in OXID ändert ihn nicht. - Automatische Erstattung: Nach einem gemeldeten Storno vor dem Versand
(
MERCHANT_DECLINE) erstattet das Modul im nächsten Lauf den vollen Betrag, sobald keine Position der Bestellung mehr aktiv ist (eine everysize-Bestellung hat ein Produkt; ein im Admin hinzugefügter Artikel verhindert die Erstattung) — es wurde nichts versendet, der Kundin steht alles zu. Nur, wenn nichts auf einen Versand hindeutet (Versanddatum, Sendungsnummer), im Shop kein Rabatt oder Gutschein gegeben wurde (auch nicht vor einem Positionsstorno: OXID setzt beide Felder bei der Neuberechnung zurück, das Modul hält den Rabatt vorher als Sperre fest), der gezahlte Betrag bei everysize lesbar ist, und über everysize noch gar nichts erstattet wurde (weder automatisch noch im Reiter): nach einer bewussten Teilerstattung wird der Rest nicht obendrauf gezahlt. Retouren nicht (der Händler erstattet nach Wareneingang, ggf. mit Abzug). Standard: an (Einstellung „Storno vor dem Versand automatisch erstatten“); erstattet werden nur Stornos ab dem Einschalten (autoRefundSince, beim Update per Migration gesetzt), höchstens 14 Tage zurück; Stornos, die die Automatik übergeht (vor dem Einschaltdatum, mit noch aktiver Position), stehen einmal im Protokoll. FesterIdempotency-Keyauto-decline-<everysize-ID>je Bestellung. Die Automatik sendet höchstens einmal: unmittelbar vor dem Senden fragt das Modul Einstellung und alle Bedingungen für genau diese Bestellung neu und liest die Erstattungsliste bei everysize. Ist sie nicht lesbar, wird nichts gesendet und im nächsten Lauf erneut geprüft; steht dort eine abgeschlossene oder ausstehende Erstattung (auch in PayPal gemacht), erstattet das Modul nicht. Bleibt das Ergebnis unklar (Verbindungs- oder Serverfehler), wird der Fall nie automatisch wiederholt: spätere Läufe lesen nur die Liste und schließen den Fall bei einer neuen passenden Erstattung ab, nach 15 Minuten steht er in jedem Lauf im Protokoll; wiederholt wird im Reiter. Eine Ablehnung (nicht erstattbar, PayPal lehnt ab) und jede Sperre stehen einmal im Protokoll und im Reiter. Grenze: Eine Gutschrift, die nur im Shop gebucht wird, erkennt das Modul nicht (außer über die genannten Indizien Versanddatum, Sendungsnummer, Rabatt und Gutschein). Erstatten Sie vor dem Storno selbst oder schalten Sie die Automatik ab. - Bestellliste: Spalte „everysize“ (vor den Storno-/Löschen-Symbolen) mit der everysize-Bestellnummer,
in der Filterzeile „alle / nur everysize / ohne everysize“ und die Suche
nach der everysize-Nummer (Erweiterung von
OrderListundviews/twig/extensions/themes/admin_twig/order_list.html.twig). - Protokoll: nur Läufe, die etwas übertragen haben oder fehlgeschlagen sind; 31 Tage.
- Selbstauskunft: jeder API-Aufruf trägt
ERP-Shop-System: OXID, die OXID-Version,Interface-Version(Modulversion) und den User-AgenteverysizeCheckoutPlugin-OXID/<Version>.
Eingehende Endpunkte gibt es keine — der Checkout ruft Shops nicht auf.
API-Umgebung
Standard ist immer Produktion (https://ext-checkout.everysize.com) —
eine Installation beim Händler braucht dafür nichts einzustellen.
Für Test- und Entwicklungsshops nennt everysize auf Anfrage die Adresse
einer Testumgebung samt Test-Zugangsdaten. Sie wird in der Umgebung des
PHP-Prozesses gesetzt (Webserver und Cron); OXID 7.5 lädt zusätzlich die
.env/.env.local im Projektverzeichnis (7.0 noch nicht):
EVERYSIZE_CHECKOUT_API_URL=https://<von everysize genannt>.everysize.com
Erlaubt ist nur https:// mit einem Host unter everysize.com, ohne Port und
Pfad. Ein anderer Wert ist ein Fehler — es gibt bewusst keinen stillen
Rückfall auf Produktion, damit ein Testshop mit Tippfehler keine Bestellungen
an den Live-Checkout schickt. Bewusst keine Einstellung im Admin. Die aktive
Umgebung zeigt die Admin-Seite an.
Installation
Im Projektverzeichnis des Shops, aus Packagist:
composer require everysize/checkout-oxid
vendor/bin/oe-console oe:module:install vendor/everysize/checkout-oxid
vendor/bin/oe-console oe:module:activate everysize_checkout # legt Tabellen und Zahlart an
Ohne Packagist: ZIP aus everysize Business (Shop-Integration → OXID eShop)
nach ./modules/ entpacken und als Path-Repository einbinden, dann wie oben:
composer config repositories.everysize-checkout '{"type": "path", "url": "./modules/everysize-checkout", "options": {"symlink": false}}'
Update: composer update everysize/checkout-oxid (beim ZIP vorher die
Quellen ersetzen), vendor/bin/oe-console oe:module:install vendor/everysize/checkout-oxid,
dann Modul deaktivieren und wieder aktivieren
(oe:module:deactivate / oe:module:activate everysize_checkout): das
führt die Migrationen aus und übernimmt neue Klassenerweiterungen (ab 1.1.0
die Bestellliste, in dieser Fassung das Bestellmodell). Alle drei Schritte sind nötig:
Bis sie gemacht sind, läuft die automatische Erstattung nicht und meldet das im Protokoll,
und der Reiter „everysize“ zeigt „Datenbank-Update nötig“ ohne Erstattungsformulare.
Deaktivieren löscht keine Daten.
Einrichtung
-
Admin → Bestellungen → everysize Checkout: Client-ID und Client-Secret aus everysize Business (Shop-Integration → OXID eShop) eintragen, Verbindung testen, Versandart und Versanddienstleister wählen, speichern.
-
Cronjob, empfohlen alle 5 Minuten:
*/5 * * * * cd /pfad/zum/shop && vendor/bin/oe-console everysize:sync
everysize:order:importundeverysize:order:exportlaufen je eine Hälfte (Test, Fehlersuche). Zwei Läufe gleichzeitig verhindert eine MySQL-Sperre (GET_LOCK).
Die Zugangsdaten liegen in der Shop-Datenbank (everysize_checkout_settings),
nicht in OXIDs Modul-Einstellungen: die stehen in var/configuration/*.yaml,
und das Verzeichnis gehört laut OXID in die Versionsverwaltung — das Secret
landete im Git des Händlers. Ohne Zugangsdaten macht das Modul keine
API-Aufrufe.
Unter OXID 7.0 mit PHP 8.1 gibt der OXID-Kern selbst viele
PHP-Deprecations aus (z. B. ListModel); OXID empfiehlt, sie per
error_reporting auszublenden — für den Cron z. B.
php -d error_reporting=24575 vendor/bin/oe-console everysize:sync.
Entwicklung
composer install vendor/bin/phpunit # Unit-Tests (reine Logik, ohne OXID) docker run --rm -v "$PWD":/app -w /app php:8.0-cli vendor/bin/phpunit # Untergrenze PHP 8.0
Der Code muss unter PHP 8.0 laufen (OXID 7.0): keine Enums, keine
readonly-Properties.
Lizenz
MIT, siehe LICENSE.