// PayPal REST API client — OAuth2 client-credentials + a thin fetch helper. // // Enabled only when PAYPAL_CLIENT_ID and PAYPAL_SECRET are set (mirrors the // gating used for the other optional integrations). PAYPAL_ENVIRONMENT selects // the sandbox (default) or live host. const ENVIRONMENT = process.env.PAYPAL_ENVIRONMENT === "live" ? "live" : "sandbox" const BASE_URL = ENVIRONMENT === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com" export function paypalConfigured(): boolean { return Boolean(process.env.PAYPAL_CLIENT_ID && process.env.PAYPAL_SECRET) } export function paypalEnvironment() { return ENVIRONMENT } // Access tokens live ~9h; cache in-process (the app runs a persistent Node // server, so this survives across requests) and refresh a minute early. let cachedToken: { token: string; expiresAt: number } | null = null async function getAccessToken(): Promise { if (cachedToken && cachedToken.expiresAt > Date.now() + 60_000) return cachedToken.token const id = process.env.PAYPAL_CLIENT_ID const secret = process.env.PAYPAL_SECRET if (!id || !secret) throw new Error("PayPal is not configured") const res = await fetch(`${BASE_URL}/v1/oauth2/token`, { method: "POST", headers: { Authorization: `Basic ${Buffer.from(`${id}:${secret}`).toString("base64")}`, "Content-Type": "application/x-www-form-urlencoded", }, body: "grant_type=client_credentials", }) if (!res.ok) throw new Error(`PayPal auth failed: ${res.status} ${await res.text().catch(() => "")}`) const json = (await res.json()) as { access_token: string; expires_in: number } cachedToken = { token: json.access_token, expiresAt: Date.now() + json.expires_in * 1000 } return cachedToken.token } /** Authenticated fetch against the PayPal REST API. Path is relative (e.g. "/v1/..."). */ export async function paypalFetch(path: string, init: RequestInit = {}): Promise { const token = await getAccessToken() return fetch(`${BASE_URL}${path}`, { ...init, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", ...(init.headers ?? {}), }, }) }