Files
NianAIGC/lib/client/payment-api.ts
T
2026-10-12 13:57:23 +08:00

125 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export type PaymentOrder = {
id: string;
organizationId: string;
accountId: string;
amountFen: number;
currency: string;
status: "pending" | "paid" | "closed";
codeUrl?: string;
transactionId?: string;
createdAt: string;
updatedAt: string;
expiresAt: string;
paidAt?: string;
};
export type AdminPaymentOrder = PaymentOrder & {
organizationName: string;
accountName: string;
refundedFen: number;
refundingFen: number;
refundableFen: number;
};
export type PaymentRefund = {
id: string;
orderId: string;
organizationId: string;
operatorId: string;
operatorName?: string;
transactionId: string;
providerId?: string;
amountFen: number;
totalFen: number;
currency: string;
reason: string;
status: "pending" | "processing" | "success" | "closed" | "abnormal" | "rejected";
failureReason?: string;
createdAt: string;
updatedAt: string;
succeededAt?: string;
};
export type AdminPaymentOrdersPage = { orders: AdminPaymentOrder[]; hasMore: boolean; enabled: boolean };
export type PaymentOptions = {
enabled: boolean;
canRecharge: boolean;
presetAmountsFen: number[];
minAmountFen: number;
maxAmountFen: number;
};
// Work with decimal text so every accepted value maps to exactly one amount in fen.
export function parseYuanToFen(value: string, minimum: number, maximum: number): number | null {
const trimmed = value.trim();
if (!/^(?:0|[1-9]\d*)(?:\.\d{1,2})?$/.test(trimmed)) return null;
const [yuan, fraction = ""] = trimmed.split(".");
const amount = Number(yuan) * 100 + Number(fraction.padEnd(2, "0"));
return Number.isSafeInteger(amount) && amount >= minimum && amount <= maximum ? amount : null;
}
export function formatYuan(fen: number): string {
return (fen / 100).toFixed(2);
}
async function paymentRequest<T>(url: string, init?: RequestInit): Promise<T> {
const response = await fetch(url, { cache: "no-store", ...init });
let payload: { error?: string } & Partial<T>;
try {
payload = await response.json();
} catch {
throw new Error(`支付服务返回了无效响应(HTTP ${response.status})`);
}
if (!response.ok) throw new Error(payload.error || `支付请求失败(HTTP ${response.status})`);
return payload as T;
}
export async function getPaymentOptions(signal?: AbortSignal): Promise<PaymentOptions> {
return paymentRequest<PaymentOptions>("/api/payments/options", { signal });
}
export async function listPaymentOrders(signal?: AbortSignal): Promise<PaymentOrder[]> {
const result = await paymentRequest<{ orders: PaymentOrder[] }>("/api/payments/orders", { signal });
return result.orders || [];
}
export async function createPaymentOrder(amountFen: number, requestKey: string): Promise<PaymentOrder> {
const result = await paymentRequest<{ order: PaymentOrder }>("/api/payments/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amountFen, requestKey })
});
return result.order;
}
export async function getPaymentOrder(id: string, signal?: AbortSignal): Promise<PaymentOrder> {
const result = await paymentRequest<{ order: PaymentOrder }>(`/api/payments/orders/${encodeURIComponent(id)}`, { signal });
return result.order;
}
export async function listAdminPaymentOrders(organizationId: string, offset: number, signal?: AbortSignal): Promise<AdminPaymentOrdersPage> {
const query = new URLSearchParams({ limit: "50", offset: String(offset) });
if (organizationId) query.set("organizationId", organizationId);
return paymentRequest<AdminPaymentOrdersPage>(`/api/payments/admin/orders?${query}`, { signal });
}
export async function listPaymentRefunds(orderId: string, signal?: AbortSignal): Promise<PaymentRefund[]> {
const result = await paymentRequest<{ refunds: PaymentRefund[] }>(`/api/payments/admin/orders/${encodeURIComponent(orderId)}/refunds`, { signal });
return result.refunds || [];
}
export async function createPaymentRefund(orderId: string, amountFen: number, reason: string, requestKey: string): Promise<PaymentRefund> {
const result = await paymentRequest<{ refund: PaymentRefund }>(`/api/payments/admin/orders/${encodeURIComponent(orderId)}/refunds`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amountFen, reason, requestKey })
});
return result.refund;
}
export async function getPaymentRefund(id: string, signal?: AbortSignal): Promise<PaymentRefund> {
const result = await paymentRequest<{ refund: PaymentRefund }>(`/api/payments/admin/refunds/${encodeURIComponent(id)}`, { signal });
return result.refund;
}