Files
fotospiel-app/docs/ops/billing-ops.md

134 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Billing & Zahlungs-Operationen
sidebar_label: Billing-Runbook
---
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Paddle/RevenueCatWebhooks und PaketInkonsistenzen operativ umzugehen ist.
## 1. Komponentenüberblick
- **Paddle**
- Abwicklung von WebCheckout, Paketen und Subscriptions.
- Webhooks für Käufe, 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 PaddleMigration und zum KatalogSync.
## 2. Typische Problemszenarien
- **Webhook kommt nicht an / schlägt fehl**
- Symptom: Paddle zeigt Zahlung „completed“, TenantPaket im Backend bleibt unverändert.
- Checkliste:
- Logs der WebhookRoutes prüfen (Statuscodes, Exceptions).
- Endpoint: `POST /paddle/webhook``PaddleWebhookController::handle()`.
- Controller ruft `CheckoutWebhookService::handlePaddleEvent()` auf.
- WebhookReplay über das Paddle 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 (Paddle/RevenueCat) mit internem Ledger.
- Bei doppelten Buchungen: Prozess definieren (Refund via Paddle, 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**
- PaddleDashboard: ist die Zahlung dort korrekt verbucht? (Transaktions/AbonnementAnsicht).
3. **Backend-Status prüfen**
- Paketzuweisung und Limits in der DB (Readonly!) inspizieren:
- `checkout_sessions` wurde die Session korrekt auf `completed` gesetzt? (`provider = paddle`, `paddle_transaction_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:
- PaddleEvents erneut senden lassen, ggf. `tests/api/_testing/checkout/sessions/{session}/simulate-paddle` (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 Paddle/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**
- PaddleKeys, 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 PaddleMigration abgeschlossen ist.
## 6. Konkrete Paddle-Flows im System
### 6.1 Checkout-Erstellung
- Marketing-Checkout / API:
- `MarketingController` und `PackageController` nutzen `PaddleCheckoutService::createCheckout()` (`App\Services\Paddle\PaddleCheckoutService`).
- Der Service:
- Stellt sicher, dass ein `paddle_customer_id` für den Tenant existiert (`PaddleCustomerService::ensureCustomerId()`).
- Baut Metadaten (`tenant_id`, `package_id`, optional `checkout_session_id`) für spätere Zuordnung.
- Ruft `POST /checkout/links` im PaddleAPI auf und erhält eine `checkout_url`.
- Ops-Sicht:
- Wenn `paddle_price_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 (`PaddleCheckoutService`, Controller) und PackageKonfiguration prüfen.
### 6.2 Webhook-Verarbeitung & Idempotenz
- Endpoint: `POST /paddle/webhook``PaddleWebhookController::handle()`.
- Service: `CheckoutWebhookService` (`App\Services\Checkout\CheckoutWebhookService`):
- Unterscheidet zwischen **TransaktionsEvents** (`transaction.*`) und **SubscriptionEvents** (`subscription.*`).
- Idempotenz:
- Nutzt ein CacheLock (`checkout:webhook:paddle:{transaction_id|session_id}`), um parallele Verarbeitung desselben Events zu verhindern.
- Schreibt Metadaten (`paddle_last_event`, `paddle_status`, `paddle_checkout_id`) in `checkout_sessions.provider_metadata`.
- Ergebnis:
- Bei `transaction.completed`:
- `CheckoutSession` wird als `processing` markiert.
- `CheckoutAssignmentService::finalise()` weist Paket/Tenant zu.
- Session wird auf `completed` gesetzt.
- Bei `transaction.failed` / `transaction.cancelled`:
- Session wird auf `failed` gesetzt, Coupons werden als fehlgeschlagen markiert.
### 6.3 Subscriptions & TenantPackages
- SubscriptionEvents (`subscription.*`) werden ebenfalls von `CheckoutWebhookService` behandelt:
- Tenant wird aus `metadata.tenant_id` oder `paddle_customer_id` ermittelt.
- Package wird über `metadata.package_id` oder `paddle_price_id` aufgelöst.
- `TenantPackage` wird erstellt/aktualisiert (`paddle_subscription_id`, `expires_at`, `active`).
- `Tenant.subscription_status` und `subscription_expires_at` werden gesteuert.
- Ops-Sicht:
- Bei abweichenden Abostatus (z.B. Paddle zeigt „active“, Tenant nicht):
- SubscriptionEvents im PaddleDashboard prüfen.
- Letzte `subscription.*`Events in den Logs, `TenantPackage` und `Tenant`Felder gegenprüfen.
### 6.4 Paket- & Coupon-Synchronisation
- Pakete:
- ArtisanCommand `paddle:sync-packages` (`App\Console\Commands\PaddleSyncPackages`) stößt für ausgewählte oder alle Pakete `SyncPackageToPaddle`/`PullPackageFromPaddle` Jobs an.
- SyncJobs nutzen `PaddleCatalogService`, um Produkte/Preise in Paddle zu erstellen/aktualisieren und `paddle_product_id`/`paddle_price_id` lokal zu pflegen.
- Coupons:
- `SyncCouponToPaddle`Job spiegelt interne CouponKonfiguration in Paddle Discounts (`PaddleDiscountService`).
- Ops-Sicht:
- Bei KatalogAbweichungen `paddle:sync-packages --dry-run` verwenden, um Snapshots zu prüfen, bevor tatsächliche Änderungen gesendet werden.
- Fehlgeschlagene Syncs in den Logs (`Paddle package sync failed`, `Paddle discount sync failed`) beobachten.
Diese Untersektion soll dir als Operator helfen zu verstehen, wie PaddleAktionen im System abgebildet sind und an welchen Stellen du im Fehlerfall ansetzen kannst.