feat(superadmin): migrate internal docs from docusaurus to guava kb
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-02-07 09:58:39 +01:00
parent 1d2242fb4d
commit fb45d1f6ab
77 changed files with 3813 additions and 18636 deletions

View File

@@ -0,0 +1,205 @@
---
title: Billing & Zahlungs-Operationen
---
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Lemon Squeezy/RevenueCatWebhooks und PaketInkonsistenzen operativ umzugehen ist.
## 1. Komponentenüberblick
- **Lemon Squeezy**
- Abwicklung von WebCheckout, Paketen und Subscriptions.
- Webhooks für Bestellungen, Verlängerungen, Stornos.
- **Fotospiel Backend**
- Modelle wie `Tenant`, `Packages`, `tenant_packages`, `event_packages`.
- Services/Jobs zur PaketZuweisung, LimitBerechnung und Nutzungstracking.
> Details zur Architektur findest du in den PRPKapiteln (Billing/Freemium) sowie in den bd-Issues zur Lemon SqueezyMigration und zum KatalogSync.
## 2. Typische Problemszenarien
- **Webhook kommt nicht an / schlägt fehl**
- Symptom: Lemon Squeezy zeigt Zahlung „completed/paid“, TenantPaket im Backend bleibt unverändert.
- Checkliste:
- Logs der WebhookRoutes prüfen (Statuscodes, Exceptions).
- Endpoint: `POST /lemonsqueezy/webhook``LemonSqueezyWebhookController::handle()`.
- Controller ruft `CheckoutWebhookService::handleLemonSqueezyEvent()` auf.
- WebhookReplay über das Lemon Squeezy Dashboard auslösen (für einzelne Events).
- QueueStatus prüfen:
- Falls Webhooks über Queues verarbeitet werden, auf `default`/`billing`Queues achten (je nach Konfiguration).
- **Doppelte oder fehlende Abbuchungen**
- Abgleich von ZahlungsproviderDaten (Lemon Squeezy/RevenueCat) mit internem Ledger.
- Bei doppelten Buchungen: Prozess definieren (Refund via Lemon Squeezy, Anpassung im Ledger).
- Bei fehlenden Buchungen: ggf. manuelle Paketzuweisung nach erfolgter Zahlung.
- **Pakete/Limits passen nicht zur Realität**
- Tenant meldet: „Paket falsch“, „Galerie schon abgelaufen“ o.Ä.
- Prüfen:
- Aktives Paket (`tenant_packages`, `event_packages`).
- LimitZähler (`used_photos`, `used_events`) und aktuelle Nutzung.
- Letzte relevante Webhooks/Jobs (z.B. vor kurzem migriert?).
## 3. Operative Schritte bei Payment Incidents
1. **Event/Tenant identifizieren**
- IDs und relevante PaketInfos aus DB/Admin UI holen.
2. **Provider-Status prüfen**
- Lemon SqueezyDashboard: ist die Zahlung dort korrekt verbucht? (Order/SubscriptionAnsicht).
3. **Backend-Status prüfen**
- Paketzuweisung und Limits in der DB (Readonly!) inspizieren:
- `checkout_sessions` wurde die Session korrekt auf `completed` gesetzt? (`provider = lemonsqueezy`, `lemonsqueezy_order_id` gefüllt?)
- `package_purchases` existiert ein Eintrag für Tenant/Package mit erwarteter ProviderReferenz?
- `tenant_packages` stimmt der `active`Status und `expires_at` mit dem erwarteten Abostatus überein?
4. **Entscheidung**
- Automatische Nachverarbeitung via WebhookReplay/JobRetry:
- Lemon SqueezyEvents erneut senden lassen, ggf. `tests/api/_testing/checkout/sessions/{session}/simulate-lemonsqueezy` (in TestUmgebungen) nutzen.
- Notfall: manuelle PaketAnpassung (nur mit klar dokumentierter Begründung):
- Paket in `tenant_packages` aktivieren/verlängern und `package_purchases` sauber nachziehen.
5. **Dokumentation**
- Vorgang im Ticket oder als bd-Issue festhalten, falls wiederkehrend.
> TODO: Ergänze konkrete Tabellen-/Modellnamen und die relevanten Jobs/Artisan Commands, sobald Lemon Squeezy/RevenueCat Migration finalisiert ist.
## 4. Zusammenarbeit mit Finance/Support
- Klar definieren, wer Rückerstattungen freigibt und durchführt.
- Playbook für Support:
- Welche Informationen sie sammeln sollen, bevor sie an Ops eskalieren (TenantID, EventID, PaymentProviderReferenz, Zeitstempel).
- Welche Standardantworten es gibt (z.B. „Zahlung in Prüfung, Paket kurzfristig manuell freigeschaltet“).
## 5. Hinweise zur Implementierung
- **Konfiguration**
- Lemon SqueezyKeys, WebhookSecrets und FeatureFlags sollten ausschließlich in `.env`/ConfigDateien liegen und niemals im Code/Logs landen.
- Sandbox vs. LiveKeys klar trennen; Ops sollte wissen, welche Umgebung gerade aktiv ist.
- **Sicherheit**
- WebhookSignaturen und Timestamps prüfen; bei verdächtigen Mustern (z.B. ReplayAngriffe) SecurityRunbooks konsultieren.
- Keine sensiblen PaymentDetails in ApplikationsLogs ausgeben.
Diese Sektion ist bewusst generisch gehalten, damit sie auch nach Implementation der finalen BillingArchitektur noch passt. Details zu Tabellen/Jobs sollten ergänzt werden, sobald die Lemon SqueezyMigration abgeschlossen ist.
## 6. Konkrete Lemon Squeezy-Flows im System
### 6.1 Checkout-Erstellung
- Marketing-Checkout / API:
- `MarketingController` und `PackageController` nutzen `LemonSqueezyCheckoutService::createCheckout()` (`App\Services\LemonSqueezy\LemonSqueezyCheckoutService`).
- Der Service:
- Baut `custom_data` (`tenant_id`, `package_id`, optional `checkout_session_id`) für spätere Zuordnung.
- Ruft `POST /checkouts` im Lemon SqueezyAPI auf und erhält eine `checkout_url`.
- Ops-Sicht:
- Wenn `lemonsqueezy_variant_id` bei einem Package fehlt, wird kein Checkout erzeugt MarketingUI zeigt entsprechende Fehlertexte (siehe `resources/lang/*/marketing.php`).
- Bei wiederkehrenden „checkout failed“Fehlern die Logs (`LemonSqueezyCheckoutService`, Controller) und PackageKonfiguration prüfen.
### 6.2 Webhook-Verarbeitung & Idempotenz
- Endpoint: `POST /lemonsqueezy/webhook``LemonSqueezyWebhookController::handle()`.
- Service: `CheckoutWebhookService` (`App\Services\Checkout\CheckoutWebhookService`):
- Unterscheidet zwischen **OrderEvents** (`order_*`) und **SubscriptionEvents** (`subscription_*`).
- Idempotenz:
- Nutzt ein CacheLock (`checkout:webhook:lemonsqueezy:{order_id|session_id}`), um parallele Verarbeitung desselben Events zu verhindern.
- Schreibt Metadaten (`lemonsqueezy_last_event`, `lemonsqueezy_status`, `lemonsqueezy_checkout_id`, `lemonsqueezy_order_id`) in `checkout_sessions.provider_metadata`.
- Ergebnis:
- Bei `order_created`/`order_updated` mit Status `paid`:
- `CheckoutSession` wird als `processing` markiert.
- `CheckoutAssignmentService::finalise()` weist Paket/Tenant zu.
- Session wird auf `completed` gesetzt.
- Bei `order_payment_failed` / `order_refunded`:
- Session wird auf `failed` gesetzt, Coupons werden als fehlgeschlagen markiert.
### 6.2.1 SandboxWebhook registrieren
- Command (TestMode aktiv):
- `php artisan lemonsqueezy:webhooks:register --test-mode`
- Optional mit URL-Override:
- `php artisan lemonsqueezy:webhooks:register --url=https://staging.example.com/lemonsqueezy/webhook --test-mode`
- EventListe kommt aus `config/lemonsqueezy.php` (`webhook_events`). Override möglich:
- `php artisan lemonsqueezy:webhooks:register --events=order_created --events=subscription_created`
### 6.2.2 Verarbeitete Lemon Squeezy-Events
Die WebhookHandler erwarten mindestens diese Events:
- `order_created`
- `order_updated`
- `order_payment_failed`
- `order_refunded`
- `subscription_created`
- `subscription_updated`
- `subscription_cancelled`
- `subscription_expired`
- `subscription_paused`
### 6.3 Subscriptions & TenantPackages
- SubscriptionEvents (`subscription_*`) werden ebenfalls von `CheckoutWebhookService` behandelt:
- Tenant wird aus `metadata.tenant_id` oder `lemonsqueezy_customer_id` ermittelt.
- Package wird über `metadata.package_id` oder `lemonsqueezy_variant_id` aufgelöst.
- `TenantPackage` wird erstellt/aktualisiert (`lemonsqueezy_subscription_id`, `expires_at`, `active`).
- `Tenant.subscription_status` und `subscription_expires_at` werden gesteuert.
- Ops-Sicht:
- Bei abweichenden Abostatus (z.B. Lemon Squeezy zeigt „active“, Tenant nicht):
- SubscriptionEvents im Lemon SqueezyDashboard prüfen.
- Letzte `subscription_*`Events in den Logs, `TenantPackage` und `Tenant`Felder gegenprüfen.
### 6.4 Paket- & Coupon-Synchronisation
- Pakete:
- ArtisanCommand `lemonsqueezy:sync-packages` (`App\Console\Commands\LemonSqueezySyncPackages`) stößt für ausgewählte oder alle Pakete `SyncPackageToLemonSqueezy`/`PullPackageFromLemonSqueezy` Jobs an.
- SyncJobs nutzen `LemonSqueezyCatalogService`, um Produkte/Preise in Lemon Squeezy zu erstellen/aktualisieren und `lemonsqueezy_product_id`/`lemonsqueezy_variant_id` lokal zu pflegen.
- Coupons:
- `SyncCouponToLemonSqueezy`Job spiegelt interne CouponKonfiguration in Lemon Squeezy Discounts (`LemonSqueezyDiscountService`).
- Ops-Sicht:
- Bei KatalogAbweichungen `lemonsqueezy:sync-packages --dry-run` verwenden, um Snapshots zu prüfen, bevor tatsächliche Änderungen gesendet werden.
- Fehlgeschlagene Syncs in den Logs (`Lemon Squeezy package sync failed`, `Lemon Squeezy addon sync failed`, `Failed syncing coupon to Lemon Squeezy`) beobachten.
### 6.5 Recovery-Playbook: KatalogSync fehlgeschlagen
Wenn der KatalogSync fehlschlägt oder Pakete nicht mehr korrekt verknüpft sind:
1. **Status im Admin prüfen**
- `PackageResource` → Felder `lemonsqueezy_sync_status`, `lemonsqueezy_synced_at` und `Letzter Fehler`.
- Fehlermeldung stammt aus `lemonsqueezy_snapshot.error.message`.
2. **Trockenlauf ausführen**
- `php artisan lemonsqueezy:sync-packages --package=<id|slug> --dry-run`
- Prüfe die erzeugten PayloadSnapshots (in `lemonsqueezy_snapshot`) auf falsche IDs/Preise.
3. **Mapping prüfen**
- Falls `lemonsqueezy_product_id` oder `lemonsqueezy_variant_id` fehlt oder falsch ist: im PaketAdmin korrigieren.
- BulkSync blockiert unmapped Pakete; gezielte Korrektur vor dem nächsten Lauf ist Pflicht.
4. **Sync erneut anstoßen**
- `php artisan lemonsqueezy:sync-packages --package=<id|slug> --queue`
- Bei Bedarf: `--allow-unmapped` nur bewusst verwenden (z.B. initiales Mapping).
5. **Pull für Abgleich**
- `php artisan lemonsqueezy:sync-packages --package=<id|slug> --pull` zum Abgleich mit Lemon Squeezy.
6. **Logs prüfen**
- Erwartete Logeinträge: `Lemon Squeezy package sync failed`, `Lemon Squeezy addon sync failed`, `Failed syncing coupon to Lemon Squeezy`.
- Achte auf wiederkehrende Fehler (z.B. invalid product/price IDs).
Diese Untersektion soll dir als Operator helfen zu verstehen, wie Lemon SqueezyAktionen im System abgebildet sind und an welchen Stellen du im Fehlerfall ansetzen kannst.
## 7. Production Cutover: Lemon Squeezy Migration
Diese Checkliste beschreibt den kontrollierten Wechsel auf Lemon Squeezy in Produktion.
1. **Vorbereitung (T1 Woche)**
- Confirm: `LEMONSQUEEZY_API_KEY`, `LEMONSQUEEZY_STORE_ID`, `LEMONSQUEEZY_WEBHOOK_SECRET`, `LEMONSQUEEZY_TEST_MODE=false`.
- Package IDs validieren: alle aktiven Packages haben `lemonsqueezy_product_id` und `lemonsqueezy_variant_id`.
- `lemonsqueezy:sync-packages --dry-run` auf eine Stichprobe anwenden.
- EventListe prüfen: `config/lemonsqueezy.php` (`webhook_events`).
2. **Staging Smoke (T2 Tage)**
- `lemonsqueezy:webhooks:register --test-mode` auf Staging ausführen.
- Testkauf via Lemon Squeezy Test Mode und Webhook Replay verifizieren.
3. **Cutover Window (T0)**
- MarketingCheckout kurz einfrieren (kein Checkout waehrend der Umschaltung).
- Production Webhook registrieren:
- `lemonsqueezy:webhooks:register --url=https://<prod-domain>/lemonsqueezy/webhook`
- Queue worker laufen lassen (Queue: `webhooks`/`billing` sofern konfiguriert).
4. **Activation**
- Erstes ProduktionsCheckout ausfuehren.
- Verify: `checkout_sessions.provider_metadata` wird mit `lemonsqueezy_*` Feldern befuellt.
- Verify: `TenantPackage` aktiv und `subscription_status` korrekt.
5. **Rollback (falls notwendig)**
- Checkout wieder deaktivieren (MarketingCheckout ausblenden).
- Lemon Squeezy Webhook Destination im Lemon Squeezy Dashboard deaktivieren.
- Status und Logs sichern (Webhooks, `lemonsqueezy-sync` Log).
6. **PostCutover (T+1)**
- Stichproben auf neue Tenants/Packages.
- Monitoring: Fehlgeschlagene Webhooks, SyncFehler, Support Tickets.

