import { ApiError, compactApiParams, http } from "@/services/api/request"; export type PaymentProvider = { id: "wechat-native" | "alipay-page-pay" | string; pluginId: string; name: string; icon: string; checkoutMode: "qr_code" | "redirect"; enabled: boolean; pluginEnabled: boolean; configured: boolean; closeAfterMinutes: number; }; export type TopupProduct = { id: string; name: string; description?: string; amountFen: number; creditsMicrocredits: number; enabled: boolean; sortOrder: number; createdBy: string; updatedBy: string; createdAt: string; updatedAt: string; }; export type PaymentOrderStatus = "created" | "pending" | "closing" | "closed" | "credited" | "create_failed"; export type PaymentOrder = { id: string; userId?: string; merchantOrderNo: string; productId: string; productName: string; providerId: string; amountFen: number; currency: "CNY" | string; creditsMicrocredits: number; status: PaymentOrderStatus; providerStatus?: string; providerTradeNo?: string; checkout: { mode: "qr_code" | "redirect" | ""; value?: string; url?: string; expiresAt?: string; }; expiresAt: string; providerPaidAt?: string; creditedAt?: string; closedAt?: string; createdAt: string; updatedAt: string; }; export type PaymentConfigField = { name: string; type: "string" | "password" | "textarea" | "url" | string; label?: string; required?: boolean; secret?: boolean; default?: unknown; description?: string; }; export type AdminPaymentProvider = PaymentProvider & { configId?: string; configEnabled: boolean; version: number; values: Record; secretConfigured: Record; configFields: PaymentConfigField[]; updatedAt?: string; }; export function listPaymentProviders() { return http.get<{ providers: PaymentProvider[] }>("/payments/providers"); } export function listTopupProducts() { return http.get<{ products: TopupProduct[] }>("/payments/products"); } export function createPaymentOrder(input: { productId: string; providerId: string; idempotencyKey: string }) { return http.post<{ order: PaymentOrder }>("/payments/orders", input); } export function getPaymentOrder(id: string) { return http.get<{ order: PaymentOrder }>(`/payments/orders/${encodeURIComponent(id)}`); } export function queryPaymentOrder(id: string) { return http.post<{ order: PaymentOrder }>(`/payments/orders/${encodeURIComponent(id)}/query`); } export function closePaymentOrder(id: string) { return http.post<{ order: PaymentOrder }>(`/payments/orders/${encodeURIComponent(id)}/close`); } export function refreshPaymentCheckout(id: string) { return http.post<{ order: PaymentOrder }>(`/payments/orders/${encodeURIComponent(id)}/checkout/refresh`); } export function listAdminPaymentProviders() { return http.get<{ providers: AdminPaymentProvider[] }>("/admin/payments/providers"); } export function updateAdminPaymentProvider(id: string, input: { enabled: boolean; closeAfterMinutes: number; values: Record }) { return http.put<{ provider: AdminPaymentProvider }>(`/admin/payments/providers/${encodeURIComponent(id)}/config`, input); } export function listAdminTopupProducts() { return http.get<{ products: TopupProduct[] }>("/admin/payments/products"); } export type TopupProductInput = Pick & { description?: string }; export function createAdminTopupProduct(input: TopupProductInput) { return http.post<{ product: TopupProduct }>("/admin/payments/products", input); } export function updateAdminTopupProduct(id: string, input: TopupProductInput) { return http.put<{ product: TopupProduct }>(`/admin/payments/products/${encodeURIComponent(id)}`, input); } export type AdminPaymentOrder = PaymentOrder & { user: { id: string; username: string; displayName: string; email: string } | null; }; export type PaymentOrderFilters = { status?: string; keyword?: string; providerId?: string; timeField?: "created" | "paid" | "credited"; from?: string; to?: string }; export type PaymentReconciliationFilters = { providerId?: string; status?: string; from?: string; to?: string }; export function listAdminPaymentOrders(params: PaymentOrderFilters & { page?: number; pageSize?: number } = {}, signal?: AbortSignal) { return http.get<{ orders: AdminPaymentOrder[]; total: number; page: number; pageSize: number }>("/admin/payments/orders", { params: compactApiParams(params), signal }); } export function queryAdminPaymentOrder(id: string) { return http.post<{ order: PaymentOrder }>(`/admin/payments/orders/${encodeURIComponent(id)}/query`); } export function closeAdminPaymentOrder(id: string) { return http.post<{ order: PaymentOrder }>(`/admin/payments/orders/${encodeURIComponent(id)}/close`); } export type PaymentReconciliationStatus = "running" | "completed" | "failed"; export type PaymentReconciliationResult = "matched" | "recovered" | "local_order_not_found" | "provider_record_missing" | "amount_mismatch" | "trade_no_mismatch" | "credit_failed"; export type PaymentReconciliationRun = { id: string; providerId: string; configId: string; billDate: string; status: PaymentReconciliationStatus; totalItems: number; matchItems: number; recoveredItems: number; errorItems: number; error?: string; startedBy?: string; startedAt: string; completedAt?: string; createdAt: string; updatedAt: string; }; export type PaymentReconciliationItem = { id: string; runId: string; providerId: string; paymentOrderId?: string; merchantOrderNo: string; providerTradeNo?: string; amountFen: number; currency: string; result: PaymentReconciliationResult; resolved: boolean; detail?: string; createdAt: string; }; export function runAdminPaymentReconciliation(input: { providerId: string; billDate: string }) { return http.post<{ run: PaymentReconciliationRun }>("/admin/payments/reconciliations", input); } export function listAdminPaymentReconciliations(params: PaymentReconciliationFilters & { page?: number; pageSize?: number } = {}, signal?: AbortSignal) { return http.get<{ runs: PaymentReconciliationRun[]; total: number; page: number; pageSize: number }>("/admin/payments/reconciliations", { params: compactApiParams(params), signal }); } export function listAdminPaymentReconciliationItems(id: string, params: { result?: string; page?: number; pageSize?: number } = {}, signal?: AbortSignal) { return http.get<{ run: PaymentReconciliationRun; items: PaymentReconciliationItem[]; total: number; page: number; pageSize: number }>(`/admin/payments/reconciliations/${encodeURIComponent(id)}/items`, { params: compactApiParams(params), signal }); } export function exportAdminPaymentOrders(params: PaymentOrderFilters) { return downloadPaymentCSV("/admin/payments/orders/export.csv", params); } export function exportAdminPaymentReconciliations(params: PaymentReconciliationFilters) { return downloadPaymentCSV("/admin/payments/reconciliations/export.csv", params); } export function exportAdminPaymentReconciliationItems(id: string, result?: string) { return downloadPaymentCSV(`/admin/payments/reconciliations/${encodeURIComponent(id)}/items/export.csv`, { result }); } async function downloadPaymentCSV(url: string, params: Record) { try { const response = await http.raw({ method: "get", url, params: compactApiParams(params), responseType: "blob" }); return response.data; } catch (error) { // Axios also wraps JSON error envelopes in a Blob for download requests. const blob = error instanceof ApiError ? (error.cause as { response?: { data?: unknown } } | undefined)?.response?.data : undefined; if (blob instanceof Blob) { let envelope: { msg?: string; code?: number; reason?: string } | undefined; try { envelope = JSON.parse(await blob.text()); } catch { /* Keep the original transport error. */ } if (envelope?.msg && error instanceof ApiError) { throw new ApiError(envelope.msg, { status: error.status, code: envelope.code, reason: envelope.reason, cause: error }); } } throw error; } }