Appearance
Runbook: Abrechnung im Stripe-Testmodus prüfen
Die automatischen Tests laufen gegen stripe-mock und simulierte, signierte Webhooks (PHPUnit tests/Billing, E2E e2e/billing.spec.ts). Was stripe-mock nicht kann – die echte Checkout-Seite, das Customer Portal, echte Webhooks, Rechnungen und Stripe Tax mit Bruttopreisen –, prüft dieses Runbook einmal manuell im Testmodus eines Stripe-Kontos. Dauer: etwa eine halbe Stunde.
Voraussetzungen
- Stripe-Konto mit Zugriff auf den Testmodus (Schalter „Test mode“ im Dashboard). Im Testmodus fließt kein Geld; alles lässt sich unter Developers → Test data wieder löschen.
- Settings → Tax: Stripe Tax aktiviert, Firmensitz (Deutschland) und eine Registrierung eingetragen – sonst schlägt der Checkout mit
automatic_taxfehl. Alternativ für einen ersten DurchlaufBILLING_AUTOMATIC_TAX=0. - Für den SEPA-Schritt: Settings → Payment methods → SEPA-Lastschrift aktiviert.
- Stripe CLI installiert und angemeldet (
stripe login). - Lokaler Stack läuft (
make up), Mailpit unter http://localhost:8482. - Ein Test-Secret-Key
sk_test_…(Developers → API keys).
1. Umschalten und Webhooks weiterleiten
sh
STRIPE_SECRET_KEY=sk_test_… ci/billing/test-mode.shDas Skript
- holt mit
stripe listen --print-secretdas Signing Secret der CLI, - startet
php,workerundschedulermitSTRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETund leeremSTRIPE_API_BASE(also gegenapi.stripe.comstatt stripe-mock) neu, - legt mit
app:billing:sync-productsdas Produkt, die Paketpreise und die Portal-Konfiguration an (ein zweiter Lauf muss nur „unchanged“ melden – Idempotenz prüfen:docker compose exec php bin/console app:billing:sync-products), - leitet
checkout.session.completedundcheckout.session.async_payment_succeededmitstripe listen --forward-to http://localhost:8480/api/billing/stripe/webhookan die lokale API weiter.
Das Terminal offen lassen; jede weitergeleitete Anfrage erscheint dort mit ihrem HTTP-Status (erwartet: 200).
Ohne Skript (z. B. gegen Staging): Variablen in der App setzen, app:billing:sync-products im Worker-Container ausführen, im Stripe-Dashboard einen Webhook-Endpunkt https://api.<domain>/api/billing/stripe/webhook mit den beiden Ereignissen anlegen (siehe Abrechnung → Webhook-Endpunkt).
2. Testzahlungsmittel
| Zahlungsmittel | Verhalten |
|---|---|
4242 4242 4242 4242 | Zahlung erfolgreich |
4000 0025 0000 3155 | verlangt 3-D Secure (im Testdialog bestätigen) |
4000 0000 0000 9995 | abgelehnt (unzureichende Deckung) |
SEPA, IBAN DE89370400440532013000 | verzögert: erst „processing“, nach kurzer Zeit erfolgreich |
Ablaufdatum: beliebig in der Zukunft, CVC: beliebig, Name/Adresse: beliebig. Test-USt-IdNr.: z. B. ATU12345678 (Stripe akzeptiert im Testmodus gültig aussehende Nummern).
3. Durchlauf
Vorbereitung: im Dashboard (http://localhost:5173) neu registrieren, Organisation auf Standard setzen: docker compose exec php bin/console app:organization:set-plan <slug> standard (im Entwicklungs-Stack starten neue Organisationen mit internal, die kein Guthaben brauchen). Das Willkommensguthaben bucht nur die Registrierung auf Standard; nach dem Umstellen ggf. docker compose exec php bin/console app:credits:grant <slug> 1 ausführen.
Nach jedem Schritt: docker compose exec php bin/console app:billing:show <slug> und die Guthaben-Seite neu laden.
| # | Schritt | Erwartet |
|---|---|---|
| 1 | Einstellungen → Guthaben | Kontostand 1, vier Pakete (4,95 € bis 49,50 €, inkl. MwSt.), noch keine Rechnungen, kein Portal-Link |
| 2 | Eine Umfrage veröffentlichen | Kontostand 0, Umfrage live mit paidUntil in einem Jahr; Buchung „Veröffentlichen“, Audit credits.used |
| 3 | Dieselbe Umfrage ändern und erneut veröffentlichen | neue Version, Kontostand bleibt 0, paidUntil unverändert |
| 4 | Zweite Umfrage veröffentlichen | 402 no_credits, das Dashboard bietet den Kauf an |
| 5 | Paket 10 kaufen: Checkout mit 4242…, Adresse in Deutschland | Checkout zeigt 9,90 € inkl. 1,58 € USt.; zurück auf der Guthaben-Seite, nach wenigen Sekunden Kontostand 10; stripe listen zeigt 200 für checkout.session.completed |
| 6 | Rechnungsliste, Audit-Log | Rechnung „bezahlt“ mit PDF-Link, USt. ausgewiesen; Audit credits.added mit reason: purchase |
| 7 | Paket 5 mit Adresse in Österreich und USt-IdNr. ATU12345678 | Reverse Charge, Rechnung ohne USt. mit Hinweis; Betrag laut Stripe Tax (Bruttopreis, siehe Steuern) – Ergebnis notieren; Kontostand 15 |
| 8 | Paket 5 per SEPA-Lastschrift | erst checkout.session.completed ohne Buchung, dann checkout.session.async_payment_succeeded: Kontostand 20 – genau einmal gebucht |
| 9 | Zweite Umfrage veröffentlichen, danach die erste verlängern (POST /api/surveys/{id}/extend oder Dashboard) | je ein Guthaben weniger; paidUntil der ersten Umfrage ein Jahr nach dem bisherigen Ende |
| 10 | „Rechnungen & Rechnungsadresse verwalten“ | Rechnungshistorie, Adresse und USt-IdNr. änderbar; kein Abo, keine Kündigung; „Zurück“ führt auf die Guthaben-Seite |
| 11 | Erstattung: Zahlung aus Schritt 5 im Stripe-Dashboard erstatten, dann app:credits:grant <slug> -10 --reason=refund | Kontostand um 10 niedriger (darf negativ werden), Buchung „Erstattung“ |
| 12 | Webhook doppelt senden: im Stripe-Dashboard ein Ereignis „Resend“ | Antwort 200 mit outcome: duplicate, keine zweite Buchung |
| 13 | Falsches Secret: curl -X POST -H 'Stripe-Signature: t=1,v1=x' localhost:8480/api/billing/stripe/webhook | 400 |
Ablauf nach einem Jahr (ohne Stripe): in der Datenbank paid_until einer Umfrage in die Vergangenheit setzen und bis zu einer Minute warten (Scheduler, RunSurveySchedule) – die Umfrage schließt mit reason: expired; Verlängern öffnet sie wieder. Abgedeckt ist das auch in tests/Survey/PrepaidPublishTest.php.
Ergebnisse (Datum, Stripe-Konto, Abweichungen) im Ticket festhalten.
4. Zurückschalten
Ctrl+C beendet die Weiterleitung, danach
sh
ci/billing/test-mode.sh --restorestartet die Container wieder gegen stripe-mock. Testdaten im Stripe-Dashboard bei Bedarf unter Developers → Test data → Delete all test data löschen.
Live-Schaltung
- Live-Schlüssel (
sk_live_…, besser ein Restricted Key mit Schreibrechten auf Customers, Checkout Sessions, Billing Portal, Products, Prices und Leserechten auf Invoices) und das Signing Secret des Live-Endpunkts als Secrets in API, Worker und Scheduler (STRIPE_WEBHOOK_SECRETbraucht nur die API). - Webhook-Endpunkt mit
checkout.session.completedundcheckout.session.async_payment_succeededanlegen. app:billing:sync-productseinmal im Live-Modus ausführen.- Rechnungsdesign, Firmendaten und Steuer-Registrierungen (Deutschland, OSS) im Stripe-Dashboard; Mails von Stripe (Belege) nach Wunsch aktivieren.