View File

@@ -0,0 +1,92 @@
---
title: How-to Zahlung erfolgreich, Paket nicht aktiv
---
Dieses Howto beschreibt, wie du vorgehst, wenn ein Tenant meldet: „Zahlung war erfolgreich, aber mein Paket ist nicht aktiv / Galerie bleibt limitiert.“
## 1. Informationen vom Tenant einsammeln
Bevor du nachschaust:
- TenantID oder TenantSlug.
- Betroffenes Paket (Name oder Beschreibung, z.B. „ProPaket 79 €“).
- Zeitpunkt der Zahlung (Datum/Uhrzeit, ggf. Screenshot).
- Ggf. Auszug aus der Lemon SqueezyBestätigung (ohne vollständige Kartendaten!).
Diese Infos erlauben dir, die korrekte Transaktion sowohl in Lemon Squeezy als auch im Backend zu finden.
## 2. Lemon Squeezy-Status prüfen
1. Im Lemon SqueezyDashboard:
- Suche nach EMail, TenantName oder der vom Tenant genannten OrderID.
- Stelle sicher, dass die Zahlung dort als „paid“/„completed“ markiert ist.
2. Notiere:
- Lemon SqueezyOrderID und ggf. CheckoutID.
- Status (paid/processing/failed/cancelled).
Wenn Lemon Squeezy die Zahlung nicht als erfolgreich zeigt, ist dies primär ein Finance/CustomerTopic ggf. mit Customer Support klären, ob eine neue Zahlung oder Klärung mit dem Kunden notwendig ist.
## 3. Backend-Status prüfen
Mit bestätigter Zahlung in Lemon Squeezy:
1. `checkout_sessions`:
- Suche nach Sessions des Tenants (`tenant_id`) mit dem betroffenen `package_id`:
- Achte auf `status` (erwartet `completed`) und `provider = lemonsqueezy`.
- Prüfe `provider_metadata` auf `lemonsqueezy_last_event`, `lemonsqueezy_status`, `lemonsqueezy_checkout_id`, `lemonsqueezy_order_id`.
- Wenn du die Session über Lemon SqueezyMetadaten finden möchtest:
- `lemonsqueezy_checkout_id` oder `lemonsqueezy_order_id` verwenden.
2. `package_purchases`:
- Prüfe, ob ein Eintrag für `(tenant_id, package_id)` mit passender ProviderReferenz existiert:
- z.B. `provider = 'lemonsqueezy'`, `provider_id` = OrderID.
3. `tenant_packages`:
- Prüfe, ob es einen aktiven Eintrag für `(tenant_id, package_id)` gibt:
- `active = 1`, `expires_at` in der Zukunft.
## 4. Webhook-/Verarbeitungsstatus untersuchen
Wenn `checkout_sessions` noch nicht auf `completed` steht oder `tenant_packages` nicht aktualisiert wurden:
1. Logs prüfen:
- `storage/logs/laravel.log` und ggf. `billing`Channel.
- Suche nach Einträgen von `LemonSqueezyWebhookController` / `CheckoutWebhookService` rund um den Zahlungszeitpunkt.
2. Typische Ursachen:
- Webhook nicht zugestellt (Netzwerk/SSL).
- Webhook konnte die Session nicht auflösen (`[CheckoutWebhook] Lemon Squeezy session not resolved`).
- IdempotenzLock (`Lemon Squeezy lock busy`) hat dazu geführt, dass Event nur teilweise verarbeitet wurde.
## 5. Korrektur-Schritte
### 5.1 Automatischer Replay (empfohlen)
1. Im Lemon SqueezyDashboard:
- Den betreffenden `order_*`Event finden.
- WebhookReplay auslösen.
2. In den Logs beobachten:
- Ob `CheckoutWebhookService::handleLemonSqueezyEvent()` diesmal die Session findet und `CheckoutAssignmentService::finalise()` ausführt.
3. Nochmal `checkout_sessions` und `tenant_packages` prüfen:
- Session sollte auf `completed` stehen, Paket aktiv sein.
### 5.2 Manuelle Korrektur (Notfall)
Nur anwenden, wenn klare Freigabe vorliegt und Lemon Squeezy die Zahlung eindeutig als erfolgreich listet.
1. `tenant_packages` aktualisieren:
- Entweder neuen Eintrag anlegen oder bestehenden für `(tenant_id, package_id)` so setzen, dass:
- `active = 1`,
- `purchased_at` und `expires_at` zu Lemon SqueezyDaten passen.
2. `package_purchases` ergänzen:
- Sicherstellen, dass die Zahlung als Zeile mit `provider = 'lemonsqueezy'`, `provider_id = OrderID` und passender `price` existiert (für spätere Audits).
3. Konsistenz prüfen:
- Admin UI für Tenant öffnen und prüfen, ob Limits/Paketstatus jetzt korrekt angezeigt werden.
4. Dokumentation:
- Den Vorgang im Ticket oder als bd-Issue (falls wiederkehrend) dokumentieren.
## 6. Kommunikation mit dem Tenant
- Sobald der BackendStatus korrigiert ist:
- Kurz bestätigen, dass das Paket aktiv ist und welche Auswirkungen das hat (z.B. neue Limits, verlängerte Galerie).
- Falls Lemon Squeezy die Zahlung nicht als erfolgreich führt:
- Ehrlich kommunizieren, dass laut Zahlungsprovider noch keine endgültige Zahlung vorliegt und welche Optionen es gibt (z.B. neue Zahlung, Klärung mit Bank/Kreditkarte).
Dieses Howto sollte dem Support/OnCall helfen, den gängigsten BillingFehlerfall strukturiert abzuarbeiten. Für tiefere Ursachenanalysen siehe `docs/ops/billing-ops.md`.