fix: Worker übersteht Stripe-Fehler einzelner Buchungen, Rechnungsentwürfe beim Beenden löschen

Befunde aus dem Sicherheits-Review:

- Anhalten, Beenden und Abgleich der Ratenpläne fangen Fehler je Buchung ab.
  Ein Stripe-Fehler bei einer Buchung bricht nicht mehr den ganzen
  Worker-Lauf ab (E-Mails, Reservierungen liefen sonst nicht weiter).
  Teilfehler setzen den Exit-Code 1, damit der CronJob-Lauf sichtbar
  fehlschlägt.
- Beim Beenden eines Plans werden neben offenen Rechnungen auch Entwürfe
  gelöscht. Laut Stripe-Testuhr bleibt ein Entwurf nach dem Beenden zwar
  liegen und wird nicht eingezogen; das Löschen schließt das aus.

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:34:20 +02:00
1 parent 1ce7947d93
commit a28d18c3e6
5 files changed
+43 -17

No files matched your search

+4 -4
View File
@@ -10,7 +10,7 @@
import { audit, WORKER } from "./audit";
import type { Db } from "./db";
import { flushEmails } from "./email";
import { ensureInstallmentSchedule, holdSchedulesInReview, settleInstallmentInvoice, stopCanceledInstallmentPlans } from "./installment-billing";
import { eachBooking, ensureInstallmentSchedule, holdSchedulesInReview, settleInstallmentInvoice, stopCanceledInstallmentPlans } from "./installment-billing";
import { applyCheckoutExpired, applyFirstPaymentFailed, type EventContext } from "./payment-events";
import type { PaymentGateway } from "./payment-gateway";
import { purgeRateLimits } from "./rate-limit";
@@ -112,13 +112,13 @@ export async function reconcileInstallmentInvoices(db: Db, gateway: PaymentGatew
take: 50,
});
let settled = 0;
for (const booking of bookings) {
const failed = await eachBooking(bookings, "abgleichen", async (booking) => {
const invoices = await gateway.listScheduleInvoices(booking.stripeScheduleId!);
for (const invoice of invoices.filter((i) => i.status === "paid")) {
if (await settleInstallmentInvoice(db, gateway, invoice, ctx, WORKER)) settled++;
}
}
return { checked: bookings.length, settled };
});
return { checked: bookings.length, settled, failed };
}
/** Faengt verzoegerte erste Zahlungen (SEPA) auf, deren Ergebnis-Webhook ausblieb. */