增加支付逻辑

This commit is contained in:
andy committed 2026-10-12 13:57:23 +08:00
1 parent 4ac786aafb
commit dfb2c91b8d
77 files changed
+6370 -45

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
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;
}