7.9 KiB
title, sidebar_label
| title | sidebar_label |
|---|---|
| Billing & Zahlungs-Operationen | Billing-Runbook |
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Paddle/RevenueCat‑Webhooks und Paket‑Inkonsistenzen operativ umzugehen ist.
1. Komponentenüberblick
- Paddle
- Abwicklung von Web‑Checkout, Paketen und Subscriptions.
- Webhooks für Käufe, Verlängerungen, Stornos.
- Fotospiel Backend
- Modelle wie
Tenant,Packages,tenant_packages,event_packages. - Services/Jobs zur Paket‑Zuweisung, Limit‑Berechnung und Nutzungstracking.
- Modelle wie
Details zur Architektur findest du in den PRP‑Kapiteln (Billing/Freemium) sowie in den bd-Issues zur Paddle‑Migration und zum Katalog‑Sync.
2. Typische Problemszenarien
- Webhook kommt nicht an / schlägt fehl
- Symptom: Paddle zeigt Zahlung „completed“, Tenant‑Paket im Backend bleibt unverändert.
- Checkliste:
- Logs der Webhook‑Routes prüfen (Statuscodes, Exceptions).
- Endpoint:
POST /paddle/webhook→PaddleWebhookController::handle(). - Controller ruft
CheckoutWebhookService::handlePaddleEvent()auf.
- Endpoint:
- Webhook‑Replay über das Paddle Dashboard auslösen (für einzelne Events).
- Queue‑Status prüfen:
- Falls Webhooks über Queues verarbeitet werden, auf
default/billing‑Queues achten (je nach Konfiguration).
- Falls Webhooks über Queues verarbeitet werden, auf
- Logs der Webhook‑Routes prüfen (Statuscodes, Exceptions).
- Doppelte oder fehlende Abbuchungen
- Abgleich von Zahlungsprovider‑Daten (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). - Limit‑Zähler (
used_photos,used_events) und aktuelle Nutzung. - Letzte relevante Webhooks/Jobs (z.B. vor kurzem migriert?).
- Aktives Paket (
3. Operative Schritte bei Payment Incidents
- Event/Tenant identifizieren
- IDs und relevante Paket‑Infos aus DB/Admin UI holen.
- Provider-Status prüfen
- Paddle‑Dashboard: ist die Zahlung dort korrekt verbucht? (Transaktions‑/Abonnement‑Ansicht).
- Backend-Status prüfen
- Paketzuweisung und Limits in der DB (Read‑only!) inspizieren:
checkout_sessions– wurde die Session korrekt aufcompletedgesetzt? (provider = paddle,paddle_transaction_idgefüllt?)package_purchases– existiert ein Eintrag für Tenant/Package mit erwarteter Provider‑Referenz?tenant_packages– stimmt deractive‑Status undexpires_atmit dem erwarteten Abostatus überein?
- Paketzuweisung und Limits in der DB (Read‑only!) inspizieren:
- Entscheidung
- Automatische Nachverarbeitung via Webhook‑Replay/Job‑Retry:
- Paddle‑Events erneut senden lassen, ggf.
tests/api/_testing/checkout/sessions/{session}/simulate-paddle(in Test‑Umgebungen) nutzen.
- Paddle‑Events erneut senden lassen, ggf.
- Notfall: manuelle Paket‑Anpassung (nur mit klar dokumentierter Begründung):
- Paket in
tenant_packagesaktivieren/verlängern undpackage_purchasessauber nachziehen.
- Paket in
- Automatische Nachverarbeitung via Webhook‑Replay/Job‑Retry:
- 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 (Tenant‑ID, Event‑ID, Payment‑Provider‑Referenz, Zeitstempel).
- Welche Standardantworten es gibt (z.B. „Zahlung in Prüfung, Paket kurzfristig manuell freigeschaltet“).
5. Hinweise zur Implementierung
- Konfiguration
- Paddle‑Keys, Webhook‑Secrets und Feature‑Flags sollten ausschließlich in
.env/Config‑Dateien liegen und niemals im Code/Logs landen. - Sandbox vs. Live‑Keys klar trennen; Ops sollte wissen, welche Umgebung gerade aktiv ist.
- Paddle‑Keys, Webhook‑Secrets und Feature‑Flags sollten ausschließlich in
- Sicherheit
- Webhook‑Signaturen und Timestamps prüfen; bei verdächtigen Mustern (z.B. Replay‑Angriffe) Security‑Runbooks konsultieren.
- Keine sensiblen Payment‑Details in Applikations‑Logs ausgeben.
Diese Sektion ist bewusst generisch gehalten, damit sie auch nach Implementation der finalen Billing‑Architektur noch passt. Details zu Tabellen/Jobs sollten ergänzt werden, sobald die Paddle‑Migration abgeschlossen ist.
6. Konkrete Paddle-Flows im System
6.1 Checkout-Erstellung
- Marketing-Checkout / API:
MarketingControllerundPackageControllernutzenPaddleCheckoutService::createCheckout()(App\Services\Paddle\PaddleCheckoutService).- Der Service:
- Stellt sicher, dass ein
paddle_customer_idfür den Tenant existiert (PaddleCustomerService::ensureCustomerId()). - Baut Metadaten (
tenant_id,package_id, optionalcheckout_session_id) für spätere Zuordnung. - Ruft
POST /checkout/linksim Paddle‑API auf und erhält einecheckout_url.
- Stellt sicher, dass ein
- Ops-Sicht:
- Wenn
paddle_price_idbei einem Package fehlt, wird kein Checkout erzeugt – Marketing‑UI zeigt entsprechende Fehlertexte (sieheresources/lang/*/marketing.php). - Bei wiederkehrenden „checkout failed“‑Fehlern die Logs (
PaddleCheckoutService, Controller) und Package‑Konfiguration prüfen.
- Wenn
6.2 Webhook-Verarbeitung & Idempotenz
- Endpoint:
POST /paddle/webhook→PaddleWebhookController::handle(). - Service:
CheckoutWebhookService(App\Services\Checkout\CheckoutWebhookService):- Unterscheidet zwischen Transaktions‑Events (
transaction.*) und Subscription‑Events (subscription.*). - Idempotenz:
- Nutzt ein Cache‑Lock (
checkout:webhook:paddle:{transaction_id|session_id}), um parallele Verarbeitung desselben Events zu verhindern. - Schreibt Metadaten (
paddle_last_event,paddle_status,paddle_checkout_id) incheckout_sessions.provider_metadata.
- Nutzt ein Cache‑Lock (
- Unterscheidet zwischen Transaktions‑Events (
- Ergebnis:
- Bei
transaction.completed:CheckoutSessionwird alsprocessingmarkiert.CheckoutAssignmentService::finalise()weist Paket/Tenant zu.- Session wird auf
completedgesetzt.
- Bei
transaction.failed/transaction.cancelled:- Session wird auf
failedgesetzt, Coupons werden als fehlgeschlagen markiert.
- Session wird auf
- Bei
6.3 Subscriptions & TenantPackages
- Subscription‑Events (
subscription.*) werden ebenfalls vonCheckoutWebhookServicebehandelt:- Tenant wird aus
metadata.tenant_idoderpaddle_customer_idermittelt. - Package wird über
metadata.package_idoderpaddle_price_idaufgelöst. TenantPackagewird erstellt/aktualisiert (paddle_subscription_id,expires_at,active).Tenant.subscription_statusundsubscription_expires_atwerden gesteuert.
- Tenant wird aus
- Ops-Sicht:
- Bei abweichenden Abostatus (z.B. Paddle zeigt „active“, Tenant nicht):
- Subscription‑Events im Paddle‑Dashboard prüfen.
- Letzte
subscription.*‑Events in den Logs,TenantPackage‑ undTenant‑Felder gegenprüfen.
- Bei abweichenden Abostatus (z.B. Paddle zeigt „active“, Tenant nicht):
6.4 Paket- & Coupon-Synchronisation
- Pakete:
- Artisan‑Command
paddle:sync-packages(App\Console\Commands\PaddleSyncPackages) stößt für ausgewählte oder alle PaketeSyncPackageToPaddle/PullPackageFromPaddleJobs an. - Sync‑Jobs nutzen
PaddleCatalogService, um Produkte/Preise in Paddle zu erstellen/aktualisieren undpaddle_product_id/paddle_price_idlokal zu pflegen.
- Artisan‑Command
- Coupons:
SyncCouponToPaddle‑Job spiegelt interne Coupon‑Konfiguration in Paddle Discounts (PaddleDiscountService).
- Ops-Sicht:
- Bei Katalog‑Abweichungen
paddle:sync-packages --dry-runverwenden, 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.
- Bei Katalog‑Abweichungen
Diese Untersektion soll dir als Operator helfen zu verstehen, wie Paddle‑Aktionen im System abgebildet sind und an welchen Stellen du im Fehlerfall ansetzen kannst.