fix: Stripe-Ratenplan bei Prüfung anhalten, doppelte Pläne verhindern

Befunde aus dem Sicherheits-Review des vorherigen Commits:

- Gerät eine Buchung in "Prüfung nötig" (Rückbuchung, Doppelzahlung ...),
  wird der Stripe-Plan beendet und offene Rechnungen storniert. Nach
  Bestätigung legt der Worker einen neuen Plan für die offenen Raten an.
  Vorher zog Stripe auch bei Buchungen in Prüfung weiter ein.
- Vor dem Anlegen wird bei Stripe nach einem laufenden Plan der Buchung
  gesucht; der Idempotenzschlüssel enthält Generation und Start. Verhindert
  einen zweiten Plan nach verlorener ID und die Rückgabe des alten,
  beendeten Plans nach einer Prüfung.
- Start bei bereits verstrichener Fälligkeit deterministisch am nächsten
  9-Uhr-Termin, letzte Rate bleibt am Stichtag.
- Rückbuchungs-Event meldet die betroffene Buchung zurück.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-06 06:28:41 +02:00
1 parent c1ad9ad2ca
commit 1ce7947d93
8 files changed
+168 -23

No files matched your search

+2 -1
View File
@@ -56,7 +56,8 @@ Der gehostete Checkout bildet den Ablauf vollständig ab: Betrag, Ablaufzeit, da
- Storno beendet den Zeitplan sofort und storniert offene Rechnungen (`void`). Scheitert das bei Stripe, holt der Worker es nach; die Zahlungsmethode wird erst danach getrennt.
- Fehlschlag: Stripe wiederholt nach den Einstellungen unter „Revenue recovery“ (Smart Retries). Die Rate steht auf `FAILED` mit nächstem Versuch, die Kundin bekommt eine Mail mit Zahlungslink. Gibt Stripe auf, wird die Buchung „Prüfung nötig“.
- Verlangt die Bank eine Bestätigung, geht die Rate auf `REQUIRES_ACTION`. Die Kundin zahlt über Stripes Rechnungsseite (`hosted_invoice_url`), auch mit einer anderen Zahlungsmethode. Eine eigene Checkout-Session gibt es bewusst nicht, sonst würde Stripe parallel weiter einziehen.
- Idempotenz: Der Zeitplan wird mit Schlüssel `schedule:<buchung>` angelegt. Doppelte oder ungeordnete Events werden über die Rechnungs-ID und den PaymentIntent nur einmal verbucht.
- „Prüfung nötig“ hält den Plan an: Gerät eine Buchung in Prüfung (z. B. Rückbuchung, Doppelzahlung), wird der Stripe-Plan beendet, offene Rechnungen werden storniert und unbezahlte Folgeraten gehen zurück auf `PENDING`. Bestätigt ein Mensch die Buchung, legt der Worker einen neuen Plan für die offenen Raten an (Start an der Fälligkeit oder, falls vorbei, am nächsten 9-Uhr-Termin; letzte Rate bleibt am Stichtag). So zieht Stripe wie früher der Worker nur bei bestätigten Buchungen ein.
- Idempotenz: Vor dem Anlegen sucht die Anwendung bei Stripe nach einem laufenden Plan dieser Buchung (Metadaten `bookingId`) und übernimmt ihn, auch wenn das Speichern der ID früher scheiterte. Angelegt wird mit Schlüssel `schedule:<buchung>:<generation>:<start>`; die Generation steigt mit jedem Anhalten, sonst lieferte Stripe binnen 24 Stunden den alten, beendeten Plan zurück. Doppelte oder ungeordnete Events werden über die Rechnungs-ID und den PaymentIntent nur einmal verbucht.
- Verlorene Webhooks: Der Worker legt fehlende Zeitpläne an und fragt bei Raten, die seit 6 Stunden fällig und nicht verbucht sind, die Rechnungen des Zeitplans ab.
- Zustimmung: Vor der Zahlung wird ausdrücklich dem Einzug zugestimmt, mit Zeitstempel `installmentMandateAt`.
- Keine Gleichsetzung mit Klarna: Es ist die eigene Ratenvereinbarung des Veranstalters, das Ausfallrisiko bleibt beim Veranstalter. Im Livemodus ist sie erst nach ausdrücklicher Freigabe verfügbar.