Node.js/Express-Server als Ersatz für Cloudflare Pages Functions + D1 (läuft auf eigenem Server statt Cloudflare)
Build & Deploy / deploy (push) Waiting to run
Build & Deploy / deploy (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/* =====================================================================
|
||||
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) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user