Migrate billing from Paddle to Lemon Squeezy
This commit is contained in:
@@ -84,5 +84,5 @@ related:
|
||||
- Ensure every `.en.md` file has a matching `.de.md` file with equal `slug`/`version_introduced`.
|
||||
- Validate required front matter keys.
|
||||
- Future enhancements:
|
||||
- Integrate with Paddle customer portal for billing-related admin help.
|
||||
- Integrate with Lemon Squeezy customer portal for billing-related admin help.
|
||||
- Add analytics event (non-PII) for article views through the app to measure usefulness.
|
||||
|
||||
@@ -14,7 +14,7 @@ Internal troubleshooting guide for superadmins and on-call.
|
||||
- **Guest cannot join**: confirm event is published and the join link is current.
|
||||
|
||||
## Billing and quota blocks
|
||||
- Check Paddle / RevenueCat status dashboards.
|
||||
- Check Lemon Squeezy / RevenueCat status dashboards.
|
||||
- Confirm webhook freshness and retry failures if needed.
|
||||
|
||||
## Communications
|
||||
|
||||
@@ -3,33 +3,33 @@ title: Billing & Zahlungs-Operationen
|
||||
sidebar_label: Billing-Runbook
|
||||
---
|
||||
|
||||
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Paddle/RevenueCat‑Webhooks und Paket‑Inkonsistenzen operativ umzugehen ist.
|
||||
Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Lemon Squeezy/RevenueCat‑Webhooks und Paket‑Inkonsistenzen operativ umzugehen ist.
|
||||
|
||||
## 1. Komponentenüberblick
|
||||
|
||||
- **Paddle**
|
||||
- **Lemon Squeezy**
|
||||
- Abwicklung von Web‑Checkout, Paketen und Subscriptions.
|
||||
- Webhooks für Käufe, Verlängerungen, Stornos.
|
||||
- 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.
|
||||
|
||||
> Details zur Architektur findest du in den PRP‑Kapiteln (Billing/Freemium) sowie in den bd-Issues zur Paddle‑Migration und zum Katalog‑Sync.
|
||||
> 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: Paddle zeigt Zahlung „completed“, Tenant‑Paket im Backend bleibt unverändert.
|
||||
- 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 /paddle/webhook` → `PaddleWebhookController::handle()`.
|
||||
- Controller ruft `CheckoutWebhookService::handlePaddleEvent()` auf.
|
||||
- Webhook‑Replay über das Paddle Dashboard auslösen (für einzelne Events).
|
||||
- Endpoint: `POST /lemonsqueezy/webhook` → `LemonSqueezyWebhookController::handle()`.
|
||||
- Controller ruft `CheckoutWebhookService::handleLemonSqueezyEvent()` auf.
|
||||
- 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).
|
||||
- **Doppelte oder fehlende Abbuchungen**
|
||||
- Abgleich von Zahlungsprovider‑Daten (Paddle/RevenueCat) mit internem Ledger.
|
||||
- Bei doppelten Buchungen: Prozess definieren (Refund via Paddle, Anpassung im Ledger).
|
||||
- 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.Ä.
|
||||
@@ -43,21 +43,21 @@ Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Paddle/RevenueCat‑Webhoo
|
||||
1. **Event/Tenant identifizieren**
|
||||
- IDs und relevante Paket‑Infos aus DB/Admin UI holen.
|
||||
2. **Provider-Status prüfen**
|
||||
- Paddle‑Dashboard: ist die Zahlung dort korrekt verbucht? (Transaktions‑/Abonnement‑Ansicht).
|
||||
- Lemon Squeezy‑Dashboard: ist die Zahlung dort korrekt verbucht? (Order‑/Subscription‑Ansicht).
|
||||
3. **Backend-Status prüfen**
|
||||
- Paketzuweisung und Limits in der DB (Read‑only!) inspizieren:
|
||||
- `checkout_sessions` – wurde die Session korrekt auf `completed` gesetzt? (`provider = paddle`, `paddle_transaction_id` gefüllt?)
|
||||
- `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 Provider‑Referenz?
|
||||
- `tenant_packages` – stimmt der `active`‑Status und `expires_at` mit dem erwarteten Abostatus überein?
|
||||
4. **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.
|
||||
- Lemon Squeezy‑Events erneut senden lassen, ggf. `tests/api/_testing/checkout/sessions/{session}/simulate-lemonsqueezy` (in Test‑Umgebungen) nutzen.
|
||||
- Notfall: manuelle Paket‑Anpassung (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.
|
||||
> TODO: Ergänze konkrete Tabellen-/Modellnamen und die relevanten Jobs/Artisan Commands, sobald Lemon Squeezy/RevenueCat Migration finalisiert ist.
|
||||
|
||||
## 4. Zusammenarbeit mit Finance/Support
|
||||
|
||||
@@ -69,141 +69,138 @@ Dieses Runbook beschreibt, wie mit Zahlungsproblemen, Paddle/RevenueCat‑Webhoo
|
||||
## 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.
|
||||
- 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.
|
||||
- **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.
|
||||
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 Paddle-Flows im System
|
||||
## 6. Konkrete Lemon Squeezy-Flows im System
|
||||
|
||||
### 6.1 Checkout-Erstellung
|
||||
|
||||
- Marketing-Checkout / API:
|
||||
- `MarketingController` und `PackageController` nutzen `PaddleCheckoutService::createCheckout()` (`App\Services\Paddle\PaddleCheckoutService`).
|
||||
- `MarketingController` und `PackageController` nutzen `LemonSqueezyCheckoutService::createCheckout()` (`App\Services\LemonSqueezy\LemonSqueezyCheckoutService`).
|
||||
- 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 Paddle‑API auf und erhält eine `checkout_url`.
|
||||
- Baut `custom_data` (`tenant_id`, `package_id`, optional `checkout_session_id`) für spätere Zuordnung.
|
||||
- Ruft `POST /checkouts` im Lemon Squeezy‑API auf und erhält eine `checkout_url`.
|
||||
- Ops-Sicht:
|
||||
- Wenn `paddle_price_id` bei einem Package fehlt, wird kein Checkout erzeugt – Marketing‑UI zeigt entsprechende Fehlertexte (siehe `resources/lang/*/marketing.php`).
|
||||
- Bei wiederkehrenden „checkout failed“‑Fehlern die Logs (`PaddleCheckoutService`, Controller) und Package‑Konfiguration prüfen.
|
||||
- Wenn `lemonsqueezy_variant_id` bei einem Package fehlt, wird kein Checkout erzeugt – Marketing‑UI zeigt entsprechende Fehlertexte (siehe `resources/lang/*/marketing.php`).
|
||||
- Bei wiederkehrenden „checkout failed“‑Fehlern die Logs (`LemonSqueezyCheckoutService`, Controller) und Package‑Konfiguration prüfen.
|
||||
|
||||
### 6.2 Webhook-Verarbeitung & Idempotenz
|
||||
|
||||
- Endpoint: `POST /paddle/webhook` → `PaddleWebhookController::handle()`.
|
||||
- Endpoint: `POST /lemonsqueezy/webhook` → `LemonSqueezyWebhookController::handle()`.
|
||||
- Service: `CheckoutWebhookService` (`App\Services\Checkout\CheckoutWebhookService`):
|
||||
- Unterscheidet zwischen **Transaktions‑Events** (`transaction.*`) und **Subscription‑Events** (`subscription.*`).
|
||||
- Unterscheidet zwischen **Order‑Events** (`order_*`) 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`) in `checkout_sessions.provider_metadata`.
|
||||
- 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`) in `checkout_sessions.provider_metadata`.
|
||||
- Ergebnis:
|
||||
- Bei `transaction.completed`:
|
||||
- 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 `transaction.failed` / `transaction.cancelled`:
|
||||
- Bei `order_payment_failed` / `order_refunded`:
|
||||
- Session wird auf `failed` gesetzt, Coupons werden als fehlgeschlagen markiert.
|
||||
|
||||
### 6.2.1 Sandbox‑Webhook registrieren
|
||||
|
||||
- Command (Sandbox-Umgebung aktiv):
|
||||
- `php artisan paddle:webhooks:register --traffic-source=simulation`
|
||||
- Command (Test‑Mode aktiv):
|
||||
- `php artisan lemonsqueezy:webhooks:register --test-mode`
|
||||
- Optional mit URL-Override:
|
||||
- `php artisan paddle:webhooks:register --url=https://staging.example.com/paddle/webhook --traffic-source=simulation`
|
||||
- Event‑Liste kommt aus `config/paddle.php` (`webhook_events`). Override möglich:
|
||||
- `php artisan paddle:webhooks:register --events=transaction.completed --events=subscription.created`
|
||||
- `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 Paddle-Events
|
||||
### 6.2.2 Verarbeitete Lemon Squeezy-Events
|
||||
|
||||
Die Webhook‑Handler erwarten mindestens diese Events:
|
||||
|
||||
- `transaction.created`
|
||||
- `transaction.processing`
|
||||
- `transaction.completed` (inkl. Add-ons/Gift‑Vouchers)
|
||||
- `transaction.failed`
|
||||
- `transaction.cancelled`
|
||||
- `subscription.created`
|
||||
- `subscription.updated`
|
||||
- `subscription.paused`
|
||||
- `subscription.resumed`
|
||||
- `subscription.cancelled`
|
||||
- `subscription.past_due`
|
||||
- `order_created`
|
||||
- `order_updated`
|
||||
- `order_payment_failed`
|
||||
- `order_refunded`
|
||||
- `subscription_created`
|
||||
- `subscription_updated`
|
||||
- `subscription_cancelled`
|
||||
- `subscription_expired`
|
||||
- `subscription_paused`
|
||||
|
||||
### 6.3 Subscriptions & TenantPackages
|
||||
|
||||
- Subscription‑Events (`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`).
|
||||
- Subscription‑Events (`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. Paddle zeigt „active“, Tenant nicht):
|
||||
- Subscription‑Events im Paddle‑Dashboard prüfen.
|
||||
- Letzte `subscription.*`‑Events in den Logs, `TenantPackage`‑ und `Tenant`‑Felder gegenprüfen.
|
||||
- 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`‑ und `Tenant`‑Felder gegenprüfen.
|
||||
|
||||
### 6.4 Paket- & Coupon-Synchronisation
|
||||
|
||||
- Pakete:
|
||||
- Artisan‑Command `paddle:sync-packages` (`App\Console\Commands\PaddleSyncPackages`) stößt für ausgewählte oder alle Pakete `SyncPackageToPaddle`/`PullPackageFromPaddle` Jobs an.
|
||||
- Sync‑Jobs nutzen `PaddleCatalogService`, um Produkte/Preise in Paddle zu erstellen/aktualisieren und `paddle_product_id`/`paddle_price_id` lokal zu pflegen.
|
||||
- Artisan‑Command `lemonsqueezy:sync-packages` (`App\Console\Commands\LemonSqueezySyncPackages`) stößt für ausgewählte oder alle Pakete `SyncPackageToLemonSqueezy`/`PullPackageFromLemonSqueezy` Jobs an.
|
||||
- Sync‑Jobs nutzen `LemonSqueezyCatalogService`, um Produkte/Preise in Lemon Squeezy zu erstellen/aktualisieren und `lemonsqueezy_product_id`/`lemonsqueezy_variant_id` lokal zu pflegen.
|
||||
- Coupons:
|
||||
- `SyncCouponToPaddle`‑Job spiegelt interne Coupon‑Konfiguration in Paddle Discounts (`PaddleDiscountService`).
|
||||
- `SyncCouponToLemonSqueezy`‑Job spiegelt interne Coupon‑Konfiguration in Lemon Squeezy Discounts (`LemonSqueezyDiscountService`).
|
||||
- Ops-Sicht:
|
||||
- Bei Katalog‑Abweichungen `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.
|
||||
- Bei Katalog‑Abweichungen `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: Katalog‑Sync fehlgeschlagen
|
||||
|
||||
Wenn der Katalog‑Sync fehlschlägt oder Pakete nicht mehr korrekt verknüpft sind:
|
||||
|
||||
1. **Status im Admin prüfen**
|
||||
- `PackageResource` → Felder `paddle_sync_status`, `paddle_synced_at` und `Letzter Fehler`.
|
||||
- Fehlermeldung stammt aus `paddle_snapshot.error.message`.
|
||||
- `PackageResource` → Felder `lemonsqueezy_sync_status`, `lemonsqueezy_synced_at` und `Letzter Fehler`.
|
||||
- Fehlermeldung stammt aus `lemonsqueezy_snapshot.error.message`.
|
||||
2. **Trockenlauf ausführen**
|
||||
- `php artisan paddle:sync-packages --package=<id|slug> --dry-run`
|
||||
- Prüfe die erzeugten Payload‑Snapshots (in `paddle_snapshot`) auf falsche IDs/Preise.
|
||||
- `php artisan lemonsqueezy:sync-packages --package=<id|slug> --dry-run`
|
||||
- Prüfe die erzeugten Payload‑Snapshots (in `lemonsqueezy_snapshot`) auf falsche IDs/Preise.
|
||||
3. **Mapping prüfen**
|
||||
- Falls `paddle_product_id` oder `paddle_price_id` fehlt oder falsch ist: im Paket‑Admin korrigieren.
|
||||
- Falls `lemonsqueezy_product_id` oder `lemonsqueezy_variant_id` fehlt oder falsch ist: im Paket‑Admin korrigieren.
|
||||
- Bulk‑Sync blockiert unmapped Pakete; gezielte Korrektur vor dem nächsten Lauf ist Pflicht.
|
||||
4. **Sync erneut anstoßen**
|
||||
- `php artisan paddle:sync-packages --package=<id|slug> --queue`
|
||||
- `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 paddle:sync-packages --package=<id|slug> --pull` zum Abgleich mit Paddle.
|
||||
- `php artisan lemonsqueezy:sync-packages --package=<id|slug> --pull` zum Abgleich mit Lemon Squeezy.
|
||||
6. **Logs prüfen**
|
||||
- Erwartete Logeinträge: `Paddle package sync failed`, `Paddle addon sync failed`, `Paddle discount sync failed`.
|
||||
- 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 Paddle‑Aktionen im System abgebildet sind und an welchen Stellen du im Fehlerfall ansetzen kannst.
|
||||
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: Paddle Migration
|
||||
## 7. Production Cutover: Lemon Squeezy Migration
|
||||
|
||||
Diese Checkliste beschreibt den kontrollierten Wechsel auf Paddle in Produktion.
|
||||
Diese Checkliste beschreibt den kontrollierten Wechsel auf Lemon Squeezy in Produktion.
|
||||
|
||||
1. **Vorbereitung (T‑1 Woche)**
|
||||
- Confirm: `PADDLE_ENVIRONMENT=production`, `PADDLE_API_KEY`, `PADDLE_CLIENT_TOKEN`, `PADDLE_WEBHOOK_SECRET`.
|
||||
- Package IDs validieren: alle aktiven Packages haben `paddle_product_id` und `paddle_price_id`.
|
||||
- `paddle:sync-packages --dry-run` auf eine Stichprobe anwenden.
|
||||
- Event‑Liste prüfen: `config/paddle.php` (`webhook_events`).
|
||||
- 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.
|
||||
- Event‑Liste prüfen: `config/lemonsqueezy.php` (`webhook_events`).
|
||||
2. **Staging Smoke (T‑2 Tage)**
|
||||
- `paddle:webhooks:register --traffic-source=simulation` auf Staging ausfuehren.
|
||||
- Testkauf via Paddle Sandbox und Webhook Replay verifizieren.
|
||||
- `lemonsqueezy:webhooks:register --test-mode` auf Staging ausführen.
|
||||
- Testkauf via Lemon Squeezy Test Mode und Webhook Replay verifizieren.
|
||||
3. **Cutover Window (T‑0)**
|
||||
- Marketing‑Checkout kurz einfrieren (kein Checkout waehrend der Umschaltung).
|
||||
- Production Webhook registrieren:
|
||||
- `paddle:webhooks:register --traffic-source=platform --url=https://<prod-domain>/paddle/webhook`
|
||||
- `lemonsqueezy:webhooks:register --url=https://<prod-domain>/lemonsqueezy/webhook`
|
||||
- Queue worker laufen lassen (Queue: `webhooks`/`billing` sofern konfiguriert).
|
||||
4. **Activation**
|
||||
- Erstes Produktions‑Checkout ausfuehren.
|
||||
- Verify: `checkout_sessions.provider_metadata` wird mit `paddle_*` Feldern befuellt.
|
||||
- Verify: `checkout_sessions.provider_metadata` wird mit `lemonsqueezy_*` Feldern befuellt.
|
||||
- Verify: `TenantPackage` aktiv und `subscription_status` korrekt.
|
||||
5. **Rollback (falls notwendig)**
|
||||
- Checkout wieder deaktivieren (Marketing‑Checkout ausblenden).
|
||||
- Paddle Webhook Destination im Paddle Dashboard deaktivieren.
|
||||
- Status und Logs sichern (Webhooks, `paddle_sync` Log).
|
||||
- Lemon Squeezy Webhook Destination im Lemon Squeezy Dashboard deaktivieren.
|
||||
- Status und Logs sichern (Webhooks, `lemonsqueezy-sync` Log).
|
||||
6. **Post‑Cutover (T+1)**
|
||||
- Stichproben auf neue Tenants/Packages.
|
||||
- Monitoring: Fehlgeschlagene Webhooks, Sync‑Fehler, Support Tickets.
|
||||
|
||||
@@ -30,13 +30,12 @@ flowchart LR
|
||||
flowchart LR
|
||||
Tenant[Browser Tenant-Admin] -->|Paket wählen| App[Laravel App]
|
||||
App -->|CheckoutSession anlegen| DB[(DB: checkout_sessions,\n tenant_packages)]
|
||||
App -->|Redirect| Paddle[Paddle Checkout]
|
||||
App -->|Redirect| LemonSqueezy[Lemon Squeezy Checkout]
|
||||
|
||||
Paddle -->|Zahlung erfolgreich| Webhook[Paddle Webhook Endpoint]
|
||||
LemonSqueezy -->|Zahlung erfolgreich| Webhook[Lemon Squeezy Webhook Endpoint]
|
||||
Webhook -->|Event verarbeiten| BillingService[CheckoutWebhookService]
|
||||
BillingService -->|TenantPackage aktualisieren| DB
|
||||
|
||||
DB --> App
|
||||
App --> Tenant
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Dieses How‑to beschreibt, wie du für einen Tenant kurz vor Vertragsende einen
|
||||
## 4. Billing-Unterlagen
|
||||
|
||||
- Rechnungen / Zahlungsbelege:
|
||||
- Paddle‑Belege (Links oder PDFs).
|
||||
- Lemon Squeezy‑Belege (Links oder PDFs).
|
||||
- Interne Rechnungs‑PDFs (falls generiert).
|
||||
|
||||
## 5. Nach dem Export
|
||||
@@ -49,4 +49,3 @@ Siehe auch:
|
||||
|
||||
- `docs/ops/compliance-dsgvo-ops.md`
|
||||
- `docs/ops/backup-restore.md`
|
||||
|
||||
|
||||
@@ -13,34 +13,34 @@ Bevor du nachschaust:
|
||||
- Tenant‑ID oder Tenant‑Slug.
|
||||
- Betroffenes Paket (Name oder Beschreibung, z.B. „Pro‑Paket 79 €“).
|
||||
- Zeitpunkt der Zahlung (Datum/Uhrzeit, ggf. Screenshot).
|
||||
- Ggf. Auszug aus der Paddle‑Bestätigung (ohne vollständige Kartendaten!).
|
||||
- Ggf. Auszug aus der Lemon Squeezy‑Bestätigung (ohne vollständige Kartendaten!).
|
||||
|
||||
Diese Infos erlauben dir, die korrekte Transaktion sowohl in Paddle als auch im Backend zu finden.
|
||||
Diese Infos erlauben dir, die korrekte Transaktion sowohl in Lemon Squeezy als auch im Backend zu finden.
|
||||
|
||||
## 2. Paddle-Status prüfen
|
||||
## 2. Lemon Squeezy-Status prüfen
|
||||
|
||||
1. Im Paddle‑Dashboard:
|
||||
- Suche nach E‑Mail, Tenant‑Name oder dem vom Tenant genannten Transaktions‑Identifier.
|
||||
- Stelle sicher, dass die Zahlung dort als „completed“/„paid“ markiert ist.
|
||||
1. Im Lemon Squeezy‑Dashboard:
|
||||
- Suche nach E‑Mail, Tenant‑Name oder der vom Tenant genannten Order‑ID.
|
||||
- Stelle sicher, dass die Zahlung dort als „paid“/„completed“ markiert ist.
|
||||
2. Notiere:
|
||||
- Paddle‑Transaction‑ID und ggf. Checkout‑ID.
|
||||
- Lemon Squeezy‑Order‑ID und ggf. Checkout‑ID.
|
||||
- Status (paid/processing/failed/cancelled).
|
||||
|
||||
Wenn Paddle die Zahlung nicht als erfolgreich zeigt, ist dies primär ein Finance‑/Customer‑Topic – ggf. mit Customer Support klären, ob eine neue Zahlung oder Klärung mit dem Kunden notwendig ist.
|
||||
Wenn Lemon Squeezy die Zahlung nicht als erfolgreich zeigt, ist dies primär ein Finance‑/Customer‑Topic – 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 Paddle:
|
||||
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 = paddle`.
|
||||
- Prüfe `provider_metadata` auf `paddle_last_event`, `paddle_status`, `paddle_checkout_id`.
|
||||
- Wenn du die Session über Paddle‑Metadaten finden möchtest:
|
||||
- `paddle_checkout_id` aus dem Webhook/Provider‑Metadata oder `transaction_id` verwenden.
|
||||
- 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 Squeezy‑Metadaten 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 Provider‑Referenz existiert:
|
||||
- z.B. `provider = 'paddle'`, `provider_id` = Transaction‑ID.
|
||||
- z.B. `provider = 'lemonsqueezy'`, `provider_id` = Order‑ID.
|
||||
3. `tenant_packages`:
|
||||
- Prüfe, ob es einen aktiven Eintrag für `(tenant_id, package_id)` gibt:
|
||||
- `active = 1`, `expires_at` in der Zukunft.
|
||||
@@ -51,34 +51,34 @@ Wenn `checkout_sessions` noch nicht auf `completed` steht oder `tenant_packages`
|
||||
|
||||
1. Logs prüfen:
|
||||
- `storage/logs/laravel.log` und ggf. `billing`‑Channel.
|
||||
- Suche nach Einträgen von `PaddleWebhookController` / `CheckoutWebhookService` rund um den Zahlungszeitpunkt.
|
||||
- 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] Paddle session not resolved`).
|
||||
- Idempotenz‑Lock (`Paddle lock busy`) hat dazu geführt, dass Event nur teilweise verarbeitet wurde.
|
||||
- Webhook konnte die Session nicht auflösen (`[CheckoutWebhook] Lemon Squeezy session not resolved`).
|
||||
- Idempotenz‑Lock (`Lemon Squeezy lock busy`) hat dazu geführt, dass Event nur teilweise verarbeitet wurde.
|
||||
|
||||
## 5. Korrektur-Schritte
|
||||
|
||||
### 5.1 Automatischer Replay (empfohlen)
|
||||
|
||||
1. Im Paddle‑Dashboard:
|
||||
- Den betreffenden `transaction.*`‑Event finden.
|
||||
1. Im Lemon Squeezy‑Dashboard:
|
||||
- Den betreffenden `order_*`‑Event finden.
|
||||
- Webhook‑Replay auslösen.
|
||||
2. In den Logs beobachten:
|
||||
- Ob `CheckoutWebhookService::handlePaddleEvent()` diesmal die Session findet und `CheckoutAssignmentService::finalise()` ausführt.
|
||||
- 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 Paddle die Zahlung eindeutig als erfolgreich listet.
|
||||
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 Paddle‑Daten passen.
|
||||
- `active = 1`,
|
||||
- `purchased_at` und `expires_at` zu Lemon Squeezy‑Daten passen.
|
||||
2. `package_purchases` ergänzen:
|
||||
- Sicherstellen, dass die Zahlung als Zeile mit `provider = 'paddle'`, `provider_id = Transaction‑ID` und passender `price` existiert (für spätere Audits).
|
||||
- Sicherstellen, dass die Zahlung als Zeile mit `provider = 'lemonsqueezy'`, `provider_id = Order‑ID` 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:
|
||||
@@ -88,7 +88,7 @@ Nur anwenden, wenn klare Freigabe vorliegt und Paddle die Zahlung eindeutig als
|
||||
|
||||
- Sobald der Backend‑Status korrigiert ist:
|
||||
- Kurz bestätigen, dass das Paket aktiv ist und welche Auswirkungen das hat (z.B. neue Limits, verlängerte Galerie).
|
||||
- Falls Paddle die Zahlung nicht als erfolgreich führt:
|
||||
- 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 How‑to sollte dem Support/On‑Call helfen, den gängigsten Billing‑Fehlerfall strukturiert abzuarbeiten. Für tiefere Ursachenanalysen siehe `docs/ops/billing-ops.md`.
|
||||
|
||||
@@ -49,7 +49,7 @@ Nutze bei Public‑API‑Problems zusätzlich das `docs/ops/deployment/public-ap
|
||||
- Fälle: FTP nicht erreichbar, Ingest nicht laufend, falsche Credentials, Security‑Vorfälle.
|
||||
- **Abrechnung & Billing**
|
||||
- Siehe `docs/ops/billing-ops.md`.
|
||||
- Fälle: Paddle/RevenueCat‑Webhook‑Fehler, falsche Paket‑Zustände, doppelte/fehlende Buchungen.
|
||||
- Fälle: Lemon Squeezy/RevenueCat‑Webhook‑Fehler, falsche Paket‑Zustände, doppelte/fehlende Buchungen.
|
||||
|
||||
Dieses Dokument verweist immer nur auf die jeweils tieferen Runbooks – bei konkreten Problemen gehst du dort in die Details.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Dieses Dokument sammelt die wichtigsten Monitoring‑Punkte der Plattform und so
|
||||
- HTTP 5xx/4xx spitzenweise, gruppiert nach Route/Service.
|
||||
- Applikations‑Logs mit Error/Warning‑Level.
|
||||
- **Billing & Webhooks**
|
||||
- Fehlgeschlagene Paddle/RevenueCat‑Webhooks.
|
||||
- Fehlgeschlagene Lemon Squeezy/RevenueCat‑Webhooks.
|
||||
- Differenz zwischen erwarteten und verarbeiteten Zahlungen (optional).
|
||||
|
||||
## 2. Werkzeuge & Quellen
|
||||
@@ -134,7 +134,7 @@ Nachfolgend beispielhafte Formulierungen, wie Alerts unabhängig vom verwendeten
|
||||
|
||||
### 6.4 Billing-Webhook Alert
|
||||
|
||||
**Ziel**: Fehler bei Paddle/RevenueCat‑Webhook‑Verarbeitung erkennen.
|
||||
**Ziel**: Fehler bei Lemon Squeezy/RevenueCat‑Webhook‑Verarbeitung erkennen.
|
||||
|
||||
- Bedingung:
|
||||
- Mehr als 10 fehlgeschlagene Webhook‑Verarbeitungen innerhalb von 10 Minuten, oder Verhältnis `failed/success` > 0,2.
|
||||
|
||||
@@ -34,7 +34,7 @@ Dieser Spickzettel ist für On‑Call‑Personen gedacht, die im Incident schnel
|
||||
- API‑Fehler‑Rate (5xx, 4xx für Public API).
|
||||
- Queue‑Backlog (`default`, `media-storage`, `media-security`, `notifications`).
|
||||
- Response‑Time Guest‑/Tenant‑PWA.
|
||||
- Paddle‑Webhook‑Fehler (falls im Monitoring abgebildet).
|
||||
- Lemon Squeezy‑Webhook‑Fehler (falls im Monitoring abgebildet).
|
||||
|
||||
> Ergänze hier konkrete Links zu euren Grafana/Datadog‑Dashboards, sobald diese stabil sind.
|
||||
|
||||
@@ -45,4 +45,3 @@ Dieser Spickzettel ist für On‑Call‑Personen gedacht, die im Incident schnel
|
||||
- SEV‑3: Einzelne Tenants oder Funktionen, Workaround vorhanden.
|
||||
|
||||
Siehe auch `docs/ops/incidents-major.md` für detaillierte SEV‑Definitionen und Kommunikationsregeln.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Ziel ist, dass du von hier aus schnell zu den relevanten Runbooks und Referenzen
|
||||
- Laravel App + Nginx + Redis + MySQL.
|
||||
- Async‑Pipeline: Queues (`default`, `media-storage`, `media-security`, `notifications`) und Horizon.
|
||||
- Satelliten: Photobooth‑FTP + Control‑Service, Docs‑Site (`/internal-docs`), Monitoring/Dokploy.
|
||||
- Externe Dienste: Paddle (Billing), RevenueCat (Mobile‑Abos), E‑Mail Provider, Logging/Monitoring (Loki/Grafana o.ä.).
|
||||
- Externe Dienste: Lemon Squeezy (Billing), RevenueCat (Mobile‑Abos), E‑Mail Provider, Logging/Monitoring (Loki/Grafana o.ä.).
|
||||
|
||||
> TODO: Ergänze ein Architekturdiagramm aus Sicht des Betriebs (z.B. als PNG oder PlantUML) und verlinke es hier.
|
||||
|
||||
@@ -61,7 +61,7 @@ Diese Seiten sollen praktische Steuerung über Tenant‑Admins und die Guest‑E
|
||||
- **Compliance‑Tools:** DSGVO‑Export‑Requests und Retention‑Overrides pro Tenant/Event.
|
||||
- **Superadmin‑Audit‑Log:** Jede Admin‑Aktion nachvollziehbar (ohne PII‑Payloads).
|
||||
- **Tenant‑Announcements:** Zielgerichtete Hinweise/Release‑Notes an Tenant‑Admins, inkl. Zeitplanung.
|
||||
- **Integrations‑Health:** Status‑Board für Paddle/RevenueCat/Webhooks inkl. Störungen.
|
||||
- **Integrations‑Health:** Status‑Board für Lemon Squeezy/RevenueCat/Webhooks inkl. Störungen.
|
||||
|
||||
## 2. Deployments & Infrastruktur
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Dieses Dokument beschreibt, welche Informationen der Support einsammeln sollte,
|
||||
- Zeitpunkt der letzten sichtbaren Fotos.
|
||||
- Ob die Photobooth selbst Fehler anzeigt.
|
||||
- **Paket nicht aktiv / Limits falsch**
|
||||
- Bestellnummer / Paddle‑Checkout‑ID (falls vorhanden).
|
||||
- Bestellnummer / Lemon Squeezy‑Checkout‑ID (falls vorhanden).
|
||||
- Zeitpunkt der Zahlung.
|
||||
- Welches Paket wurde erwartet?
|
||||
|
||||
@@ -47,4 +47,3 @@ Siehe auch:
|
||||
|
||||
- `docs/ops/oncall-roles.md`
|
||||
- `docs/ops/oncall-cheatsheet.md`
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ This document tracks the UI/E2E automation efforts. The suites now live under `t
|
||||
- `npm install`
|
||||
- Laravel app running at `http://localhost:8000`
|
||||
- Seeded tenant admin account (see below)
|
||||
- Paddle sandbox credentials/config applied to the local `.env`
|
||||
- Lemon Squeezy sandbox credentials/config applied to the local `.env`
|
||||
|
||||
## Deterministic Data
|
||||
|
||||
@@ -42,7 +42,7 @@ The backend exposes `/api/_testing/...` endpoints (local/testing env only):
|
||||
| `GET /api/_testing/mailbox` | Returns every captured email (see `App\Testing\Mailbox`). |
|
||||
| `DELETE /api/_testing/mailbox` | Flushes the captured emails. |
|
||||
| `GET /api/_testing/checkout/sessions/latest` | Fetches the newest checkout session for a given email/tenant filter. |
|
||||
| `POST /api/_testing/checkout/sessions/{session}/simulate-paddle` | Triggers the Paddle webhook handler for the given session with a mock payload. |
|
||||
| `POST /api/_testing/checkout/sessions/{session}/simulate-lemonsqueezy` | Triggers the Lemon Squeezy webhook handler for the given session with a mock payload. |
|
||||
| `GET /api/_testing/events/join-token` | Resolves (and optionally regenerates) a join token + QR for a given event ID or slug. |
|
||||
| `POST /api/_testing/guest-events` | Provisions a deterministic guest/tenant event with sample tasks and returns its slug + join token. |
|
||||
|
||||
@@ -64,7 +64,7 @@ This section provides a staged, repeatable checklist for dynamic security review
|
||||
### Environment Assumptions (Required)
|
||||
- **Run in staging/test only** — never against production data.
|
||||
- **Dedicated test tenants/users** — use seeded accounts (see above) and avoid real customer data.
|
||||
- **Sandbox billing** — Paddle sandbox and mock webhook endpoints only.
|
||||
- **Sandbox billing** — Lemon Squeezy sandbox and mock webhook endpoints only.
|
||||
- **Testing token enabled** — set `E2E_TESTING_TOKEN` and ensure the backend accepts it for `/api/_testing/*`.
|
||||
- **Stable base URL** — set `E2E_BASE_URL` to the target environment (`http://localhost:8000` or staging).
|
||||
- **Email sink** — use `/api/_testing/mailbox` instead of real email delivery.
|
||||
@@ -95,7 +95,7 @@ This section provides a staged, repeatable checklist for dynamic security review
|
||||
### Checklist: Webhooks/Billing (Dynamic)
|
||||
1) **Signature validation**: invalid signature is rejected (401/403) and logged.
|
||||
2) **Freshness**: stale timestamps are rejected; replayed webhook payloads are idempotent.
|
||||
3) **Paddle sandbox flow**: use `/api/_testing/checkout/sessions/{session}/simulate-paddle` to simulate success/failure; verify ledger updates.
|
||||
3) **Lemon Squeezy sandbox flow**: use `/api/_testing/checkout/sessions/{session}/simulate-lemonsqueezy` to simulate success/failure; verify ledger updates.
|
||||
4) **Webhook retries**: transient failures produce retry‑safe behavior (no duplicate ledger entries).
|
||||
5) **Error handling**: malformed payload returns 4xx (not 500), with minimal error detail.
|
||||
|
||||
@@ -103,9 +103,9 @@ This section provides a staged, repeatable checklist for dynamic security review
|
||||
|
||||
| Suite | Location | Primary Coverage |
|
||||
| --- | --- | --- |
|
||||
| Purchase | `tests/ui/purchase` | Marketing site package selection, checkout flow, coupon handling, Paddle sandbox hand-off, post-purchase dashboard verification. |
|
||||
| Purchase | `tests/ui/purchase` | Marketing site package selection, checkout flow, coupon handling, Lemon Squeezy sandbox hand-off, post-purchase dashboard verification. |
|
||||
| Auth | `tests/ui/auth` | Registration/login fuzzing, password reset, Social/OAuth hooks, email delivery assertions, throttling/error UX. |
|
||||
| Admin | `tests/ui/admin` | Tenant onboarding wizard, dashboard widgets, event creation (incl. wedding preset), task assignment, join-token + QR verification, Paddle billing history. |
|
||||
| Admin | `tests/ui/admin` | Tenant onboarding wizard, dashboard widgets, event creation (incl. wedding preset), task assignment, join-token + QR verification, Lemon Squeezy billing history. |
|
||||
| Guest | `tests/ui/guest` | Guest PWA onboarding, join-token entry, offline sync, uploads/likes/tasks for ≥15 guests, achievement + notification UX. |
|
||||
|
||||
Each suite should be executable independently to keep CI fast and to allow targeted debugging.
|
||||
@@ -123,7 +123,7 @@ Traces are recorded on first retry (`playwright.config.ts`); open via `npx playw
|
||||
|
||||
1. **Purchase suite**
|
||||
- Seed coupons via helper.
|
||||
- Cover `/de/packages` Standard selection, coupon states (valid/invalid/expired), Paddle inline + hosted checkout using sandbox card `4000 0566 5566 5557 / CVV 100`.
|
||||
- Cover `/de/packages` Standard selection, coupon states (valid/invalid/expired), Lemon Squeezy inline + hosted checkout using sandbox card `4000 0566 5566 5557 / CVV 100`.
|
||||
- Simulate webhook success (helper endpoint TBD) so dashboard reflects the purchase.
|
||||
- Assert confirmation emails captured via mailbox API.
|
||||
|
||||
@@ -135,7 +135,7 @@ Traces are recorded on first retry (`playwright.config.ts`); open via `npx playw
|
||||
3. **Admin suite**
|
||||
- After purchase, log into `/event-admin`, confirm latest package appears, create a wedding event, assign predefined tasks, fetch join token + QR (helper should expose raw token/URL).
|
||||
- Cover task management UX (assign, reorder, complete).
|
||||
- Verify billing history shows the recent Paddle transaction.
|
||||
- Verify billing history shows the recent Lemon Squeezy transaction.
|
||||
|
||||
4. **Guest suite**
|
||||
- Use join token from Admin suite (or seed via helper) to onboard 15 simulated guests in parallel contexts.
|
||||
@@ -143,7 +143,7 @@ Traces are recorded on first retry (`playwright.config.ts`); open via `npx playw
|
||||
- Validate guest-facing error states (expired token, upload failure, network loss).
|
||||
|
||||
5. **Shared helpers (backend + Playwright)**
|
||||
- Webhook trigger endpoint for Paddle sandbox.
|
||||
- Webhook trigger endpoint for Lemon Squeezy sandbox.
|
||||
- Join token + QR extraction endpoint for tests.
|
||||
- Task template seeding helper.
|
||||
- Optional guest factory endpoint to mint attendees quickly.
|
||||
|
||||
Reference in New Issue
Block a user