Files
property-management-network/lib/paypal/client.ts
T

58 lines
2.1 KiB
TypeScript
Raw Normal View History

// 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<string> {
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<Response> {
const token = await getAccessToken()
return fetch(`${BASE_URL}${path}`, {
...init,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
})
}