Skip to content

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_tax fehl. Alternativ für einen ersten Durchlauf BILLING_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.sh

Das Skript

  1. holt mit stripe listen --print-secret das Signing Secret der CLI,
  2. startet php, worker und scheduler mit STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET und leerem STRIPE_API_BASE (also gegen api.stripe.com statt stripe-mock) neu,
  3. legt mit app:billing:sync-products das 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),
  4. leitet checkout.session.completed und checkout.session.async_payment_succeeded mit stripe listen --forward-to http://localhost:8480/api/billing/stripe/webhook an 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 ​

ZahlungsmittelVerhalten
4242 4242 4242 4242Zahlung erfolgreich
4000 0025 0000 3155verlangt 3-D Secure (im Testdialog bestätigen)
4000 0000 0000 9995abgelehnt (unzureichende Deckung)
SEPA, IBAN DE89370400440532013000verzö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.

#SchrittErwartet
1Einstellungen → GuthabenKontostand 1, vier Pakete (4,95 € bis 49,50 €, inkl. MwSt.), noch keine Rechnungen, kein Portal-Link
2Eine Umfrage veröffentlichenKontostand 0, Umfrage live mit paidUntil in einem Jahr; Buchung „Veröffentlichen“, Audit credits.used
3Dieselbe Umfrage ändern und erneut veröffentlichenneue Version, Kontostand bleibt 0, paidUntil unverändert
4Zweite Umfrage veröffentlichen402 no_credits, das Dashboard bietet den Kauf an
5Paket 10 kaufen: Checkout mit 4242…, Adresse in DeutschlandCheckout 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
6Rechnungsliste, Audit-LogRechnung „bezahlt“ mit PDF-Link, USt. ausgewiesen; Audit credits.added mit reason: purchase
7Paket 5 mit Adresse in Österreich und USt-IdNr. ATU12345678Reverse Charge, Rechnung ohne USt. mit Hinweis; Betrag laut Stripe Tax (Bruttopreis, siehe Steuern) – Ergebnis notieren; Kontostand 15
8Paket 5 per SEPA-Lastschrifterst checkout.session.completed ohne Buchung, dann checkout.session.async_payment_succeeded: Kontostand 20 – genau einmal gebucht
9Zweite 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
11Erstattung: Zahlung aus Schritt 5 im Stripe-Dashboard erstatten, dann app:credits:grant <slug> -10 --reason=refundKontostand um 10 niedriger (darf negativ werden), Buchung „Erstattung“
12Webhook doppelt senden: im Stripe-Dashboard ein Ereignis „Resend“Antwort 200 mit outcome: duplicate, keine zweite Buchung
13Falsches Secret: curl -X POST -H 'Stripe-Signature: t=1,v1=x' localhost:8480/api/billing/stripe/webhook400

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 --restore

startet 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_SECRET braucht nur die API).
  • Webhook-Endpunkt mit checkout.session.completed und checkout.session.async_payment_succeeded anlegen.
  • app:billing:sync-products einmal im Live-Modus ausführen.
  • Rechnungsdesign, Firmendaten und Steuer-Registrierungen (Deutschland, OSS) im Stripe-Dashboard; Mails von Stripe (Belege) nach Wunsch aktivieren.