import { getPaymentService } from "./core.ts"; import { ensureOwnedPayment, getPaymentApiConfig, paymentApiAllowed, paymentApiSubject, publicPayment, type PaymentApiContext, } from "./http.ts"; const json = (value: unknown, status = 200) => Response.json(value, { status }); const bodyOf = async (req: Request) => (await req.json().catch(() => ({}))) as Record; const idOf = (ctx: PaymentApiContext) => ctx.params?.id?.trim() ?? ""; const resultResponse = (result: any, success = 200) => result.ok ? json(result.value, success) : json({ error: result.reason, fault: result.fault }, 422); export async function GET(ctx: PaymentApiContext) { const service = getPaymentService(); const path = new URL(ctx.req.url).pathname; if (path.endsWith("/providers")) { if (getPaymentApiConfig().enabled === false) return json({ error: "payment API disabled" }, 404); return json({ providers: service.providers() }); } if (path.endsWith("/methods")) { if (!(await paymentApiAllowed(ctx, "methods"))) return json({ error: "payment access denied" }, 403); return json({ methods: await service.paymentMethods(await paymentApiSubject(ctx)) }); } if (idOf(ctx)) { const owned = await ensureOwnedPayment(service, ctx, idOf(ctx), "read"); if (owned.response) return owned.response; return json(publicPayment(owned.payment!, await service.checkPayment(owned.payment!.id))); } if (!(await paymentApiAllowed(ctx, "list"))) return json({ error: "payment access denied" }, 403); const subject = await paymentApiSubject(ctx); const rows = await service.listPayments({ subject }); return json({ payments: await Promise.all( rows.map(async (payment) => publicPayment(payment, await service.checkPayment(payment.id))), ), }); } export async function POST(ctx: PaymentApiContext) { const service = getPaymentService(); const path = new URL(ctx.req.url).pathname; const body = await bodyOf(ctx.req); if (path.endsWith("/intents")) { if (!(await paymentApiAllowed(ctx, "initialize"))) return json({ error: "payment access denied" }, 403); const subject = await paymentApiSubject(ctx); const resolver = getPaymentApiConfig().initialize; const input = resolver ? await resolver(ctx, body, subject) : { amount: Number(body.amount), currency: String(body.currency ?? "").toUpperCase(), gateway: typeof body.gateway === "string" ? body.gateway : undefined, country: typeof body.country === "string" ? body.country : undefined, capture: body.capture === "manual" ? ("manual" as const) : ("automatic" as const), idempotencyKey: String( body.idempotencyKey ?? ctx.req.headers.get("idempotency-key") ?? "", ), returnUrl: typeof body.returnUrl === "string" ? body.returnUrl : undefined, metadata: body.metadata && typeof body.metadata === "object" ? (body.metadata as Record) : undefined, }; const result = await service.initializePayment({ ...input, subject }); if (!result.ok) return resultResponse(result, 201); return json(publicPayment(result.value, await service.checkPayment(result.value.id)), 201); } if (path.endsWith("/reconcile")) { if (!(await paymentApiAllowed(ctx, "reconcile"))) return json({ error: "payment access denied" }, 403); return json({ discrepancies: await service.reconcilePayments() }); } const id = idOf(ctx); if (!id) return json({ error: "payment id is required" }, 400); const action = path.endsWith("/sync") ? "sync" : path.endsWith("/capture") ? "capture" : path.endsWith("/cancel") ? "cancel" : path.endsWith("/refund") ? "refund" : undefined; if (!action) return json({ error: "payment operation not found" }, 404); const owned = await ensureOwnedPayment(service, ctx, id, action); if (owned.response) return owned.response; if (action === "sync") return resultResponse(await service.syncPayment(id)); if (action === "capture") return resultResponse( await service.capturePayment( id, body.amount === undefined ? undefined : { amount: Number(body.amount), currency: owned.payment!.currency }, ), ); if (action === "cancel") return resultResponse(await service.cancelPayment(id, String(body.reason ?? ""))); return resultResponse( await service.refundPayment({ id, amount: body.amount === undefined ? undefined : Number(body.amount), reason: String(body.reason ?? ""), idempotencyKey: String(body.idempotencyKey ?? ctx.req.headers.get("idempotency-key") ?? ""), }), ); }