feat(payment): expose secure APIs and themed pages

This commit is contained in:
Clintchiz committed 2026-08-28 09:59:07 +05:30
1 parent dd8d6ecc3b
commit 2d77f5457c
15 files changed
+654 -13

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
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<string, unknown>;
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<string, string>)
: 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") ?? ""),
}),
);
}