fix(order): 前置余额扣减并统一金额精度处理,补充余额校验与单测
- 抽取 validateSufficientBalance,统一使用 normalizeMoney 校验与比较,提升健壮性\n- AbstractOrderPlacementStrategy:在创建订单前根据 shouldDeduct 进行余额校验与扣减,使用上下文 orderId 记录流水,避免不一致\n- deductCustomerBalance:使用 amountToDeduct 变量并先归一化后运算,修正可能的精度问题\n- 调整/补充测试用例:扣减失败不插入订单、不保存用户信息;更新 selectById 调用次数校验
This commit is contained in:
@@ -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,16 +27,19 @@ abstract class AbstractOrderPlacementStrategy implements OrderPlacementStrategy
|
|||||||
throw new CustomException("支付信息不能为空");
|
throw new CustomException("支付信息不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayOrderInfoEntity order = service.createOrderRecord(context);
|
BigDecimal netAmount = service.normalizeMoney(paymentInfo.getFinalAmount());
|
||||||
|
boolean shouldDeduct = command.isDeductBalance() && service.shouldDeductBalance(context);
|
||||||
if (command.isDeductBalance() && service.shouldDeductBalance(context)) {
|
if (shouldDeduct) {
|
||||||
|
service.validateSufficientBalance(context.getPurchaserBy(), netAmount);
|
||||||
service.deductCustomerBalance(
|
service.deductCustomerBalance(
|
||||||
context.getPurchaserBy(),
|
context.getPurchaserBy(),
|
||||||
service.normalizeMoney(paymentInfo.getFinalAmount()),
|
netAmount,
|
||||||
command.getBalanceOperationAction(),
|
command.getBalanceOperationAction(),
|
||||||
order.getId());
|
context.getOrderId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayOrderInfoEntity order = service.createOrderRecord(context);
|
||||||
|
|
||||||
OrderAmountBreakdown amountBreakdown =
|
OrderAmountBreakdown amountBreakdown =
|
||||||
breakdown != null ? breakdown : service.fallbackBreakdown(paymentInfo);
|
breakdown != null ? breakdown : service.fallbackBreakdown(paymentInfo);
|
||||||
return OrderPlacementResult.of(order, amountBreakdown);
|
return OrderPlacementResult.of(order, amountBreakdown);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
|||||||
Reference in New Issue
Block a user