Files
sub2apipay/src/lib/order/fee.ts
erio 937f54dec2 feat: 集成微信支付直连(Native + H5)及金融级安全修复
- 新增 wxpay provider(wechatpay-node-v3 SDK),支持 Native 扫码和 H5 跳转
- 新增 /api/wxpay/notify 回调路由,AES-256-GCM 解密 + RSA 签名验证
- 修复 confirmPayment count=0 静默成功、充值失败返回 true 等 P0 问题
- 修复 notifyUrl 硬编码 easypay、回调金额覆盖订单金额等 P1 问题
- 手续费计算改用 Prisma.Decimal 精确运算,消除浮点误差
- 支付宝 provider 移除冗余 paramsForVerify,fetch 添加超时
- 补充 .env.example 配置文档和 CLAUDE.md 支付渠道说明
2026-03-06 13:57:52 +08:00

45 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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): number {
if (feeRate <= 0) return rechargeAmount;
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).toNumber();
}