12 KiB
title, sidebar_label
| title | sidebar_label |
|---|---|
| Billing & Zahlungs-Operationen | Billing-Runbook |
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Lemon Squeezy/RevenueCat‑Webhooks und Paket‑Inkonsistenzen operativ umzugehen ist.
1. Komponentenüberblick
- Lemon Squeezy
- Abwicklung von Web‑Checkout, Paketen und Subscriptions.
- Webhooks für Bestellungen, 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 Lemon Squeezy‑Migration und zum Katalog‑Sync.
2. Typische Problemszenarien
- Webhook kommt nicht an / schlägt fehl
- Symptom: Lemon Squeezy zeigt Zahlung „completed/paid“, Tenant‑Paket im Backend bleibt unverändert.
- Checkliste:
- Logs der Webhook‑Routes prüfen (Statuscodes, Exceptions).
- Endpoint:
POST /lemonsqueezy/webhook→LemonSqueezyWebhookController::handle(). - Controller ruft
CheckoutWebhookService::handleLemonSqueezyEvent()auf.
- Endpoint:
- Webhook‑Replay über das Lemon Squeezy 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 (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). - 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
- Lemon Squeezy‑Dashboard: ist die Zahlung dort korrekt verbucht? (Order‑/Subscription‑Ansicht).
- Backend-Status prüfen
- Paketzuweisung und Limits in der DB (Read‑only!) inspizieren:
checkout_sessions– wurde die Session korrekt aufcompletedgesetzt? (provider = lemonsqueezy,lemonsqueezy_order_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:
- Lemon Squeezy‑Events erneut senden lassen, ggf.
tests/api/_testing/checkout/sessions/{session}/simulate-lemonsqueezy(in Test‑Umgebungen) nutzen.
- Lemon Squeezy‑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 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 (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
- Lemon Squeezy‑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.
- Lemon Squeezy‑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 Lemon Squeezy‑Migration abgeschlossen ist.
6. Konkrete Lemon Squeezy-Flows im System
6.1 Checkout-Erstellung
- Marketing-Checkout / API:
MarketingControllerundPackageControllernutzenLemonSqueezyCheckoutService::createCheckout()(App\Services\LemonSqueezy\LemonSqueezyCheckoutService).- Der Service:
- Baut
custom_data(tenant_id,package_id, optionalcheckout_session_id) für spätere Zuordnung. - Ruft
POST /checkoutsim Lemon Squeezy‑API auf und erhält einecheckout_url.
- Baut
- Ops-Sicht:
- Wenn
lemonsqueezy_variant_idbei einem Package fehlt, wird kein Checkout erzeugt – Marketing‑UI zeigt entsprechende Fehlertexte (sieheresources/lang/*/marketing.php). - Bei wiederkehrenden „checkout failed“‑Fehlern die Logs (
LemonSqueezyCheckoutService, Controller) und Package‑Konfiguration prüfen.
- Wenn
6.2 Webhook-Verarbeitung & Idempotenz
- Endpoint:
POST /lemonsqueezy/webhook→LemonSqueezyWebhookController::handle(). - Service:
CheckoutWebhookService(App\Services\Checkout\CheckoutWebhookService):- Unterscheidet zwischen Order‑Events (
order_*) und Subscription‑Events (subscription_*). - Idempotenz:
- Nutzt ein Cache‑Lock (
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) incheckout_sessions.provider_metadata.
- Nutzt ein Cache‑Lock (
- Unterscheidet zwischen Order‑Events (
- Ergebnis:
- Bei
order_created/order_updatedmit Statuspaid:CheckoutSessionwird alsprocessingmarkiert.CheckoutAssignmentService::finalise()weist Paket/Tenant zu.- Session wird auf
completedgesetzt.
- Bei
order_payment_failed/order_refunded:- Session wird auf
failedgesetzt, Coupons werden als fehlgeschlagen markiert.
- Session wird auf
- Bei
6.2.1 Sandbox‑Webhook registrieren
- Command (Test‑Mode 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
- Event‑Liste 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 Webhook‑Handler erwarten mindestens diese Events:
order_createdorder_updatedorder_payment_failedorder_refundedsubscription_createdsubscription_updatedsubscription_cancelledsubscription_expiredsubscription_paused
6.3 Subscriptions & TenantPackages
- Subscription‑Events (
subscription_*) werden ebenfalls vonCheckoutWebhookServicebehandelt:- Tenant wird aus
metadata.tenant_idoderlemonsqueezy_customer_idermittelt. - Package wird über
metadata.package_idoderlemonsqueezy_variant_idaufgelöst. TenantPackagewird erstellt/aktualisiert (lemonsqueezy_subscription_id,expires_at,active).Tenant.subscription_statusundsubscription_expires_atwerden gesteuert.
- Tenant wird aus
- Ops-Sicht:
- Bei abweichenden Abostatus (z.B. Lemon Squeezy zeigt „active“, Tenant nicht):
- Subscription‑Events im Lemon Squeezy‑Dashboard prüfen.
- Letzte
subscription_*‑Events in den Logs,TenantPackage‑ undTenant‑Felder gegenprüfen.
- Bei abweichenden Abostatus (z.B. Lemon Squeezy zeigt „active“, Tenant nicht):
6.4 Paket- & Coupon-Synchronisation
- Pakete:
- Artisan‑Command
lemonsqueezy:sync-packages(App\Console\Commands\LemonSqueezySyncPackages) stößt für ausgewählte oder alle PaketeSyncPackageToLemonSqueezy/PullPackageFromLemonSqueezyJobs an. - Sync‑Jobs nutzen
LemonSqueezyCatalogService, um Produkte/Preise in Lemon Squeezy zu erstellen/aktualisieren undlemonsqueezy_product_id/lemonsqueezy_variant_idlokal zu pflegen.
- Artisan‑Command
- Coupons:
SyncCouponToLemonSqueezy‑Job spiegelt interne Coupon‑Konfiguration in Lemon Squeezy Discounts (LemonSqueezyDiscountService).
- Ops-Sicht:
- Bei Katalog‑Abweichungen
lemonsqueezy:sync-packages --dry-runverwenden, 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.
- Bei Katalog‑Abweichungen
6.5 Recovery-Playbook: Katalog‑Sync fehlgeschlagen
Wenn der Katalog‑Sync fehlschlägt oder Pakete nicht mehr korrekt verknüpft sind:
- Status im Admin prüfen
PackageResource→ Felderlemonsqueezy_sync_status,lemonsqueezy_synced_atundLetzter Fehler.- Fehlermeldung stammt aus
lemonsqueezy_snapshot.error.message.
- Trockenlauf ausführen
php artisan lemonsqueezy:sync-packages --package=<id|slug> --dry-run- Prüfe die erzeugten Payload‑Snapshots (in
lemonsqueezy_snapshot) auf falsche IDs/Preise.
- Mapping prüfen
- Falls
lemonsqueezy_product_idoderlemonsqueezy_variant_idfehlt oder falsch ist: im Paket‑Admin korrigieren. - Bulk‑Sync blockiert unmapped Pakete; gezielte Korrektur vor dem nächsten Lauf ist Pflicht.
- Falls
- Sync erneut anstoßen
php artisan lemonsqueezy:sync-packages --package=<id|slug> --queue- Bei Bedarf:
--allow-unmappednur bewusst verwenden (z.B. initiales Mapping).
- Pull für Abgleich
php artisan lemonsqueezy:sync-packages --package=<id|slug> --pullzum Abgleich mit Lemon Squeezy.
- 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).
- Erwartete Logeinträge:
Diese Untersektion soll dir als Operator helfen zu verstehen, wie Lemon Squeezy‑Aktionen 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.
- Vorbereitung (T‑1 Woche)
- Confirm:
LEMONSQUEEZY_API_KEY,LEMONSQUEEZY_STORE_ID,LEMONSQUEEZY_WEBHOOK_SECRET,LEMONSQUEEZY_TEST_MODE=false. - Package IDs validieren: alle aktiven Packages haben
lemonsqueezy_product_idundlemonsqueezy_variant_id. lemonsqueezy:sync-packages --dry-runauf eine Stichprobe anwenden.- Event‑Liste prüfen:
config/lemonsqueezy.php(webhook_events).
- Confirm:
- Staging Smoke (T‑2 Tage)
lemonsqueezy:webhooks:register --test-modeauf Staging ausführen.- Testkauf via Lemon Squeezy Test Mode und Webhook Replay verifizieren.
- Cutover Window (T‑0)
- Marketing‑Checkout 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/billingsofern konfiguriert).
- Activation
- Erstes Produktions‑Checkout ausfuehren.
- Verify:
checkout_sessions.provider_metadatawird mitlemonsqueezy_*Feldern befuellt. - Verify:
TenantPackageaktiv undsubscription_statuskorrekt.
- Rollback (falls notwendig)
- Checkout wieder deaktivieren (Marketing‑Checkout ausblenden).
- Lemon Squeezy Webhook Destination im Lemon Squeezy Dashboard deaktivieren.
- Status und Logs sichern (Webhooks,
lemonsqueezy-syncLog).
- Post‑Cutover (T+1)
- Stichproben auf neue Tenants/Packages.
- Monitoring: Fehlgeschlagene Webhooks, Sync‑Fehler, Support Tickets.