This commit is contained in:
commit
59bb5a38a3
132 files changed
+19435
No files matched your search
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Aktionen der Kundin auf ihrer Statusseite. Zugang nur ueber das
|
||||
* Statustoken aus Bestaetigungsmail bzw. Stripe-Ruecksprung.
|
||||
*/
|
||||
import { z } from "zod";
|
||||
import { checkSameOrigin, fail, ok, rateLimited, readJson } from "@/lib/api";
|
||||
import { abandonCheckout, startInstallmentCheckout } 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";
|
||||
|
||||
const schema = z.discriminatedUnion("action", [
|
||||
z.object({ action: z.literal("pay_installment"), token: z.string(), installmentId: z.string().min(1).max(64) }),
|
||||
z.object({ action: z.literal("resume_checkout"), token: z.string() }),
|
||||
z.object({ action: z.literal("abandon"), token: z.string() }),
|
||||
]);
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const blocked = checkSameOrigin(request);
|
||||
if (blocked) return blocked;
|
||||
const limited = await rateLimited(request, RATE_LIMITS.statusAction);
|
||||
if (limited) return limited;
|
||||
const body = await readJson(request, schema);
|
||||
if (!body.ok) return body.response;
|
||||
|
||||
const notFound = fail(404, { code: "NOT_FOUND", message: "Diese Buchung wurde nicht gefunden." });
|
||||
if (!isWellFormedStatusToken(body.data.token)) return notFound;
|
||||
const db = getDb();
|
||||
const booking = await db.booking.findUnique({ where: { statusTokenHash: sha256Hex(body.data.token) } });
|
||||
if (!booking) return notFound;
|
||||
|
||||
const gateway = getGateway();
|
||||
if (booking.livemode !== (gateway.mode === "live")) return notFound;
|
||||
const now = new Date();
|
||||
|
||||
switch (body.data.action) {
|
||||
case "pay_installment": {
|
||||
const result = await startInstallmentCheckout(db, gateway, { bookingId: booking.id, installmentId: body.data.installmentId }, { now, secret: requireAppSecret() });
|
||||
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." })
|
||||
: fail(502, { code: "PAYMENT_PROVIDER", message: "Die Zahlungsseite ist gerade nicht erreichbar. Bitte versuche es später erneut." });
|
||||
}
|
||||
case "resume_checkout": {
|
||||
if (booking.status === "PENDING" && booking.paymentStatus === "UNPAID" && booking.stripeCheckoutUrl && booking.priceLockedUntil > now) {
|
||||
return ok({ url: booking.stripeCheckoutUrl });
|
||||
}
|
||||
return fail(409, { code: "EXPIRED", message: "Die Reservierung ist abgelaufen. Bitte starte eine neue Buchung." });
|
||||
}
|
||||
case "abandon": {
|
||||
await abandonCheckout(db, gateway, booking, { now });
|
||||
return ok({ abandoned: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user