# Log-Analyse 26.08.2026 Auswertung von 94 Logdateien (`abo_order`, `cleanup`, `cron`, `dhl`, `laravel`, `order_controller`, `payment`, `stripe`) aus `dev/2026-08-26-logs/`. Zeitraum: 05.04.2026 – 26.08.2026 (Schwerpunkt 27.07.–26.08.2026, `laravel.log` reicht ungedreht bis April zurück). ## Umsetzungsstand (26.08.2026) Alle Code-Fixes sind umgesetzt. Der Testlauf zeigt unveraendert 89 Vorbestandsfehler (BusinessPlan, InvoiceService, ConcurrentPayment – alle bereits vor diesen Aenderungen rot), bestandene Tests stiegen von 893 auf 914: die 21 neuen Tests laufen, keine Regression. | # | Punkt | Status | | --- | --- | --- | | P0-1 | `contains()` auf Array | erledigt | | P0-2 | `V53PAK` → `V53WPAK` | erledigt | | P0-3 | Zolldaten Schweiz | CH/GB/US/NO gesperrt, ExportDocument offen (eigenes Ticket) | | P1-1 | Log-Channel `userabo` | erledigt | | P1-2 | Portal-Alias `user_order_detail` | erledigt | | P1-3 | `delete-`-E-Mail-Kollision | erledigt | | P1-4 | Nicht-ASCII-E-Mails | erledigt | | P1-5 | DHL-Felder normalisieren | erledigt | | P2-1 | Mailversand in die Queue | erledigt | | P2-2 | PAYONE-Fehlerbilder | keine Codeaenderung – Kartenablehnungen sind Geschaeftsvorfaelle, `923`/`950` treten nicht mehr auf | | P2-3 | DHL-Tracking Retry | erledigt | | P2-4 | Stripe-Webhook-Rauschen | erledigt | | P2-5 | `KasController` Fatal | **offen: Serverseitig** – Datei ist im Branch bereits geloescht, Deploy-Stand pruefen | | P2-6 | ImageMagick PDF-Policy | **offen: Serverseitig** – `policy.xml` auf dem Server anpassen | | P2-7 | Passport Grant Type | Logging entschaerft; ob der `password`-Grant aktiviert werden soll, ist noch zu klaeren | | P2-8 | Logrotation / Cron-Rauschen | erledigt (Rotation loest sich mit P1-1, Erinnerungs-Logs auf `debug` + Zusammenfassung) | ## Nach dem Deploy noch zu tun 1. `php artisan view:clear` – die kompilierte Blade-View des Abo-Checks liegt im Cache. 2. Benutzer 1212 einmalig manuell aufraeumen, damit `user:cleanup` sauber durchlaeuft. 3. Bestehende Umlaut-Adressen pruefen: `SELECT id, email FROM users WHERE email <> CONVERT(email USING ASCII);` und dasselbe fuer `shopping_users.billing_email`. 4. ENV auf dem Server: `DHL_ACCOUNT_NUMBER_V53PAK` → `DHL_ACCOUNT_NUMBER_V53WPAK` (der alte Name wird als Fallback weiterhin gelesen, muss also nicht sofort umgestellt werden). 5. Die 16 im Log genannten Auslandsbestellungen brauchen neue Labels. --- # Fehlerverteilung (aggregiert) | Vorkommen | Meldung | Quelle | | --- | --- | --- | | 109 | `Unable to create configured logger` (Log `[userabo]` is not defined) | laravel | | 89 | `UserMakeAboOrder::makeOrder / makePayment Error response` | laravel | | 66 | DHL HTTP 400 – davon **33× „The product entered is unknown"** | dhl | | 53 | `Call to a member function contains() on array` | laravel | | 24 | `[DHL Service] Shipment creation failed (sync)` | laravel | | 21 | `[DHL Tracking Service] Unified API returned unexpected status` (HTTP 500) | laravel | | 18 | `AboRetryPaymentService::retry / makePayment Error` | laravel | | 17 | `Stripe-Webhook ohne zuordenbare Zahlung` | stripe | | 9 | `SOAP-ERROR: Parsing WSDL ... kasapi.kasserver.com` | laravel | | 9 | `Route [user_order_detail] not defined.` | laravel | | 7 | `deleteInactiveUsers failed ... Duplicate entry 'delete-…'` | cleanup | | 5 | `The authorization grant type is not supported` (Passport) | laravel | | 3 | `[DHL Controller] Failed to dispatch: shipping phone darf maximal 20 Zeichen haben` | laravel | | 2 | Non-ASCII-Zeichen im local-part der E-Mail-Adresse | payment / abo_order | --- # P0 – Muss sofort behoben werden ## P0-1 · Abo-Anlage im Portal wirft 500 (`contains()` on array) **Belege:** 53×, davon 43× am 24.08. und 10× am 25.08. – der mit Abstand häufigste harte Fehler. Betroffen sind echte Kunden (`userId` 1039, 1174, …). ``` production.ERROR: Call to a member function contains() on array at resources/views/portal/abo/_create_check.blade.php:128 ``` **Ursache:** [_create_check.blade.php:128](resources/views/portal/abo/_create_check.blade.php#L128) ruft `->contains(...)` auf dem Rückgabewert von `getContentByOrder()` auf. [Yard.php:695-709](app/Services/Yard.php#L695-L709) liefert aber ein **PHP-Array**, keine Collection: ```php public function getContentByOrder() { $ret = []; // … $ret = array_merge($ret, $comp); return $ret; // ← Array, nicht Collection } ``` Eingeschleppt mit Commit `50d55f4` („Abo: Einmalprodukte bereits bei der Abo-Anlage", 21.07.2026) – seitdem bricht die Prüf-/Bestätigungsseite der Abo-Anlage ab, sobald sie gerendert wird. **Der Kunde kann das Abo nicht abschließen.** **Lösungsvorschlag (minimal-invasiv, keine Signaturänderung):** ```blade @php($hasOneTimeRows = collect(Yard::instance('subscription')->getContentByOrder()) ->contains(fn ($item) => (bool) ($item->options->abo_addon ?? false))) ``` Alternative (sauberer, aber breitere Wirkung): `getContentByOrder()` auf `Illuminate\Support\Collection` umstellen und Rückgabetyp deklarieren. Dann müssen die weiteren Aufrufer geprüft werden – [CheckoutRepository.php:134](app/Repositories/CheckoutRepository.php#L134), [AboController.php:784](app/Http/Controllers/Portal/AboController.php#L784), [OrderPaymentService.php:203](app/Services/OrderPaymentService.php#L203), [PaymentHelper.php:114](app/Services/PaymentHelper.php#L114), [UserMakeOrder.php:303](app/Cron/UserMakeOrder.php#L303) sowie `AboHelper::aboHasBaseProduct()`. **Empfehlung:** Erst den Blade-Fix ausliefern (Hotfix), die Typvereinheitlichung separat mit Test. **Test:** Feature-Test, der die Abo-Anlage bis zum Schritt „check" durchspielt. --- ## P0-2 · Kein einziges Auslandslabel wird erzeugt – falscher DHL-Produktcode **Belege:** **33 von 33** Anfragen mit `"product":"V53PAK"` scheitern mit ``` "validationMessages":[{"property":"product","validationMessage":"The product entered is unknown."}] ``` Durchgehend seit mindestens 27.07.2026, betroffene Bestellungen im Log-Zeitraum: 59704, 59893, 59909, 60011, 60246, 60335, 60342, 60403, 60431, 60534, 60574, 60599, 60628, 60630, 60734, 60945. Zielländer: 27× AUT, 4× CHE, 2× ESP. **Ursache:** Der Produktcode `V53PAK` existiert in der DHL Parcel DE Shipping API v2 nicht. Korrekt ist **`V53WPAK`**. Das steht wörtlich in der im Repo abgelegten API-Spezifikation, [parcel-de-shipping-v2_2.yaml:1231](dev/2026-05-13-dhl-modul/parcel-de-shipping-v2_2.yaml#L1231): ``` * V01PAK: DHL PAKET; * V53WPAK: DHL PAKET International; enum: [V01PAK, V53WPAK, V54EPAK, V62WP, V62KP, V66WPI] ``` Der Rest der Anwendung verwendet bereits korrekt `V53WPAK` – nur der Versand-Pfad nicht. Das ist die eigentliche Inkonsistenz: | Verwendet `V53WPAK` (korrekt) | Verwendet `V53PAK` (falsch) | | --- | --- | | [ModalController.php:288](app/Http/Controllers/ModalController.php#L288) | [DhlProductResolver.php:15](app/Services/DhlProductResolver.php#L15) `INTERNATIONAL_PRODUCT_CODE` | | [admin/dhl/create.blade.php:172](resources/views/admin/dhl/create.blade.php#L172), :383 | [config/dhl.php:99](config/dhl.php#L99), :107 | | `resources/lang/{de,en,es,fr}/dhl.php:27` | [SettingController.php:143](app/Http/Controllers/SettingController.php#L143), :152 | | | [DhlShipmentWeightCalculator.php:16](app/Services/DhlShipmentWeightCalculator.php#L16) | | | [DhlModalService.php:319-320](app/Services/DhlModalService.php#L319-L320), :335 | **Nebenwirkung:** Weil die Admin-Maske `V53WPAK` sendet, der Resolver aber `V53PAK` erwartet, wirft [DhlProductResolver::resolveProductCode()](app/Services/DhlProductResolver.php#L74) für manuell angelegte Auslandssendungen zusätzlich `"Produkt V53WPAK ist fuer DHL-Versand in das Zielland … nicht erlaubt."` **Lösungsvorschlag:** 1. `DhlProductResolver::INTERNATIONAL_PRODUCT_CODE = 'V53WPAK'`. 2. Konfig-Keys in [config/dhl.php](config/dhl.php) (`account_numbers`, `dimensions`) auf `V53WPAK` umbenennen, ENV-Namen `DHL_ACCOUNT_NUMBER_V53WPAK` mit Fallback auf `DHL_ACCOUNT_NUMBER_V53PAK`. 3. In `normalizeProductCode()` einen Alias ergänzen, analog zum bestehenden `V62WP → V62KP` in [SettingController::normalizeDhlProductCode()](app/Http/Controllers/SettingController.php#L184-L187): ```php $productCode = match ($productCode) { 'V53PAK' => 'V53WPAK', 'V62WP' => 'V62KP', default => $productCode, }; ``` Damit bleiben Alt-Settings (`dhl_account_v53pak` in der `settings`-Tabelle) und gespeicherte Sendungen lauffähig. 4. `DhlShipmentWeightCalculator`, `DhlModalService` und `SettingController` nachziehen. 5. Tests anpassen: `tests/Unit/Dhl/DhlProductResolverTest.php`, `ShippingServiceProductCodeTest.php`, `DhlSanitizeLoggingTest.php`. **Die Abrechnungsnummer `63144073555301` ist korrekt** (EKP `6314407355` + Produkt `53` + Teilnahme `01`) und muss nicht geändert werden. --- ## P0-3 · Auslandssendungen in die Schweiz brauchen zusätzlich Zolldaten **Beleg:** 4 der gescheiterten Sendungen gehen nach `CHE`. Die Schweiz liegt außerhalb der EU-Zollunion. **Ursache:** Für `V53WPAK` in Drittländer verlangt die DHL-API einen `customs`-Block (ExportDocument/CN23) – siehe [parcel-de-shipping-v2_2.yaml:1059](dev/2026-05-13-dhl-modul/parcel-de-shipping-v2_2.yaml#L1059). Im gesamten Code existiert **kein** `customs`/`exportDocument`-Aufbau (`grep customs app/ packages/` → nur VIES-VAT). **Lösungsvorschlag:** Nach dem Fix aus P0-2 wird ein Teil der Sendungen weiterhin scheitern. Entweder (a) `ExportDocument` in [ShippingService::buildPayload()](packages/acme-laravel-dhl/src/Services/ShippingService.php#L535-L555) ergänzen (Positionen aus den Bestellzeilen: Beschreibung, Zolltarifnummer, Menge, Wert, Gewicht, Ursprungsland), oder (b) `CH` vorerst aus `DEFAULT_INTERNATIONAL_COUNTRIES` / dem Setting `dhl_international_countries` nehmen und dort manuell versenden. **Empfehlung:** (b) als Sofortmaßnahme, (a) als eigenes Ticket – nicht mit dem Hotfix vermischen. --- # P1 – Wichtig, sollte zeitnah behoben werden ## P1-1 · Log-Channel `userabo` existiert nicht → 109 EMERGENCY-Einträge **Beleg:** ``` laravel.EMERGENCY: Unable to create configured logger. Using emergency logger. InvalidArgumentException: Log [userabo] is not defined. ``` **Ursache:** `MyLog::writeLog('userabo', …)` wird an drei Stellen aufgerufen, aber [config/logging.php](config/logging.php) kennt den Channel nicht (definiert sind: `order_controller`, `abo_order`, `domain`, `payone`, `stripe`, `cleanup`, `payment`, `cron`, `dhl`). - [UserMakeAboOrder.php:293](app/Console/Commands/UserMakeAboOrder.php#L293) - [AboRetryPaymentService.php:202](app/Services/AboRetryPaymentService.php#L202) - [RetryFailedPaypalAbos.php:209](app/Console/Commands/RetryFailedPaypalAbos.php#L209) **Folge:** Jeder Zahlungsfehler erzeugt zwei Einträge statt einem, landet im Fallback-`laravel.log` statt in einer eigenen Datei und verschleiert die eigentliche Meldung. **Lösungsvorschlag:** An allen drei Stellen wird direkt darüber bereits sauber geloggt: ```php \Log::channel('abo_order')->error('UserMakeAboOrder: Zahlungsfehler', [...]); // existiert MyLog::writeLog('userabo', 'error', '…', $response); // Channel fehlt ``` Der `MyLog`-Aufruf dient hier nur noch dem Mailversand. Sauberste Variante: **`'userabo'` durch `'abo_order'` ersetzen** – dann fällt die doppelte Datei weg und der Mailversand bleibt erhalten. Wer die Trennung will, ergänzt stattdessen einen `userabo`-Daily-Channel in `config/logging.php`. --- ## P1-2 · `Route [user_order_detail] not defined` im Portal **Beleg:** 9× am 14.08.2026, `userId` 930. Stack: `resources/views/portal/abo/my_abo.blade.php:136` → `resources/views/admin/abo/_executions.blade.php:68`. **Ursache:** Multi-Tenant-Routing. `user_order_detail` ist nur in [routes/domains/crm.php:196](routes/domains/crm.php#L196) definiert. Die geteilte View `admin.abo._executions` wird aber auch auf der **Portal-Domain** gerendert, wo diese Route nicht geladen ist. In [routes/domains/portal.php](routes/domains/portal.php#L68-L72) steht sogar der passende Kommentar: ```php // Routen-Aliase für shared Views (admin.abo._detail, admin.abo._executions) Route::get('portal/my-data/edit/{id?}', …)->name('user_customer_edit'); Route::get('portal/my-orders/detail/{id}', …)->name('user_shop_order_detail'); // ← 'user_order_detail' fehlt ``` Der Alias für `is_for === 'ot'` wurde angelegt, der für `is_for === 'me'` vergessen. **Lösungsvorschlag:** Alias in `routes/domains/portal.php` ergänzen: ```php Route::get('portal/my-orders/own-detail/{id}', [OrderController::class, 'myOrderShow'])->name('user_order_detail'); ``` Zusätzlich in `_executions.blade.php` defensiv absichern (`@if (Route::has('user_order_detail'))`), da die View domainübergreifend genutzt wird. --- ## P1-3 · `user:cleanup` scheitert seit 7 Tagen an derselben E-Mail-Kollision **Beleg:** Täglich seit 20.08.2026, immer `user_id: 1212`: ``` SQLSTATE[23000]: Duplicate entry 'delete-kundin@beispiel.de' for key 'users.users_email_unique' ``` **Ursache:** [UserUtil::deleteUser()](app/Services/UserUtil.php#L180) präfixt die E-Mail fest mit `delete-`: ```php $user->email = 'delete-'.$user->email; ``` War dieselbe Adresse bereits einmal soft-deleted, kollidiert der zweite Lauf mit dem Unique-Index. Der Cleanup bricht für diesen User ab und wiederholt den Fehler jede Nacht – der Datensatz bleibt dauerhaft ungelöscht. **Lösungsvorschlag:** Präfix eindeutig machen und das Zurücksetzen anpassen. `str_replace('delete-', '', …)` reicht dann nicht mehr: ```php // UserUtil::deleteUser() $user->email = 'delete-'.$user->id.'-'.$user->email; // neuer Helper public static function stripDeletePrefix(string $email): string { return preg_replace('/^delete-(\d+-)?/', '', $email); } ``` Anzupassende Stellen: [UserUtil.php:211](app/Services/UserUtil.php#L211) (`checkEmailExists`), [UserUtil.php:229](app/Services/UserUtil.php#L229) (`restoreUser`), [AdminUserCleanupController.php:138](app/Http/Controllers/AdminUserCleanupController.php#L138), [AdminUserController.php:302](app/Http/Controllers/AdminUserController.php#L302). Zusätzlich: Länge der `users.email`-Spalte prüfen, das Präfix wächst um bis zu 13 Zeichen. Für User 1212 einmalig manuell aufräumen, damit der Cronlauf sauber durchläuft. **Test:** Unit-Test „zweimaliges Löschen derselben Adresse". --- ## P1-4 · E-Mail-Adressen mit Umlauten im local-part brechen Mailversand und Abo-Erinnerung **Belege:** ``` payment-2026-08-05.log: ERROR: Invalid billing email at shopping_order 59690 {"billing_email":"rüdiger.beispiel@web.de"} abo_order-2026-08-02.log: ERROR: AboSendExecutionReminder: Versand fehlgeschlagen {"abo_id":519,"error":"Invalid addresses: non-ASCII characters not supported in local-part of email."} ``` **Ursache:** Solche Adressen kommen ins System, weil bei der Registrierung/Bestellung nicht gegen ASCII validiert wird. `filter_var(FILTER_VALIDATE_EMAIL)` und Symfony Mailer lehnen sie später ab. - [Payment.php:349-352](app/Services/Payment.php#L349-L352) fängt den Fall ab und schickt an `config('app.checkout_mail')` – **der Kunde erhält seine Zahlungsbestätigung nie.** - `AboSendExecutionReminder` fängt ihn **nicht** ab, die Ausführungserinnerung fällt komplett aus. **Lösungsvorschlag:** 1. Bei der Eingabe validieren – eigene Rule `AsciiEmail`, angewandt auf Registrierung, Checkout und Profil-Bearbeitung. Verhindert neue Fälle. 2. `AboSendExecutionReminder` genauso absichern wie `Payment::paymentStatusSendMail()` (try/catch + Log), damit ein Datensatz nicht den ganzen Lauf abbricht. 3. Bestand einmalig prüfen: `SELECT id, email FROM users WHERE email <> CONVERT(email USING ASCII);` und die Betroffenen anschreiben bzw. korrigieren. --- ## P1-5 · Versand scheitert an zu langen Feldern statt sie zu bereinigen **Belege:** ``` 3× [DHL Controller] Failed to dispatch shipment creation {"error":"shipping phone darf maximal 20 Zeichen haben.","order_id":"59996"/"60027"/"60837"} 1× [DHL Service] Shipment creation failed (sync) {"order_id":60496,"error":"consignee.name2 darf maximal 50 Zeichen haben."} 3× DHL: "Parameter refNo (Shipment reference number) must be between 8 and 35 characters long." → gesendet wurde refNo "NL" (2 Zeichen) 2× DHL: "Please enter a valid phone number." → gesendet wurde z. B. "03301717813888" ``` **Ursache:** Die Telefonnummer ist für DHL ein **optionales** Feld. Trotzdem bricht [DhlShipmentController.php:301](app/Http/Controllers/DhlShipmentController.php#L301) (`'shipping_phone' => 'nullable|string|max:20'`) die komplette Sendungserstellung ab, wenn der Kunde die Nummer mit Leerzeichen/Bindestrichen/Vorwahl-Klammern eingegeben hat. Bei `refNo` fehlt umgekehrt die Mindestlänge – ein manuell eingetragenes „NL" geht ungeprüft an die API. **Lösungsvorschlag:** In [DhlAddressValidator](app/Services/DhlAddressValidator.php#L84) bzw. `DhlModalService::prepareAddressForApi()` normalisieren statt validieren: - Telefon: Sonderzeichen entfernen (`preg_replace('/[^\d+]/', '', $phone)`), auf 20 Zeichen kürzen; ist das Ergebnis unplausibel (< 6 Ziffern), Feld **weglassen** statt die Sendung scheitern zu lassen. - `name2`/`name1`: auf die API-Grenzen kürzen (`Str::limit(…, 50, '')`). - `refNo`: Fallback `Order-{id}` verwenden, sobald die eingegebene Referenz kürzer als 8 Zeichen ist; `'reference' => 'nullable|string|min:8|max:35'` als Validierung ergänzen ([DhlShipmentController.php:289](app/Http/Controllers/DhlShipmentController.php#L289)). Grundsatz: Ein schlecht formatiertes optionales Feld darf keinen Versand blockieren. --- # P2 – Optimierungen / Log-Rauschen ## P2-1 · `MyLog::writeLog()` verschickt Mails synchron [MyLog.php:31-33](app/Services/MyLog.php#L31-L33): ```php if ($mail) { Mail::to(config('app.exception_mail'))->send(new MailLog(...)); // blockierend } ``` `MailLog` nutzt bereits das `Queueable`-Trait, wird aber mit `send()` statt `queue()` verschickt. Bei jedem Zahlungsfehler wartet der Cron auf den SMTP-Roundtrip. **Sichtbare Folge:** `UserMakeAboOrder` läuft an Abrechnungstagen **250 s (20.08.)** bzw. **234 s (25.08.)**, an allen anderen Tagen ~50 ms. Und am 25.08. um 04:00 kippte genau dort der SMTP-Server weg: `Connection to "w017f6e4.kasserver.com:587" has been closed unexpectedly.` **Lösungsvorschlag:** `Mail::to(...)->queue(new MailLog(...))` – Horizon läuft ohnehin. Zusätzlich den Aufruf in try/catch kapseln, damit ein Mailfehler nie den Zahllauf abbricht. --- ## P2-2 · PAYONE-Fehlerbilder aus den Zahlläufen 89 Fehlerantworten in `UserMakeAboOrder`, 18 in `AboRetryPaymentService`. Nach Ursache getrennt: | Code | Anzahl | Bewertung | | --- | --- | --- | | `923` Payment type not available for this currency or card type | 39 | **Alle am 05.04.2026**, danach nie wieder → einmaliges Konfigurationsproblem, erledigt | | `950` Desired status change not possible for this payment process | 20 | Letzter Fall **22.07.2026** → durch das PAYONE-Refactoring offenbar behoben; nach Deploy des aktuellen Branch beobachten | | `5` / `4` / `43` / `56` / `132` Authorization declined, Card stolen … | 22 | Normale Kartenablehnungen, kein Bug | | `cardexpiredate faulty` | 3 | Abgelaufene Karte – hier wäre eine **proaktive Erinnerung** vor Ablaufdatum sinnvoll | | Stripe `card_declined` / `insufficient funds` | 4 | Normale Ablehnung | **Optimierung:** Kartenablehnungen sind Geschäftsvorfälle, keine Anwendungsfehler. Sie sollten als `warning` in `abo_order` laufen und nicht per Mail an `exception_mail` gehen – sonst geht der Blick für echte Fehler verloren. Nur „technische" Codes (`923`, `950`, HTTP-/Timeout-Fehler) sollten alarmieren. Zusätzlich: Ein Job, der Abos mit demnächst ablaufender Karte erkennt und den Kunden vorab zur Aktualisierung auffordert, würde die 3 `cardexpiredate`-Fälle und einen Teil der Wiederholungsläufe vermeiden. --- ## P2-3 · DHL-Tracking: 21 HTTP-500 der DHL-Unified-API ``` WARNING: [DHL Tracking Service] Unified API returned unexpected status {"tracking_number":"003404350651331…","status_code":500, "detail":"The server encountered an unexpected condition…"} ``` Fremdsystemfehler, kein eigener Bug. Aktuell wird pro Versuch geloggt, ohne Backoff. **Lösungsvorschlag:** Retry mit exponentiellem Backoff (`Http::retry(3, 200)`), erst nach dem letzten Fehlversuch als `warning` loggen. Sendungen, die dauerhaft 500 liefern, in ein Feld `tracking_last_error` schreiben statt jeden Lauf neu zu protokollieren. --- ## P2-4 · Stripe-Webhooks ohne zuordenbare Zahlung 17× `checkout.session.expired`, ausnahmslos ohne `metadata.shopping_payment_id`. [StripeWebhookHandler.php:181-188](app/Services/Payment/Stripe/StripeWebhookHandler.php#L181-L188) loggt das als `warning` und gibt korrekt `true` zurück (Event gilt als verarbeitet). Abgelaufene Checkout-Sessions ohne zugehörige Zahlung sind der Normalfall bei abgebrochenen Bestellungen – kein Fehler. **Lösungsvorschlag:** Für `checkout.session.expired` (und `checkout.session.async_payment_failed`) auf `info` herabstufen, `warning` nur für Eventtypen, bei denen eine Zahlung existieren *muss*. --- ## P2-5 · `KasController` läuft auf dem Server in einen Fatal Error **Beleg:** 9× am 24./25.08.2026: ``` FatalError: SOAP-ERROR: Parsing WSDL: Couldn't load from 'https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl' at app/Http/Controllers/Api/KasController.php:75 ``` **Zwei Punkte:** 1. Die Datei existiert im aktuellen Branch **nicht mehr** – sie wurde mit Commit `d4e9d10` entfernt und liegt nur noch unter `dev/app-bak/`. Auf dem Server ist sie also noch deployt. **Deploy-Stand prüfen.** 2. Der Code behandelt SOAP-Fehler mit `trigger_error(..., E_USER_ERROR)` ([dev/app-bak/.../KasController.php:60](dev/app-bak/Http/Controllers/Api/KasController.php#L60)) – das erzeugt einen nicht abfangbaren Fatal Error statt einer 5xx-Antwort. Falls der Controller wieder gebraucht wird: `SoapFault` per try/catch fangen und eine JSON-Fehlerantwort liefern. **Kontext:** Alle Vorfälle liegen im Fenster 24.08. 23:35 – 25.08. 00:25, zeitgleich mit `Connection could not be established with host "w017f6e4.kasserver.com:587"` und `[payment:check-uptime] Outage-Incident #4 für payone` (24.08. 23:50). → Eine Netzwerkstörung beim Hoster, kein Anwendungsfehler. Nur die fehlende Fehlerbehandlung ist unser Problem. --- ## P2-6 · ImageMagick verbietet PDF-Verarbeitung **Beleg:** 13.08.2026, 10:31: ``` ERROR: PDF Verarbeitung fehlgeschlagen: attempt to perform an operation not allowed by the security policy `PDF' @ error/constitute.c/IsCoderAuthorized/426 ERROR: Thumbnail creation failed: … ``` **Ursache:** Serverkonfiguration, nicht Code. Die ImageMagick-Policy (`/etc/ImageMagick-6/policy.xml`) sperrt den PDF-Coder – Standard seit CVE-2018-16509. **Lösungsvorschlag:** Auf dem Server das PDF-`rights="none"` in der Policy auf `read|write` setzen und Ghostscript aktuell halten. Im Code zusätzlich prüfen, ob die PDF-Verarbeitung verfügbar ist, und bei fehlender Berechtigung ein Platzhalter-Thumbnail liefern statt zu scheitern. --- ## P2-7 · Passport: nicht unterstützter Grant Type 5 Vorfälle (17.08., 22.08. 04:03 und 05:53, …): `Laravel\Passport\Exceptions\OAuthServerException: The authorization grant type is not supported`. Kein Stacktrace bis in Anwendungscode. Vermutlich ein API-Client, der einen nicht aktivierten Grant (z. B. `password`) anfordert, oder Scan-Traffic auf `/oauth/token`. **Lösungsvorschlag:** In [AuthServiceProvider](app/Providers/AuthServiceProvider.php) prüfen, welche Grants freigeschaltet sind, und mit den API-Clients abgleichen. Falls es Fremdtraffic ist: `/oauth/token` als 4xx behandeln und nicht als `ERROR` loggen (Rate-Limit ergänzen). --- ## P2-8 · Logrotation und Log-Rauschen **`laravel.log`** ist 572 KB groß und enthält Einträge von **05.04.2026 bis 25.08.2026** – die tägliche Rotation greift hier nicht, weil `MyLog` über nicht existierende Channels in den Fallback schreibt (siehe P1-1). Nach dem Fix von P1-1 löst sich das mit auf; zusätzlich `LOG_CHANNEL=daily` und `LOG_DAILY_DAYS` prüfen. **`laravel-2026-08-24.log`** ist 972 KB bei nur **50 Fehlern** – 5.868 von 6.054 Zeilen sind Stacktraces. Die 43 identischen `contains()`-Traces machen den Großteil aus; mit P0-1 verschwindet das. **Cron-Rauschen:** Die Zahlungserinnerungen loggen jeden geprüften Benutzer auf `INFO`: | Anzahl | Meldung | | --- | --- | | 488 | `Prüfe Zahlungserinnerungen für Benutzer` | | 429 | `Erinnerung bereits gesendet für Benutzer` | | 189 | `Zweite Erinnerung für Benutzer` | | 146 | `Inaktives Konto für Benutzer` | Über 1.200 Zeilen pro Woche für ~59 tatsächlich versandte Mails. **Lösungsvorschlag:** „geprüft"/„bereits gesendet"/„inaktiv" auf `debug` setzen, am Ende des Laufs eine Zusammenfassung auf `INFO` schreiben (`geprüft: X, versendet: Y, übersprungen: Z`). Tatsächlich versandte Erinnerungen bleiben auf `INFO`. --- # Hinweise ohne Handlungsbedarf - **`BusinessStoreOptimized: NOT EXECUTED - wrong day (expected: 1, today: N)`** – korrektes Verhalten, die Provisionsberechnung läuft nur am 1. des Monats. Der Eintrag könnte auf `debug`. - **`User #… attempted to create customer abo but one is already initiated`** (16× in `order_controller`) – die Schutzlogik greift wie vorgesehen. Auffällig ist nur, dass mehrere User es zweimal versuchen; ggf. ist die Rückmeldung in der Oberfläche zu unauffällig. - **`Shipping country not found for user #1056, country: ` (leer)** – 2×, Stammdaten ohne Land. Einzelfall, Datensatz korrigieren. - **`SponsorBuyingPointsRepair: VP-Buchung liegt bei anderem Berater — manuell pruefen (Umhaengung?)`** (26.08., `shopping_order_id: 58797`, `buyer_id: 1426`, `current_sponsor_id: 1241`, `booked_sponsor_id: 6`) – bewusst als Warnung ausgelegt, verlangt fachliche Prüfung, keinen Code-Fix. - **`payments:check-accounts`** läuft täglich in 0–2 s durch, ohne Fehler. --- # Empfohlene Reihenfolge | # | Maßnahme | Aufwand | Wirkung | | --- | --- | --- | --- | | 1 | P0-1 `collect()` im Blade | Minuten | Abo-Anlage im Portal funktioniert wieder | | 2 | P0-2 `V53PAK` → `V53WPAK` | ~1 h inkl. Tests | Auslandsversand funktioniert wieder | | 3 | P1-3 `delete-`-Kollision | ~1 h | Nächtlicher Cleanup läuft wieder durch | | 4 | P1-1 Log-Channel `userabo` | Minuten | 109 EMERGENCY-Einträge weg, saubere Logs | | 5 | P1-2 Portal-Alias `user_order_detail` | Minuten | Kein 500 mehr auf „Meine Abos" | | 6 | P1-5 DHL-Felder normalisieren | ~2 h | Keine blockierten Sendungen durch Telefonnummern | | 7 | P1-4 ASCII-Validierung E-Mail | ~2 h | Kunden erhalten ihre Bestätigungen | | 8 | P2-1 Mailversand in die Queue | ~30 min | Zahllauf von 250 s auf Sekunden | | 9 | P0-3 Zolldaten CH | eigenes Ticket | Schweiz-Versand | | 10 | P2-3/4/8 Log-Level und Retries | ~2 h | Auswertbare Logs |