# Payment Dashboard – Entwicklungsplan **Ziel:** Integration eines Payment-Monitoring-Dashboards in mivita.care, das PAYONE-Probleme frühzeitig sichtbar macht. Das Dashboard kombiniert manuell erfasste Incidents mit echten Daten aus den bestehenden Tabellen (`payment_transactions`, `shopping_payments`) und den vorhandenen PAYONE-Logs. --- ## Implementierungsstand (Stand: 13.04.2026) | Phase | Thema | Status | Anmerkungen | |-------|------------------------------|-------------------|-------------| | 1 | Datenbank & Models | ✅ Umgesetzt | Migration, alle drei Models vorhanden | | 2 | Controller & Routes | ✅ Umgesetzt | Routes in `routes/domains/crm.php`, Entwurfsdatei unter `dev/` ist veraltet | | 3 | Views (Bootstrap/Appwork) | ✅ Umgesetzt | Index, Management, Show, Transactions, Logs + 4 Partials | | 4 | Live-Transaktionsdaten | ✅ Umgesetzt | `transactions()` + View mit Filtern, Statistiken, Pagination | | 5 | Log-Viewer | ✅ Umgesetzt | `?date=`-Parameter wird ausgewertet; sichere Validierung der Datumseingabe | | 6 | Uptime-Check Artisan Command | ✅ Umgesetzt | Command `payment:check-uptime`, Schedule alle 5 Minuten, Uptime-Karten im Dashboard | | 7 | E-Mail-Benachrichtigung | ✅ Umgesetzt | Dediziertes Mailable `PaymentIncidentAlert` (Queue), sendet bei `critical`-Incidents | | 8 | Tests | ✅ Umgesetzt | `PaymentDashboardAccessTest`, `PaymentIncidentCrudTest`, `CheckPaymentUptimeCommandTest` | ### Zusätzlich umgesetzt (nicht in Phasen geplant) - **Navigation:** Sidenav-Link mit gecachtem Badge für offene Incidents in `layout-sidenav.blade.php` - **Übersetzungen:** `payment_monitor` in `de`, `en`, `es` navigation.php - **Form Requests:** `StorePaymentIncidentRequest` + `AddIncidentActivityRequest` in `app/Http/Requests/PaymentIncident/` - **Cache-Invalidierung:** `Cache::forget('open_incident_count')` in `store()`, `updateStatus()`, `addActivity()` ### Bekannte Abweichungen vom Plan - **Views:** Nutzen `layouts.layout-2` statt `layout-1` (wie im Plan angegeben) - **Entwurfsdatei** `dev/payment-dashboard/routes/payment-dashboard.php`: Veraltet — falscher Namespace (`App\Http\Controllers\PaymentDashboardController`), falsche Methodennamen (`developer` statt `index`), nur `auth`-Middleware statt `admin` ### Offene Punkte 1. **Phase 9 (Auto-Incident aus `PayoneController`):** Noch nicht begonnen — kritische Fehler (z.B. Error:2008) könnten automatisch Incidents anlegen 2. **Entwurfsdatei aufräumen:** `dev/payment-dashboard/routes/payment-dashboard.php` ggf. löschen oder als Archiv kennzeichnen --- ## Analyse des Ist-Zustands ### Bereits vorhandene Datenquellen | Quelle | Relevanz | |-------------------------------|-------------------------------------------------------------------| | `payment_transactions` | Jeder PAYONE-Callback: `txaction`, `errorcode`, `errormessage`, `mode`, `transmitted_data` | | `shopping_payments` | Jede Zahlung: `reference`, `txaction`, `clearingtype`, `amount` | | `shopping_orders` | Bestellstatus, `paid`-Flag, Verknüpfung zu Payments | | `storage/logs/payone.log` | Fehler-Details aus `MyLog::writeLog('payone', ...)` (Error 2001–2008) | | `storage/logs/payment.log` | Allgemeine Zahlungsfehler | ### Bestehende PAYONE-Fehlercodes (aus `Api/PayoneController`) | Code | Bedeutung | |--------|------------------------------------------------| | 2001 | Callback: Parameter unvollständig | | 2002 | Callback: Key-Validierung fehlgeschlagen | | 2003 | Callback: ShoppingOrder nicht gefunden | | 2004 | Callback: ShoppingPayment nicht gefunden | | 2005 | Callback: Payment ↔ Order Zuordnung falsch | | 2006 | Callback: Preisabweichung | | 2008 | Callback: Datenbank-Transaktion fehlgeschlagen | ### Wichtige Unterschiede zum Entwurf - **Layout:** Das Dashboard nutzt das bestehende Appwork/Bootstrap-Layout (`layout-1.blade.php`), nicht das Custom-Dark-Mode-Layout aus dem Entwurf. - **Auth:** Middleware `admin` (admin >= 2) statt nur `auth` — kein separates Rollen-System nötig. - **Echte Daten:** Statt rein manuelle Incidents, werden bestehende Transaktionsdaten direkt eingebunden (Live-Tab). Manuelle Incidents bleiben als Eskalationswerkzeug erhalten. - **Logs:** Der `payone`-Log-Kanal wird direkt im Dashboard lesbar gemacht. --- ## Phasen-Übersicht | Phase | Titel | Aufwand | Priorität | |-------|------------------------------|----------|-----------| | 1 | Datenbank & Models | ~1h | Muss | | 2 | Controller & Routes | ~2h | Muss | | 3 | Views (Bootstrap/Appwork) | ~3h | Muss | | 4 | Live-Daten: Transaktionen | ~2h | Hoch | | 5 | Log-Viewer | ~1h | Hoch | | 6 | Uptime-Check Artisan Command | ~2h | Mittel | | 7 | E-Mail-Benachrichtigung | ~1h | Mittel | | 8 | Tests | ~2h | Muss | --- ## Phase 1: Datenbank & Models ✅ ### Migration (aus dem Entwurf übernehmen, leicht angepasst) **Datei:** `database/migrations/YYYY_MM_DD_000001_create_payment_incidents_table.php` Drei neue Tabellen: - `payment_incidents` — manuell erfasste Störungen/Incidents - `incident_activities` — Kommunikationsverlauf pro Incident (Notizen, Tickets, Anrufe) - `provider_uptime_logs` — automatische Uptime-Checks (Phase 6) Anpassungen zum Entwurf: - `provider`-Enum zunächst auf `payone` fokussiert (andere Provider ergänzen, wenn aktiv genutzt) - `notes`-Feld ergänzen in `payment_incidents` für freie interne Kommentare - Index auf `detected_at` und `provider` für Performance bei größeren Datenmengen ### Models **`app/Models/PaymentIncident.php`** — aus dem Entwurf übernehmen, ergänzen um: - `scopeOpen()` / `scopePayone()` / `scopeLastDays(int $days)` für häufige Abfragen - `getTypeIconAttribute()` für die View-Darstellung **`app/Models/IncidentActivity.php`** — direkt aus dem Entwurf übernehmen **`app/Models/ProviderUptimeLog.php`** — neu erstellen für Phase 6 --- ## Phase 2: Controller & Routes ✅ ### Controller-Struktur ``` app/Http/Controllers/Admin/PaymentDashboardController.php ``` Platzierung unter `Admin/`, konsistent mit `PaymentSalesController.php`. #### Actions | Method | URL | Beschreibung | |-----------|-----------------------------------------|--------------------------------------------| | `index` | GET `/admin/payment-dashboard` | Entwickler-Ansicht: alle Incidents + Stats | | `management` | GET `/admin/payment-dashboard/management` | GF-Ansicht: Ampel-Karten, kein Bearbeiten | | `show` | GET `/admin/payment-dashboard/{incident}` | Incident-Detail mit Timeline | | `store` | POST `/admin/payment-dashboard` | Neuen Incident anlegen | | `addActivity` | POST `/admin/payment-dashboard/{incident}/activity` | Aktivität hinzufügen | | `updateStatus` | PATCH `/admin/payment-dashboard/{incident}/status` | Status ändern | | `transactions` | GET `/admin/payment-dashboard/transactions` | Live-PAYONE-Transaktionen (Phase 4) | | `logs` | GET `/admin/payment-dashboard/logs` | PAYONE-Log-Viewer (Phase 5) | #### Stats-Methoden im Controller Zusätzlich zu den Stats aus dem Entwurf: - `getPayoneTransactionStats()` — Fehlerquote der letzten 7/30 Tage aus `payment_transactions` - `getFailedPayments()` — alle Payments mit `txaction = 'failed'` der letzten 30 Tage - `getErrorDistribution()` — Häufigkeit der Fehlercodes 2001–2008 aus den Logs ### Routes In `routes/web.php` ergänzen (nicht als separates File, analog zu bestehender Struktur): ```php Route::prefix('admin/payment-dashboard') ->name('admin.payment-dashboard.') ->middleware(['auth', 'admin']) ->group(function () { Route::get('/', [PaymentDashboardController::class, 'index'])->name('index'); Route::get('/management', [PaymentDashboardController::class, 'management'])->name('management'); Route::get('/transactions', [PaymentDashboardController::class, 'transactions'])->name('transactions'); Route::get('/logs', [PaymentDashboardController::class, 'logs'])->name('logs'); Route::get('/{incident}', [PaymentDashboardController::class, 'show'])->name('show'); Route::post('/', [PaymentDashboardController::class, 'store'])->name('store'); Route::post('/{incident}/activity', [PaymentDashboardController::class, 'addActivity'])->name('activity.store'); Route::patch('/{incident}/status', [PaymentDashboardController::class, 'updateStatus'])->name('status.update'); }); ``` --- ## Phase 3: Views (Bootstrap/Appwork) ✅ ### Layout-Anpassung **Kein** Custom-Dark-Mode-Layout. Stattdessen: `@extends('layouts.layout-1')`, analog zu anderen Admin-Views (z.B. `resources/views/admin/payment/salesvolume.blade.php`). ### View-Struktur ``` resources/views/admin/payment-dashboard/ ├── index.blade.php # Entwickler-Ansicht ├── management.blade.php # GF-Ansicht ├── show.blade.php # Incident-Detail ├── transactions.blade.php # Live-Transaktionen (Phase 4) ├── logs.blade.php # Log-Viewer (Phase 5) └── _partials/ ├── stats-cards.blade.php ├── incident-table.blade.php ├── activity-timeline.blade.php └── create-incident-modal.blade.php ``` ### UI-Komponenten **Entwickler-Ansicht (`index.blade.php`):** - Stat-Karten oben: Offene Incidents | In Bearbeitung | PAYONE-Fehler (30 Tage) | Betroffener Umsatz - Tabs: "Incidents" | "Live-Transaktionen" | "PAYONE Logs" - Tabelle: Alle Incidents mit Status-Badge, Schwere-Farbe, Dauer, Quick-Status-Update - Floating Button: "Neuen Incident anlegen" → Bootstrap-Modal **GF-Ansicht (`management.blade.php`):** - Ampel-Karten: Grün/Gelb/Rot je nach offenen Incidents und Schwere - Sehr einfach, keine Bearbeitungsfunktionen - Tagesaktuelle Zusammenfassung **Incident-Detail (`show.blade.php`):** - Kopfbereich: Titel, Provider-Badge, Status-Badge, Severity-Indikator - Timeline: Chronologische Aktivitätsliste mit Icon je Typ - Formulare: Aktivität hinzufügen, Status ändern - Link zur betroffenen Bestellung (falls `ticket_number` eine Bestell-ID ist) --- ## Phase 4: Live-Transaktionsdaten ✅ Das wertvollste Feature: Echtzeit-Einblick in PAYONE-Transaktionen aus bestehenden Tabellen. ### Tab "Live-Transaktionen" in der Entwickler-Ansicht Datenquelle: `payment_transactions` JOIN `shopping_payments` JOIN `shopping_orders` **Anzeigen:** - Alle Transaktionen der letzten 7 Tage, gefiltert nach `txaction` - Fehlgeschlagene Transaktionen (`txaction = 'failed'`) hervorgehoben in Rot - Fehlercodes und Fehlermeldungen aus `errorcode` / `errormessage` / `customermessage` - `mode`-Feld: unterscheidet Test vs. Live-Modus (wichtig für Debugging) - `transmitted_data` JSON: aufklappbar für Detailinspektion **Filter-Optionen:** - Nach `txaction`: `appointed`, `pending`, `paid`, `failed` - Nach Zeitraum: Heute / Letzte 7 Tage / Letzte 30 Tage - Nach Modus: Test / Live **Stat-Block oben:** - Erfolgsrate (paid / gesamt) der letzten 24h - Anzahl `failed` Transaktionen heute - Letzte `failed` Transaktion: vor X Minuten - Verteilung der `clearingtype` (wlt, cc, elv…) ### Neue Methode im Controller ```php private function getTransactionStats(int $days = 7): array { $since = now()->subDays($days); return [ 'total' => PaymentTransaction::where('created_at', '>=', $since)->count(), 'failed' => PaymentTransaction::where('txaction', 'failed')->where('created_at', '>=', $since)->count(), 'paid' => PaymentTransaction::where('txaction', 'paid')->where('created_at', '>=', $since)->count(), 'errors' => PaymentTransaction::whereNotNull('errorcode')->where('created_at', '>=', $since) ->select('errorcode', 'errormessage', DB::raw('count(*) as count')) ->groupBy('errorcode', 'errormessage') ->orderByDesc('count') ->get(), 'last_failed' => PaymentTransaction::where('txaction', 'failed')->latest()->first(), ]; } ``` --- ## Phase 5: Log-Viewer ✅ ### Tab "PAYONE Logs" in der Entwickler-Ansicht Liest direkt aus `storage/logs/payone.log` (aktuellste Datei bei daily rotation). **Anzeigen:** - Letzte 100 Log-Einträge (konfigurierbar) - Farbliche Markierung nach Level: `error` (rot), `warning` (gelb), `info` (blau), `notice` (grau) - Suche/Filter nach Fehlercode (z.B. "Error:2003") - Zeitstempel, Log-Level, Nachricht, JSON-Payload (aufklappbar) **Implementierung:** ```php public function logs(): View { $logPath = storage_path('logs/payone-' . now()->format('Y-m-d') . '.log'); $entries = []; if (file_exists($logPath)) { $lines = array_reverse(file($logPath)); foreach (array_slice($lines, 0, 200) as $line) { if (preg_match('/\[(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[^\]]*)\] \w+\.(\w+): (.+)/', $line, $m)) { $entries[] = [ 'timestamp' => $m[1], 'level' => $m[2], 'message' => $m[3], ]; } } } return view('admin.payment-dashboard.logs', compact('entries')); } ``` --- ## Phase 6: Artisan Command – Uptime-Check ✅ ### `php artisan payment:check-uptime` **Datei:** `app/Console/Commands/CheckPaymentUptime.php` Prüft erreichbare PAYONE-Endpunkte (Status-API oder bekannte öffentliche URLs) und legt bei Ausfall automatisch einen Incident an. ```php // Prüft: PAYONE Server-Status-Seite oder einen konfigurierbaren Health-Endpoint // Speichert Ergebnis in provider_uptime_logs // Bei Ausfall: erstellt PaymentIncident mit severity = 'critical', type = 'outage' // Bei Wiederherstellung: setzt offene Outage-Incidents auf 'resolved' ``` **Scheduling in `app/Console/Kernel.php`:** ```php $schedule->command('payment:check-uptime')->everyFiveMinutes(); ``` **Konfiguration in `.env` / `config/services.php`:** ``` PAYONE_HEALTH_CHECK_URL=https://api.pay1.de/post-gateway/ PAYONE_HEALTH_CHECK_ENABLED=true ``` --- ## Phase 7: E-Mail-Benachrichtigung ✅ Bei neu eröffnetem Critical-Incident: automatische Mail via bestehenden `MyLog`-Mechanismus. ```php // In PaymentDashboardController::store() if ($validated['severity'] === 'critical') { MyLog::writeLog( 'payment', 'error', 'Kritischer Zahlungs-Incident eröffnet: ' . $validated['title'], $validated, true // sendet Mail an config('app.exception_mail') ); } ``` Alternativ: Dedizierte Mailable `App\Mail\PaymentIncidentAlert` für bessere Darstellung. --- ## Phase 8: Tests ✅ (vollständig) **Feature-Tests:** ``` tests/Feature/PaymentDashboard/ ├── PaymentDashboardAccessTest.php # Auth, Admin-Middleware ├── PaymentIncidentCrudTest.php # Create, Status-Update, Aktivität ├── PaymentDashboardStatsTest.php # Korrekte Stats aus Testdaten └── CheckPaymentUptimeCommandTest.php # Artisan Command (Phase 6) ``` **Testszenarien:** - Nicht eingeloggter User → Redirect - Eingeloggter User ohne Admin (admin < 2) → 403 - Admin (admin >= 2) → Zugriff auf Entwickler-Ansicht - Incident anlegen: Pflichtfelder, korrekte Aktivität wird auto-erstellt - Status auf "resolved" setzen → `resolved_at` wird gesetzt - Stats-Methoden liefern korrekte Werte mit Testdaten --- ## Implementierungsreihenfolge (empfohlen) ``` Phase 1 (Migration + Models) → Phase 2 (Routes + Controller-Grundstruktur) → Phase 3 (Views mit Bootstrap, ohne Echtdaten) → Phase 4 (Live-Transaktionsdaten einbauen) → Phase 5 (Log-Viewer) → Phase 8 (Tests für Phasen 1–5) → Phase 6 (Uptime-Check, optional) → Phase 7 (Benachrichtigung, optional) ``` --- ## Datenbankzugriff auf bestehende Daten – kein Code-Eingriff nötig Phase 4 liest nur lesend aus bestehenden Tabellen. Es sind **keine Änderungen** an: - `Api/PayoneController` (PAYONE-Callback-Handler) - `ShoppingPayment`, `PaymentTransaction` (Models) - bestehenden Migrations --- ## Navigation (`layout-sidenav.blade.php`) **Datei:** `resources/views/layouts/includes/layout-sidenav.blade.php` Der Eintrag kommt als letztes Item im bestehenden `admin/payments`-Untermenü (nach "Steuerberater", aktuell Zeile ~372). **Kein** eigener Top-Level-Eintrag — das Dashboard gehört thematisch zu Payments. ```blade