Files
vans-diy-bastelbedarf/server/lib/paypal.js
T

70 lines
2.9 KiB
JavaScript

/* =====================================================================
lib/paypal.js — echte, server-seitige PayPal-Anbindung (REST API v2). 1:1 portiert aus
functions/_shared/paypal.js (reines fetch()-basiertes Modul, keine Cloudflare-Spezifika,
deshalb unverändert übernehmbar). Zugangsdaten kommen aus process.env statt env.*.
===================================================================== */
function paypalBasisUrl() {
return process.env.PAYPAL_ENV === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com";
}
export function paypalKonfiguriert() {
return !!(process.env.PAYPAL_CLIENT_ID && process.env.PAYPAL_CLIENT_SECRET);
}
async function holeZugangstoken() {
const res = await fetch(`${paypalBasisUrl()}/v1/oauth2/token`, {
method: "POST",
headers: {
Authorization: `Basic ${Buffer.from(`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials",
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`PayPal-Zugangstoken konnte nicht geholt werden (Status ${res.status}): ${text}`);
}
const data = await res.json();
return data.access_token;
}
export async function erstellePaypalBestellung(betragEuro) {
const token = await holeZugangstoken();
const res = await fetch(`${paypalBasisUrl()}/v2/checkout/orders`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({
intent: "CAPTURE",
purchase_units: [{ amount: { currency_code: "EUR", value: Math.max(0.01, betragEuro).toFixed(2) } }],
}),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`PayPal-Bestellung konnte nicht angelegt werden (Status ${res.status}): ${text}`);
}
const data = await res.json();
return data.id;
}
export async function erfassePaypalZahlung(paypalOrderId) {
try {
const token = await holeZugangstoken();
const res = await fetch(`${paypalBasisUrl()}/v2/checkout/orders/${encodeURIComponent(paypalOrderId)}/capture`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
});
const data = await res.json().catch(() => null);
if (!res.ok || !data) {
return { ok: false, error: `PayPal-Zahlung konnte nicht erfasst werden (Status ${res.status}).` };
}
const capture = data?.purchase_units?.[0]?.payments?.captures?.[0];
if (data.status !== "COMPLETED" || !capture || capture.status !== "COMPLETED") {
return { ok: false, error: `PayPal-Zahlung ist nicht abgeschlossen (Status: ${data.status}).` };
}
return { ok: true, captureId: capture.id, betragEuro: Number(capture.amount?.value ?? 0) };
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : String(err) };
}
}