feat: Folgeraten über Stripe Billing einziehen
Statt eigener Off-Session-Einzüge durch den Worker legt die Anwendung nach bezahlter erster Rate einen Stripe Subscription Schedule an. Stripe übernimmt Einzug, Wiederholungen (Smart Retries), 3-D-Secure-Links und Mahnungen. - Gateway: Zeitplan anlegen/beenden, Rechnungen lesen; chargeOffSession entfällt - Webhook: invoice.paid, invoice.payment_failed, invoice.payment_action_required - Worker: fehlende Zeitpläne anlegen, überfällige Raten abgleichen, Zeitpläne stornierter Buchungen beenden - Storno beendet den Zeitplan und storniert offene Rechnungen - "Rate bezahlen" führt auf Stripes Rechnungsseite - Migration: Booking.stripeScheduleId, Installment.stripeInvoiceId Zeitplan-Form mit Stripe-Testuhr gegen die Test-API geprüft. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
bcf0f5c937
commit
c1ad9ad2ca
18 files changed
+800
-555
No files matched your search
@@ -4,9 +4,8 @@
|
||||
*/
|
||||
import { z } from "zod";
|
||||
import { checkSameOrigin, fail, ok, rateLimited, readJson } from "@/lib/api";
|
||||
import { abandonCheckout, startInstallmentCheckout } from "@/lib/checkout";
|
||||
import { abandonCheckout, installmentPaymentUrl } from "@/lib/checkout";
|
||||
import { getDb } from "@/lib/db";
|
||||
import { requireAppSecret } from "@/lib/env";
|
||||
import { getGateway } from "@/lib/payment-gateway";
|
||||
import { RATE_LIMITS } from "@/lib/rate-limit";
|
||||
import { isWellFormedStatusToken, sha256Hex } from "@/lib/tokens";
|
||||
@@ -37,7 +36,7 @@ export async function POST(request: Request) {
|
||||
|
||||
switch (body.data.action) {
|
||||
case "pay_installment": {
|
||||
const result = await startInstallmentCheckout(db, gateway, { bookingId: booking.id, installmentId: body.data.installmentId }, { now, secret: requireAppSecret() });
|
||||
const result = await installmentPaymentUrl(db, gateway, { bookingId: booking.id, installmentId: body.data.installmentId });
|
||||
if (result.ok) return ok({ url: result.url });
|
||||
return result.error === "NOT_PAYABLE"
|
||||
? fail(409, { code: "NOT_PAYABLE", message: "Diese Rate kann gerade nicht bezahlt werden. Bitte lade die Seite neu." })
|
||||
|
||||
Reference in new issue
Block a user