安全加固: - 系统配置 API 增加写入 key 白名单,防止任意配置注入 - ADMIN_TOKEN 最小长度要求 16 字符 - 补充安全响应头(X-Content-Type-Options, X-Frame-Options, Referrer-Policy) - /api/users/[id] 和 /api/limits 增加 token 鉴权 - console.error 敏感信息脱敏(config route) - 敏感值 mask 修复短值完全隐藏 输入校验: - admin 渠道接口校验 rate_multiplier > 0、sort_order >= 0、name 非空 - admin 订阅套餐接口校验 price > 0、validity_days > 0、sort_order >= 0 金额精度: - feeRate 字段精度从 Decimal(5,2) 提升到 Decimal(5,4) - calculatePayAmount 返回 string 避免 Number 中间转换精度丢失 - 支付宝查询订单增加金额有效性校验(isFinite && > 0) UI 统一: - 订阅管理「售卖」列改为 toggle switch 开关(与渠道管理一致) - 表单中 checkbox 改为 toggle switch - 列名统一为「启用售卖」,支持直接点击切换
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
import { initPaymentProviders, paymentRegistry } from '@/lib/payment';
|
||
import { Prisma } from '@prisma/client';
|
||
|
||
/**
|
||
* 获取指定支付渠道的手续费率(百分比)。
|
||
* 优先级:FEE_RATE_{TYPE} > FEE_RATE_PROVIDER_{KEY} > 0
|
||
*/
|
||
export function getMethodFeeRate(paymentType: string): number {
|
||
// 渠道级别:FEE_RATE_ALIPAY / FEE_RATE_WXPAY / FEE_RATE_STRIPE
|
||
const methodRaw = process.env[`FEE_RATE_${paymentType.toUpperCase()}`];
|
||
if (methodRaw !== undefined && methodRaw !== '') {
|
||
const num = Number(methodRaw);
|
||
if (Number.isFinite(num) && num >= 0) return num;
|
||
}
|
||
|
||
// 提供商级别:FEE_RATE_PROVIDER_EASYPAY / FEE_RATE_PROVIDER_STRIPE
|
||
initPaymentProviders();
|
||
const providerKey = paymentRegistry.getProviderKey(paymentType);
|
||
if (providerKey) {
|
||
const providerRaw = process.env[`FEE_RATE_PROVIDER_${providerKey.toUpperCase()}`];
|
||
if (providerRaw !== undefined && providerRaw !== '') {
|
||
const num = Number(providerRaw);
|
||
if (Number.isFinite(num) && num >= 0) return num;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/** decimal.js ROUND_UP = 0(远离零方向取整) */
|
||
const ROUND_UP = 0;
|
||
|
||
/**
|
||
* 根据到账金额和手续费率计算实付金额(使用 Decimal 精确计算,避免浮点误差)。
|
||
* feeAmount = ceil(rechargeAmount * feeRate / 100, 保留2位小数)
|
||
* payAmount = rechargeAmount + feeAmount
|
||
*/
|
||
export function calculatePayAmount(rechargeAmount: number, feeRate: number): string {
|
||
if (feeRate <= 0) return rechargeAmount.toFixed(2);
|
||
const amount = new Prisma.Decimal(rechargeAmount);
|
||
const rate = new Prisma.Decimal(feeRate.toString());
|
||
const feeAmount = amount.mul(rate).div(100).toDecimalPlaces(2, ROUND_UP);
|
||
return amount.plus(feeAmount).toFixed(2);
|
||
}
|