/* ===================================================================== POST /api/paypal/capture-order — zieht eine zuvor angelegte PayPal-Zahlung EIN und legt die Bestellung erst danach an. Das ist der entscheidende Sicherheits-Schritt der ganzen PayPal-Anbindung: Der Browser meldet hier "die Kundin hat bei PayPal auf Bezahlen geklickt" (`onApprove`-Callback), aber wir glauben das NICHT einfach — stattdessen fragen wir direkt bei PayPal selbst nach (server-zu- server, mit VanVans eigenen App-Zugangsdaten), ob die Zahlung wirklich abgeschlossen ist UND ob der eingezogene Betrag zur Bestellsumme passt. Nur wenn beides stimmt, wird die Bestellung in der Datenbank angelegt — mit Status "bezahlt" und der echten PayPal-Bestell-ID als Beleg. Schlägt die Zahlungsprüfung fehl, wird NICHTS gespeichert — die Kundin sieht eine klare Fehlermeldung im Checkout und kann es erneut versuchen, statt dass VanVan eine "Bestellung" ohne echtes Geld dahinter bekommt. ===================================================================== */ import { json } from "../../_shared/http.js"; import { paypalKonfiguriert, erfassePaypalZahlung } from "../../_shared/paypal.js"; import { erstelleBestellung } from "../../_shared/bestellung-erstellen.js"; // Kleine Toleranz für Rundungsdifferenzen (Cent-Rundung zwischen unserer Berechnung und PayPals // eigener Darstellung) — alles darüber gilt als verdächtige Abweichung und wird abgelehnt. const BETRAG_TOLERANZ_EURO = 0.02; export async function onRequestPost(context) { const { request, env } = context; if (!paypalKonfiguriert(env)) { return json(503, { ok: false, error: "PayPal ist auf dieser Seite noch nicht eingerichtet." }); } let body; try { body = await request.json(); } catch { return json(400, { ok: false, error: "Ungültige Anfrage." }); } const paypalOrderId = typeof body.paypalOrderId === "string" ? body.paypalOrderId.trim() : ""; if (!paypalOrderId) { return json(400, { ok: false, error: "Fehlende PayPal-Bestell-ID." }); } const summe = Number(body.summe); if (!Number.isFinite(summe) || summe <= 0) { return json(400, { ok: false, error: "Ungültige Bestellsumme." }); } // ── Schritt 1: Zahlung server-seitig bei PayPal einziehen + verifizieren. ── const zahlung = await erfassePaypalZahlung(env, paypalOrderId); if (!zahlung.ok) { return json(402, { ok: false, error: `Zahlung konnte nicht bestätigt werden: ${zahlung.error}` }); } // ── Schritt 2: Betrag gegenprüfen — verhindert, dass hier ein anderer (z.B. kleinerer) Betrag // als der tatsächlich fällige "durchgeschmuggelt" wird. ── if (Math.abs(zahlung.betragEuro - summe) > BETRAG_TOLERANZ_EURO) { return json(402, { ok: false, error: `Der bei PayPal gezahlte Betrag (${zahlung.betragEuro.toFixed(2)} €) stimmt nicht mit der Bestellsumme (${summe.toFixed(2)} €) überein.`, }); } // ── Schritt 3: Erst JETZT, mit bestätigter echter Zahlung, die Bestellung anlegen. ── const ergebnis = await erstelleBestellung(env, { ...body, zahlungsart: "paypal" }, { status: "bezahlt", paypalOrderId, }); if (!ergebnis.ok) { // Die Zahlung ist zu diesem Zeitpunkt bereits bei PayPal eingezogen, konnte aber nicht // gespeichert werden (z.B. D1 kurzzeitig nicht erreichbar) — das ist ein seltener, aber // ernster Fall: VanVan hat das Geld, aber keine Bestellung in der Verwaltung. Deshalb bekommt // die Kundin eine Meldung, die sie NICHT zum erneuten Bezahlen auffordert, sondern zur // Kontaktaufnahme, um Doppelzahlungen zu vermeiden. return json(500, { ok: false, error: `Deine Zahlung wurde erfolgreich bei PayPal abgebucht (Beleg-ID: ${zahlung.captureId}), aber die Bestellung konnte nicht gespeichert werden. Bitte kontaktiere uns mit dieser Beleg-ID, damit wir das von Hand nachtragen — bitte NICHT erneut bezahlen.`, }); } return json(201, { ok: true, id: ergebnis.id, bestellnummer: ergebnis.bestellnummer }); }