feat: 退款三层风控——余额必须够扣(已消费不可退)+7天窗口+每人每月限1次
This commit is contained in:
@@ -1238,8 +1238,37 @@ router.post('/vpay/orders/:id/refund', requirePermission('orders:write'), async
|
||||
const user = await User.findById(order.userId)
|
||||
if (!user) return ApiResponse.error(res, '用户不存在', 400)
|
||||
|
||||
// 扣回星尘(允许扣到负数,管理员可判断)
|
||||
user.vpayCoins = (user.vpayCoins || 0) - coins
|
||||
// ===== 退款风控(三层,防恶意充值→消费→退款循环) =====
|
||||
// 第一层:余额必须够扣(星尘已消费 = 虚拟商品已使用,不支持退款)
|
||||
const currentBalance = user.vpayCoins || 0
|
||||
if (currentBalance < coins) {
|
||||
return ApiResponse.error(res,
|
||||
`不可退款:用户星尘余额 ${currentBalance},需扣 ${coins}。已消费的星尘不支持退款。`,
|
||||
409, { blocked: 'insufficient_balance', currentBalance, coinsToDeduct: coins })
|
||||
}
|
||||
|
||||
// 第二层:退款时间窗口(支付后 7 天内可退)
|
||||
const paidAt = order.paidAt || order.createdAt
|
||||
const daysSincePaid = (Date.now() - new Date(paidAt).getTime()) / 86400000
|
||||
if (daysSincePaid > 7) {
|
||||
return ApiResponse.error(res,
|
||||
`不可退款:订单支付已 ${Math.floor(daysSincePaid)} 天,超过 7 天退款窗口。`,
|
||||
409, { blocked: 'expired' })
|
||||
}
|
||||
|
||||
// 第三层:同一用户 30 天内最多 1 次退款
|
||||
const recentRefund = await Order.findOne({
|
||||
userId: user._id, status: 'refunded',
|
||||
refundedAt: { $gte: new Date(Date.now() - 30 * 86400000) },
|
||||
})
|
||||
if (recentRefund) {
|
||||
return ApiResponse.error(res,
|
||||
`不可退款:该用户 30 天内已有退款记录(${recentRefund.orderNo})。每人每月限退 1 次。`,
|
||||
409, { blocked: 'rate_limited', lastRefund: recentRefund.orderNo })
|
||||
}
|
||||
|
||||
// 风控通过:先调微信退钱(钱是用户的)→ 成功后本地扣星尘
|
||||
user.vpayCoins = currentBalance - coins
|
||||
await user.save()
|
||||
|
||||
// ===== 调用微信虚拟支付退款 API(真正退钱给用户) =====
|
||||
|
||||
Reference in New Issue
Block a user