增加支付逻辑
This commit is contained in:
1 parent
4ac786aafb
commit
dfb2c91b8d
77 files changed
+6370
-45
No files matched your search
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user