import { prisma } from '@/lib/db'; import { getEnv } from '@/lib/config'; import { initPaymentProviders, paymentRegistry } from '@/lib/payment'; /** * 获取指定支付渠道的每日全平台限额(0 = 不限制)。 * 优先级:环境变量显式配置 > provider 默认值 > process.env 兜底 > 0 */ export function getMethodDailyLimit(paymentType: string): number { const env = getEnv(); const key = `MAX_DAILY_AMOUNT_${paymentType.toUpperCase()}` as keyof typeof env; const val = env[key]; if (typeof val === 'number') return val; // 明确配置(含 0) // 尝试从已注册的 provider 取默认值 initPaymentProviders(); const providerDefault = paymentRegistry.getDefaultLimit(paymentType); if (providerDefault?.dailyMax !== undefined) return providerDefault.dailyMax; // 兜底:process.env(支持未在 schema 中声明的动态渠道) const raw = process.env[`MAX_DAILY_AMOUNT_${paymentType.toUpperCase()}`]; if (raw !== undefined) { const num = Number(raw); return Number.isFinite(num) && num >= 0 ? num : 0; } return 0; // 默认不限制 } /** * 获取指定支付渠道的单笔限额(0 = 使用全局 MAX_RECHARGE_AMOUNT)。 * 优先级:process.env MAX_SINGLE_AMOUNT_* > provider 默认值 > 0 */ export function getMethodSingleLimit(paymentType: string): number { const raw = process.env[`MAX_SINGLE_AMOUNT_${paymentType.toUpperCase()}`]; if (raw !== undefined) { const num = Number(raw); if (Number.isFinite(num) && num >= 0) return num; } initPaymentProviders(); const providerDefault = paymentRegistry.getDefaultLimit(paymentType); if (providerDefault?.singleMax !== undefined) return providerDefault.singleMax; return 0; // 使用全局 MAX_RECHARGE_AMOUNT } export interface MethodLimitStatus { /** 每日限额,0 = 不限 */ dailyLimit: number; /** 今日已使用金额 */ used: number; /** 剩余每日额度,null = 不限 */ remaining: number | null; /** 是否还可使用(false = 今日额度已满) */ available: boolean; /** 单笔限额,0 = 使用全局配置 MAX_RECHARGE_AMOUNT */ singleMax: number; } /** * 批量查询多个支付渠道的今日使用情况。 * 一次 DB groupBy 完成,调用方按需传入渠道列表。 */ export async function queryMethodLimits( paymentTypes: string[], ): Promise> { const todayStart = new Date(); todayStart.setUTCHours(0, 0, 0, 0); const usageRows = await prisma.order.groupBy({ by: ['paymentType'], where: { paymentType: { in: paymentTypes }, status: { in: ['PAID', 'RECHARGING', 'COMPLETED'] }, paidAt: { gte: todayStart }, }, _sum: { amount: true }, }); const usageMap = Object.fromEntries( usageRows.map((r) => [r.paymentType, Number(r._sum.amount ?? 0)]), ); const result: Record = {}; for (const type of paymentTypes) { const dailyLimit = getMethodDailyLimit(type); const singleMax = getMethodSingleLimit(type); const used = usageMap[type] ?? 0; const remaining = dailyLimit > 0 ? Math.max(0, dailyLimit - used) : null; result[type] = { dailyLimit, used, remaining, available: dailyLimit === 0 || used < dailyLimit, singleMax, }; } return result; }