70 lines
2.9 KiB
JavaScript
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) };
|
|
}
|
|
}
|