Schwere Bugs im Ninepoint Stripe-Plugin in Version 3.0.5!
Nachdem ich dieses Plugin über mehrere Versionen hinweg beta-getestet habe und es eigentlich gut (gerade von der UI her) finde, muss ich nun leider doch davor warnen. Die aktuelle Plugin-Version 3.0.5 hat gleich einen ganzen Strauß an größeren Bugs und der Entwickler Ninepoint legt (im Moment?) keinen Fokus mehr auf deren Behebung, da das ganze Team mit der Migration ihres gerade an JTL verkauften Marketplace- Connector beschäftigt ist.
Hier die Liste der mir aktuell bekannten, unbehobenen Bugs bzw. Unzulänglichkeiten:
All diese Informationen (und viel mehr Details zum Debugging) sind Ninepoint teilweise seit Monaten bekannt, passiert ist wenig. Unterm Strich sind diese Bugs so schwerwiegend, dass wir jetzt die Reißleine gezogen und das Plugin rausgeworfen haben. Ich veröffentliche diese ganzen Details, damit euch das alles erspart bleibt.
- Der wohl massivste Fehler: das Plugin stürzt immer mal wieder im Zahlungsprozess ab, so dass die Zahlung zwar durchgeführt, die Bestellung aber nicht abgeschicktwird. Das scheint ausschließlich das Stripe-eigene Wallet "Link" zu betreffen, lässt sich mit dessen Deaktivierung im Stripe-Backend also umgehen - allerdings ist diese Zahlungsart den Statistiken zufolge recht (bei uns ca. 20% der Zahlungen über Stripe) beliebt, weil der Kunde die KK-Daten nicht erneut eingeben muss (und war bis vor Kurzem auch noch günstiger als normale KK-Zahlungen).
- Der Kunde sieht manchmal eine weiße Seite, bekommt keine Bestellbestätigung im Browser oder per Mail und die Artikel liegen noch in seinem Warenkorb, irgendwann beschwert er sich dann darüber, dass er keine Ware bekommt...
- Manchmal wirft das Plugin den Kunden auch mit der Fehlermeldung "This PaymentIntent's amount could not be updated because it has a status of succeeded..." in den Warenkorb zurück, führt im Hintergrund aber die Zahlung durch! Der Kunde versucht es dann nochmal und nochmal, dabei löst er jedes Mal eine Zahlung aus, bis das Limit seiner Kreditkarte erreicht ist.... so geschehen, Kunde stocksauer.
Den Anhang 130315 betrachten- Der Händler findet solche Fälle nur, wenn er die Zahlungen im Stripe-Backend einzeln durchgeht und nach denen schaut, die keine "Order-ID: 12345678", sondern noch das Stripe-interne Payment Intent Object "pi_3Sm0yd*******Re2r1Y8EjyXM" in der Description stehen haben. Die Bestellung lässt sich nicht immer über die Metadaten rekonstruieren und dann manuell anlegen, meist hilft nur eine Erstattung und Entschuldigung an den Kunden.
- Seit Einführung der Version 3 verfügt das Stripe-Plugin über eine Express-Checkout-Funktion.
- Die Nutzung dieser hat (wie solche Funktionen immer) zur Folge, dass die restlichen Bestellschritte übersprungen werden, mit den üblichen Problemen: Lieferadressen werden nicht vom Kunden überprüft und sind u.U. falsch, das Kommentarfeld wird nicht zum Ausfüllen angeboten und die Zustimmung zu AGBs, Datenschutz, Widerrufsrecht, Altersüberprüfung, Übermittlung der E-Mail an Versanddienstleister usw. wird gar nicht erst abgefragt.
- Schlimmer wiegt aber, dass die Google und Apple Pay Buttons aus dem normalen Bezahlfenster (ohne Express nach Abschicken der Bestellung) entfernt wurden und diese äußerst beliebten Zahlungsarten (bei uns fast 40% der Stripe-Zahlungen) bei Deaktivierung der Express-Funktion damit gar nicht mehr angeboten werden.
- Das Stripe-Plugin wirft in dieser Version massenhaft PHP-Fehler.
- Im Shop- Logbuch offenbar einmal mit jeder (versuchten) Zahlung:
Den Anhang 130318 betrachten- Im Tideways-Profiler deutlich häufiger, auch mal 250 in einer Stunde (die Grafik unten stellt nur einen Teil der Fehler dar):
Den Anhang 130321 betrachten- Kein Bug, aber eine Unzulänglichkeit: das Stripe-Plugin legt bei der Zahlungsabwicklung kein Kundenobjekt in Stripe an bzw. benutzt es wieder, wie das von Stripe eigentlich gedacht ist, sondern bucht jede Zahlung wieder als neuer „Guest“ obwohl die kompletten Adressdaten übergeben werden. Das hat zur Folge, dass die Betrugsprävention von Stripe trotzdem schlechter greift und den Kunden ggf. öfter mit 3DS-Abfragen nervt oder auch mal Zahlungen ablehnt, weil die Risiko-Score ohne die Verbindung zu früheren Zahlungen höher ist.
Für den Händler ist die Handhabung im Backend dadurch nicht ganz so komfortabel (mittlerweile verknüpft Stripe über die Kunden-E-Mail aber auch Guest Accounts, wodurch das nicht mehr so tragisch ist) und die Kunden-Statistiken im Stripe-Backend sind nutzlos.
Die richtige Nutzung von Kundenobjekten wäre aber die Voraussetzung für die Tokenisierung (also die Wiederverwendung von bei Stripe sicher gespeicherten Kreditkartendaten ohne neue Eingabe), die im nicht mehr existenten customweb/SellXed-Stripe-Plugin mal ein echtes Killer-Feature für Stammkunden war… das Feature scheint Ninepoint aber leider sowieso nicht zu verfolgen.
- Das Plugin führt (auch schon in früheren Versionen) bei JEDEM einzelnen Seitenaufruf im Shop (auch wenn die nichts mit der Zahlung zu tun hat) eine synchrone Abfrage an die Stripe-API durch. Mit dem Tideways-Profiler lässt sich wunderbar beobachten, dass die knappe 300ms dauert und die Ladezeit jeder einzelnen Seite um diese 300ms verlangsamt. Der JTL-Shop ist gerade wegen der ganzen nötigen Plugins auch so schon kein Geschwindigkeitswunder, aber das ist einfach schlampige Plugin-Programmierung, die zu einer völlig unnötigen Verlangsamung des Shops führt. Und das mögen weder Google noch die Kunden.
Den Anhang 130324 betrachten
So eine Abfrage gehört asynchron ausgeführt, über einen Webhook (den das Plugin sogar für Lastschriften usw. hat) oder einen Cronjob implementiert, die unabhängig von der Shop-Oberfläche laufen und diese nicht beeinflussen. Leider nicht das erste Mal, dass ich sowas bei den teilweise hobby-artig programmierten Plugins für den JTL-Shop finde.
Wir schauen uns jetzt das Plugin von Payever an, das auch Stripe anbinden kann und sogar kostenlos angeboten wird – das ist allerdings eine Middleware, nicht nur ein Plugin und hat auf den ersten Blick auch so manche Unzulänglichkeit...
Ich kann gut hier zustimmen. Bereits über 10 Emails geschrieben an die Firma, es gibt immer eine Antwort, aber keine Lösung. Mal Gemini und Claude über den Plugin Inhalt laufen lassen und beide haben die Gründe für die Fehler gefunden. Seltsam.
Code:
Pluginjtl_stripe_pluginServicesStripeService::authenticated {
error: Could not connect to Stripe (https://api.stripe.com/v1/webhook_endpoints). Please check your internet connection and try again. If this problem persists, you should check Stripes service status at https://twitter.com/stripestatus, or let us know at support@stripe.com.nn(Network error [errno 6]: Could not resolve host: api.stripe.com)
}
Pluginjtl_stripe_pluginControllersIOController::ioRequest {
error: Syntax error
}
###################
✦ In JTL-Shop 5 wird HOOK_IO_HANDLE_REQUEST bei jeder IO-Anfrage ausgelöst. Der IOController dieses Plugins versucht, jede dieser Anfragen als JSON zu dekodieren. Wenn eine andere Komponente des Shops oder ein externer Aufruf (z. B. ein
Bot) ungültiges JSON an io.php sendet, fängt dieses Plugin die Exception ab und loggt sie als eigenen Fehler. Das ist ein Designfehler des Plugins, da es zu global agiert.
Der Verbindungsfehler in StripeService::authenticated tritt auf, wenn der Server api.stripe.com nicht auflösen kann (DNS-Problem). Diese Methode wird an mehreren Stellen in der Bootstrap.php aufgerufen. Ich werde mir nun die
Bootstrap.php genauer ansehen.
✓ ReadFile Bootstrap.php
✦ Die Analyse der Log-Fehler hat folgende Ursachen ergeben:
1. Fehler: IOController::ioRequest { error: Syntax error }
* Ursache: Dieser Fehler tritt auf, weil das Plugin auf den globalen Hook HOOK_IO_HANDLE_REQUEST reagiert. Dieser Hook wird bei jeder IO-Anfrage im JTL-Shop ausgelöst (nicht nur für Stripe-relevante).
* Technischer Hintergrund: Das Plugin versucht im IOController, den Inhalt jeder Anfrage mittels json_decode zu verarbeiten. Wenn eine Anfrage (z. B. von einem Bot, einem anderen Plugin oder einer fehlerhaften Browser-Anfrage) kein
gültiges JSON enthält, wirft PHP einen Fehler. Das Stripe-Plugin fängt diesen ab und schreibt ihn in sein eigenes Log-File.
* Fazit: Dies ist oft "Hintergrundrauschen". Das Plugin loggt hier Fehler, die eventuell gar nichts mit Stripe zu tun haben, sondern durch ungültige Aufrufe an den Shop verursacht werden.
2. Fehler: StripeService::authenticated { error: Could not connect to Stripe ... Could not resolve host: api.stripe.com }
In der Datei Services/StripeService.php ist die Prüfung so programmiert:
1 public function authenticated(): bool
2 {
3 if (isset($_SESSION['isStripeCredentialsValid'])) {
4 return $_SESSION['isStripeCredentialsValid'];
5 }
6
7 try {
8 (new WebhookEndpoint())->all(); // Hier wird Stripe kontaktiert
9 } catch (\Exception $e) {
10 $this->error(__METHOD__, ['error' => $e->getMessage()]); // Fehler wird geloggt
11 return false; // Es wird false zurückgegeben, ABER NICHTS in der Session gespeichert
12 }
13
14 $_SESSION['isStripeCredentialsValid'] = true; // Nur bei Erfolg wird es gespeichert
15 return true;
16 }
Das Problem:
Wenn die Verbindung fehlschlägt, wird der Fehler geloggt und false zurückgegeben. Da das Plugin den Fehlerzustand aber nicht in der Session speichert (kein "Negativ-Caching"), versucht es bei jedem einzelnen Seitenaufruf erneut, Stripe
zu erreichen.
3. Woher kommen die Aufrufe?
Das Plugin ruft diese Prüfung in der Bootstrap.php bei fast jeder Interaktion auf:
* Artikel-Detailseiten: Jedes Mal, wenn ein Kunde (oder ein Suchmaschinen-Bot) eine Produktseite aufruft.
* Warenkorb: Bei jedem Aufruf des Warenkorbs.
* Bestellvorgang: Bei jedem Schritt im Checkout.
Wenn also 500 Mal eine Seite aufgerufen wird, während das DNS-Problem besteht, erzeugt das Plugin 500 Log-Einträge. Da auch Bots (Google etc.) ständig Seiten aufrufen, kommen diese hohen Zahlen schnell zustande.
####################