Compare commits

...

2 Commits

Author SHA1 Message Date
irving
7443c33d7a fix order placement error
Some checks failed
Build and Push Backend / docker (push) Failing after 6s
2025-11-02 21:51:08 -05:00
irving
c463179e83 fix(order): 前置余额扣减并统一金额精度处理,补充余额校验与单测
- 抽取 validateSufficientBalance,统一使用 normalizeMoney 校验与比较,提升健壮性\n- AbstractOrderPlacementStrategy:在创建订单前根据 shouldDeduct 进行余额校验与扣减,使用上下文 orderId 记录流水,避免不一致\n- deductCustomerBalance:使用 amountToDeduct 变量并先归一化后运算,修正可能的精度问题\n- 调整/补充测试用例:扣减失败不插入订单、不保存用户信息;更新 selectById 调用次数校验
2025-11-02 16:03:59 -05:00
3 changed files with 32 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ import com.starry.admin.modules.order.module.dto.OrderPlacementCommand;
import com.starry.admin.modules.order.module.dto.OrderPlacementResult; import com.starry.admin.modules.order.module.dto.OrderPlacementResult;
import com.starry.admin.modules.order.module.dto.PaymentInfo; import com.starry.admin.modules.order.module.dto.PaymentInfo;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import java.math.BigDecimal;
abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy { abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy {
@@ -26,14 +27,20 @@ abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy
throw new CustomException("支付信息不能为空"); throw new CustomException("支付信息不能为空");
} }
BigDecimal netAmount = service.normalizeMoney(paymentInfo.getFinalAmount());
boolean shouldDeduct = command.isDeductBalance() && service.shouldDeductBalance(context);
if (shouldDeduct) {
service.validateSufficientBalance(context.getPurchaserBy(), netAmount);
}
PlayOrderInfoEntity order = service.createOrderRecord(context); PlayOrderInfoEntity order = service.createOrderRecord(context);
if (command.isDeductBalance() && service.shouldDeductBalance(context)) { if (shouldDeduct) {
service.deductCustomerBalance( service.deductCustomerBalance(
context.getPurchaserBy(), context.getPurchaserBy(),
service.normalizeMoney(paymentInfo.getFinalAmount()), netAmount,
command.getBalanceOperationAction(), command.getBalanceOperationAction(),
order.getId()); context.getOrderId());
} }
OrderAmountBreakdown amountBreakdown = OrderAmountBreakdown amountBreakdown =

View File

@@ -162,6 +162,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
validateCouponUsage(context); validateCouponUsage(context);
OrderConstant.RewardType rewardType = context.getRewardType() != null OrderConstant.RewardType rewardType = context.getRewardType() != null
? context.getRewardType() ? context.getRewardType()
: OrderConstant.RewardType.NOT_APPLICABLE; : OrderConstant.RewardType.NOT_APPLICABLE;
@@ -278,6 +279,18 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
return OrderAmountBreakdown.of(grossAmount, discountAmount, netAmount); return OrderAmountBreakdown.of(grossAmount, discountAmount, netAmount);
} }
void validateSufficientBalance(String customerId, BigDecimal requiredAmount) {
PlayCustomUserInfoEntity customer = customUserInfoService.selectById(customerId);
if (customer == null) {
throw new CustomException("顾客不存在");
}
BigDecimal before = normalizeMoney(customer.getAccountBalance());
BigDecimal required = normalizeMoney(requiredAmount);
if (required.compareTo(before) > 0) {
throw new ServiceException("余额不足", 998);
}
}
void deductCustomerBalance( void deductCustomerBalance(
String customerId, String customerId,
BigDecimal netAmount, BigDecimal netAmount,
@@ -288,10 +301,11 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
throw new CustomException("顾客不存在"); throw new CustomException("顾客不存在");
} }
BigDecimal before = normalizeMoney(customer.getAccountBalance()); BigDecimal before = normalizeMoney(customer.getAccountBalance());
if (netAmount.compareTo(before) > 0) { BigDecimal amountToDeduct = normalizeMoney(netAmount);
if (amountToDeduct.compareTo(before) > 0) {
throw new ServiceException("余额不足", 998); throw new ServiceException("余额不足", 998);
} }
BigDecimal after = normalizeMoney(before.subtract(netAmount)); BigDecimal after = normalizeMoney(before.subtract(amountToDeduct));
String action = StrUtil.isNotBlank(operationAction) ? operationAction : "下单"; String action = StrUtil.isNotBlank(operationAction) ? operationAction : "下单";
customUserInfoService.updateAccountBalanceById( customUserInfoService.updateAccountBalanceById(
customerId, customerId,
@@ -299,7 +313,7 @@ public class OrderLifecycleServiceImpl implements IOrderLifecycleService {
after, after,
BalanceOperationType.CONSUME.getCode(), BalanceOperationType.CONSUME.getCode(),
action, action,
netAmount, amountToDeduct,
BigDecimal.ZERO, BigDecimal.ZERO,
orderId); orderId);
} }

View File

@@ -733,6 +733,8 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
assertThrows(ServiceException.class, () -> lifecycleService.placeOrder(command)); assertThrows(ServiceException.class, () -> lifecycleService.placeOrder(command));
verify(customUserInfoService, never()).updateAccountBalanceById(anyString(), any(), any(), anyString(), anyString(), any(), any(), anyString()); verify(customUserInfoService, never()).updateAccountBalanceById(anyString(), any(), any(), anyString(), anyString(), any(), any(), anyString());
verify(orderInfoMapper, never()).insert(any());
verify(customUserInfoService, never()).saveOrderInfo(any());
} }
@Test @Test
@@ -863,6 +865,8 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
null); null);
assertThrows(ServiceException.class, () -> lifecycleService.placeOrder(command)); assertThrows(ServiceException.class, () -> lifecycleService.placeOrder(command));
verify(orderInfoMapper, never()).insert(any());
verify(customUserInfoService, never()).saveOrderInfo(any());
} }
@Test @Test
@@ -962,7 +966,7 @@ private PlayOrderLogInfoMapper orderLogInfoMapper;
null, null,
null)); null));
verify(customUserInfoService).selectById(context.getPurchaserBy()); verify(customUserInfoService, times(2)).selectById(context.getPurchaserBy());
verify(customUserInfoService).updateAccountBalanceById( verify(customUserInfoService).updateAccountBalanceById(
eq(customer.getId()), eq(customer.getId()),
eq(customer.getAccountBalance()), eq(customer.getAccountBalance()),