118 lines
4.7 KiB
TypeScript
118 lines
4.7 KiB
TypeScript
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") ?? ""),
|
|
}),
|
|
);
|
|
}
|