fix(billing): 修复计费漏洞
- 允许余额透支策略
## 问题
- 扣费失败时只记录日志,不阻止请求完成
- 用户可以用极少余额无限次免费使用服务
- 数据库层使用 BalanceGTE 条件防止余额变负,导致余额不足时扣费失败
## 修复
- 移除 DeductBalance 方法中的 BalanceGTE 条件,允许余额变为负数
- 修改错误返回:用户不存在时返回 ErrUserNotFound
- 实现透支策略:余额不足时允许本次请求完成,余额变负后阻止后续请求
## 测试
- 更新 TestDeductBalance_InsufficientFunds 测试,验证透支功能
- 更新 TestDeductBalance_NotFound 测试,验证正确的错误类型
- 新增 TestDeductBalance_AllowsOverdraft 测试,专门测试透支场景
- 所有测试通过 ✅
This commit is contained in:
@@ -290,9 +290,14 @@ func (s *UserRepoSuite) TestDeductBalance() {
|
||||
func (s *UserRepoSuite) TestDeductBalance_InsufficientFunds() {
|
||||
user := s.mustCreateUser(&service.User{Email: "insuf@test.com", Balance: 5})
|
||||
|
||||
// 透支策略:允许扣除超过余额的金额
|
||||
err := s.repo.DeductBalance(s.ctx, user.ID, 999)
|
||||
s.Require().Error(err, "expected error for insufficient balance")
|
||||
s.Require().ErrorIs(err, service.ErrInsufficientBalance)
|
||||
s.Require().NoError(err, "DeductBalance should allow overdraft")
|
||||
|
||||
// 验证余额变为负数
|
||||
got, err := s.repo.GetByID(s.ctx, user.ID)
|
||||
s.Require().NoError(err)
|
||||
s.Require().InDelta(-994.0, got.Balance, 1e-6, "Balance should be negative after overdraft")
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestDeductBalance_ExactAmount() {
|
||||
@@ -306,6 +311,19 @@ func (s *UserRepoSuite) TestDeductBalance_ExactAmount() {
|
||||
s.Require().InDelta(0.0, got.Balance, 1e-6)
|
||||
}
|
||||
|
||||
func (s *UserRepoSuite) TestDeductBalance_AllowsOverdraft() {
|
||||
user := s.mustCreateUser(&service.User{Email: "overdraft@test.com", Balance: 5.0})
|
||||
|
||||
// 扣除超过余额的金额 - 应该成功
|
||||
err := s.repo.DeductBalance(s.ctx, user.ID, 10.0)
|
||||
s.Require().NoError(err, "DeductBalance should allow overdraft")
|
||||
|
||||
// 验证余额为负
|
||||
got, err := s.repo.GetByID(s.ctx, user.ID)
|
||||
s.Require().NoError(err)
|
||||
s.Require().InDelta(-5.0, got.Balance, 1e-6, "Balance should be -5.0 after overdraft")
|
||||
}
|
||||
|
||||
// --- Concurrency ---
|
||||
|
||||
func (s *UserRepoSuite) TestUpdateConcurrency() {
|
||||
@@ -511,6 +529,6 @@ func (s *UserRepoSuite) TestUpdateConcurrency_NotFound() {
|
||||
func (s *UserRepoSuite) TestDeductBalance_NotFound() {
|
||||
err := s.repo.DeductBalance(s.ctx, 999999, 5)
|
||||
s.Require().Error(err, "expected error for non-existent user")
|
||||
// DeductBalance 在用户不存在时返回 ErrInsufficientBalance 因为 WHERE 条件不匹配
|
||||
s.Require().ErrorIs(err, service.ErrInsufficientBalance)
|
||||
// DeductBalance 在用户不存在时返回 ErrUserNotFound
|
||||
s.Require().ErrorIs(err, service.ErrUserNotFound)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user