Files
EpicNext-Cms/src/lib/services/paypal-topup.ts
T
openhands df38dccbf1
Local Build and Deploy / deploy (push) Failing after 46s
style: format code biome
2026-07-13 21:57:41 +02:00

92 lines
2.0 KiB
TypeScript

export interface PendingTopup {
userId: number;
status: string | null;
amount?: number;
}
export type AuthorizedTopup = PendingTopup & {
action: "CAPTURE" | "DELIVER_CREDITS";
};
export type FindTopupOrder = (orderId: string) => Promise<PendingTopup | null>;
export interface CreatedTopupRecord {
userId: number;
transactionId: string;
status: "CREATED";
description: string;
amount: number;
currency: string;
createdAt: Date;
updatedAt: Date;
}
export type CreateTopupRecord = (data: CreatedTopupRecord) => Promise<unknown>;
export class TopupCaptureError extends Error {
constructor(
public readonly code: "ORDER_NOT_FOUND" | "ORDER_ALREADY_PROCESSED",
) {
super(code);
}
}
export class TopupDeliveryError extends Error {
readonly code = "DELIVERY_ALREADY_CLAIMED";
constructor() {
super("DELIVERY_ALREADY_CLAIMED");
}
}
export async function claimTopupDelivery(
claim: () => Promise<{ count: number }>,
): Promise<void> {
const result = await claim();
if (result.count !== 1) throw new TopupDeliveryError();
}
export async function authorizeTopupCapture(
userId: number,
orderId: string,
findOrder: FindTopupOrder,
): Promise<AuthorizedTopup> {
const order = await findOrder(orderId);
if (!order || order.userId !== userId) {
throw new TopupCaptureError("ORDER_NOT_FOUND");
}
if (
order.status === "CAPTURED_PENDING_CREDIT" &&
Number.isFinite(order.amount)
) {
return { ...order, action: "DELIVER_CREDITS" };
}
if (order.status !== "CREATED") {
throw new TopupCaptureError("ORDER_ALREADY_PROCESSED");
}
return { ...order, action: "CAPTURE" };
}
export async function recordCreatedTopup(
topup: {
userId: number;
orderId: string;
amount: number;
currency: string;
credits: number;
},
createRecord: CreateTopupRecord,
): Promise<void> {
const now = new Date();
await createRecord({
userId: topup.userId,
transactionId: topup.orderId,
status: "CREATED",
description: `Top-up: ${topup.credits} credits`,
amount: topup.amount,
currency: topup.currency,
createdAt: now,
updatedAt: now,
});
}