diff --git a/src/lib/config.ts b/src/lib/config.ts
index c9316ef..a0e4be4 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -37,11 +37,11 @@ const envSchema = z.object({
// 每日每用户最大累计充值额,0 = 不限制
MAX_DAILY_RECHARGE_AMOUNT: z.string().default('10000').transform(Number).pipe(z.number().min(0)),
- // 每日各渠道全平台总限额,0 = 不限制
- // 新增渠道按 MAX_DAILY_AMOUNT_{TYPE大写} 命名即可自动生效
- MAX_DAILY_AMOUNT_ALIPAY: z.string().default('10000').transform(Number).pipe(z.number().min(0)),
- MAX_DAILY_AMOUNT_WXPAY: z.string().default('10000').transform(Number).pipe(z.number().min(0)),
- MAX_DAILY_AMOUNT_STRIPE: z.string().default('0').transform(Number).pipe(z.number().min(0)),
+ // 每日各渠道全平台总限额,可选覆盖(0 = 不限制)。
+ // 未设置时由各 PaymentProvider.defaultLimits 提供默认值。
+ MAX_DAILY_AMOUNT_ALIPAY: z.string().optional().transform((v) => (v !== undefined ? Number(v) : undefined)).pipe(z.number().min(0).optional()),
+ MAX_DAILY_AMOUNT_WXPAY: z.string().optional().transform((v) => (v !== undefined ? Number(v) : undefined)).pipe(z.number().min(0).optional()),
+ MAX_DAILY_AMOUNT_STRIPE: z.string().optional().transform((v) => (v !== undefined ? Number(v) : undefined)).pipe(z.number().min(0).optional()),
PRODUCT_NAME: z.string().default('Sub2API Balance Recharge'),
ADMIN_TOKEN: z.string().min(1),
diff --git a/src/lib/easy-pay/provider.ts b/src/lib/easy-pay/provider.ts
index f849cd6..fcab030 100644
--- a/src/lib/easy-pay/provider.ts
+++ b/src/lib/easy-pay/provider.ts
@@ -15,6 +15,10 @@ import { getEnv } from '@/lib/config';
export class EasyPayProvider implements PaymentProvider {
readonly name = 'easy-pay';
readonly supportedTypes: PaymentType[] = ['alipay', 'wxpay'];
+ readonly defaultLimits = {
+ alipay: { singleMax: 1000, dailyMax: 10000 },
+ wxpay: { singleMax: 1000, dailyMax: 10000 },
+ };
async createPayment(request: CreatePaymentRequest): Promise
{
const result = await createPayment({
diff --git a/src/lib/order/limits.ts b/src/lib/order/limits.ts
index afab946..fa117e5 100644
--- a/src/lib/order/limits.ts
+++ b/src/lib/order/limits.ts
@@ -1,17 +1,23 @@
import { prisma } from '@/lib/db';
import { getEnv } from '@/lib/config';
+import { initPaymentProviders, paymentRegistry } from '@/lib/payment';
/**
* 获取指定支付渠道的每日全平台限额(0 = 不限制)。
- * 优先读 config(Zod 验证),兜底读 process.env,适配未来动态注册的新渠道。
+ * 优先级:环境变量显式配置 > 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;
+ if (typeof val === 'number') return val; // 明确配置(含 0)
- // 兜底:支持动态渠道(未在 schema 中声明的 MAX_DAILY_AMOUNT_* 变量)
+ // 尝试从已注册的 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);
@@ -20,15 +26,35 @@ export function getMethodDailyLimit(paymentType: string): number {
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 = 不限 */
+ /** 剩余每日额度,null = 不限 */
remaining: number | null;
/** 是否还可使用(false = 今日额度已满) */
available: boolean;
+ /** 单笔限额,0 = 使用全局配置 MAX_RECHARGE_AMOUNT */
+ singleMax: number;
}
/**
@@ -58,6 +84,7 @@ export async function queryMethodLimits(
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] = {
@@ -65,6 +92,7 @@ export async function queryMethodLimits(
used,
remaining,
available: dailyLimit === 0 || used < dailyLimit,
+ singleMax,
};
}
return result;
diff --git a/src/lib/payment/registry.ts b/src/lib/payment/registry.ts
index 3e769c7..c805543 100644
--- a/src/lib/payment/registry.ts
+++ b/src/lib/payment/registry.ts
@@ -1,4 +1,4 @@
-import type { PaymentProvider, PaymentType } from './types';
+import type { PaymentProvider, PaymentType, MethodDefaultLimits } from './types';
export class PaymentProviderRegistry {
private providers = new Map();
@@ -24,6 +24,12 @@ export class PaymentProviderRegistry {
getSupportedTypes(): PaymentType[] {
return Array.from(this.providers.keys());
}
+
+ /** 获取指定渠道的提供商默认限额(未注册时返回 undefined) */
+ getDefaultLimit(type: string): MethodDefaultLimits | undefined {
+ const provider = this.providers.get(type as PaymentType);
+ return provider?.defaultLimits?.[type];
+ }
}
export const paymentRegistry = new PaymentProviderRegistry();
diff --git a/src/lib/payment/types.ts b/src/lib/payment/types.ts
index 814a4b1..813080a 100644
--- a/src/lib/payment/types.ts
+++ b/src/lib/payment/types.ts
@@ -51,10 +51,20 @@ export interface RefundResponse {
status: 'success' | 'pending' | 'failed';
}
+/** Per-method default limits declared by the provider */
+export interface MethodDefaultLimits {
+ /** 单笔最大金额,0 = 不限(使用全局 MAX_RECHARGE_AMOUNT) */
+ singleMax?: number;
+ /** 每日全平台最大金额,0 = 不限 */
+ dailyMax?: number;
+}
+
/** Common interface that all payment providers must implement */
export interface PaymentProvider {
readonly name: string;
readonly supportedTypes: PaymentType[];
+ /** 各渠道默认限额,key 为 PaymentType(如 'alipay'),可被环境变量覆盖 */
+ readonly defaultLimits?: Record;
createPayment(request: CreatePaymentRequest): Promise;
queryOrder(tradeNo: string): Promise;
diff --git a/src/lib/stripe/provider.ts b/src/lib/stripe/provider.ts
index 6286121..3f8c28c 100644
--- a/src/lib/stripe/provider.ts
+++ b/src/lib/stripe/provider.ts
@@ -15,6 +15,9 @@ import type {
export class StripeProvider implements PaymentProvider {
readonly name = 'stripe';
readonly supportedTypes: PaymentType[] = ['stripe'];
+ readonly defaultLimits = {
+ stripe: { singleMax: 0, dailyMax: 0 }, // 0 = unlimited
+ };
private client: Stripe | null = null;