mirror of
https://codeup.aliyun.com/67c68d4e484ca2f0a13ac3c1/ydc/jsowell-charger-web.git
synced 2026-08-10 00:03:44 +08:00
bugfix balance refund duplicate risk
This commit is contained in:
103
docs/plan/2026-06-15-balance-refund-duplicate-optimization.md
Normal file
103
docs/plan/2026-06-15-balance-refund-duplicate-optimization.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Plan: 余额支付退款重复退款优化
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
余额支付订单退款当前允许同一订单产生多条钱包进账流水,这是合理的:客户投诉、人工补退、重算补退等场景都可能需要追加退款。
|
||||||
|
|
||||||
|
本次优化不采用“一单只能插一次”的唯一约束,而是改为累计金额控制:
|
||||||
|
|
||||||
|
- 同一订单余额退款可以多次发生。
|
||||||
|
- 累计退款金额不能超过该订单余额支付金额。
|
||||||
|
- 自动结算重复执行时,只能退到订单当前目标退款金额,不能重复按全量退款。
|
||||||
|
- 钱包余额更新失败时,不能继续插入钱包流水。
|
||||||
|
|
||||||
|
## 风险点
|
||||||
|
|
||||||
|
### 风险 1:余额退款按全量重算
|
||||||
|
|
||||||
|
`balancePaymentOrderRefund` 和 `balancePaymentOrderRefundV2` 当前会按订单支付记录与消费金额重新计算本次退款金额。
|
||||||
|
|
||||||
|
如果同一订单已经存在部分退款流水,再次执行时没有按“目标退款金额 - 已退金额”计算差额,存在重复退款风险。
|
||||||
|
|
||||||
|
### 风险 2:钱包更新失败后仍可能插流水
|
||||||
|
|
||||||
|
`MemberBasicInfoServiceImpl.updateMemberBalance` 使用 `version` 更新钱包余额,但当更新返回 `0` 时只打印日志,后续仍可能插入 `member_wallet_log`。
|
||||||
|
|
||||||
|
这会造成余额与流水不一致,也会影响后续已退款金额判断。
|
||||||
|
|
||||||
|
### 风险 3:balancePaymentOrderRefundV2 无幂等校验
|
||||||
|
|
||||||
|
`OrderBasicInfoServiceImpl.balancePaymentOrderRefundV2` 没有已退金额校验,也没有累计退款上限。
|
||||||
|
|
||||||
|
重复调用时可能持续向会员钱包退款。
|
||||||
|
|
||||||
|
## 优化原则
|
||||||
|
|
||||||
|
1. 不限制同一订单只能插入一条退款流水。
|
||||||
|
2. 同一订单累计余额退款总额不能超过余额支付总额。
|
||||||
|
3. 自动结算退款以 `order_basic_info.refund_amount` 作为当前目标退款金额;如果需要客户投诉补退,应先把目标退款金额调整到新的业务目标,且不能超过支付总额。
|
||||||
|
4. 本金与赠送金分别受各自支付金额上限约束。
|
||||||
|
5. 钱包余额更新与钱包流水必须保持事务一致。
|
||||||
|
|
||||||
|
## 子任务
|
||||||
|
|
||||||
|
### BR-001
|
||||||
|
|
||||||
|
- 优先级:P0
|
||||||
|
- 状态:done
|
||||||
|
- 目标:补 `balancePaymentOrderRefundV2` 累计退款上限与差额退款。
|
||||||
|
- 设计:
|
||||||
|
- 查询订单已退本金、已退赠送金。
|
||||||
|
- 订单目标退款金额取 `min(order.refundAmount, balancePayTotal)`。
|
||||||
|
- 本次退款金额取 `目标退款金额 - 已退总额` 范围内的金额。
|
||||||
|
- 本金累计退款不能超过本金支付金额,赠送金累计退款不能超过赠送金支付金额。
|
||||||
|
- 验收:
|
||||||
|
- 同一订单重复调用 V2,不会超过当前目标退款金额。
|
||||||
|
- 如果目标退款金额被业务调整变大,可以继续补退,但累计不超过余额支付总额。
|
||||||
|
|
||||||
|
### BR-002
|
||||||
|
|
||||||
|
- 优先级:P1
|
||||||
|
- 状态:done
|
||||||
|
- 目标:统一新旧余额退款逻辑。
|
||||||
|
- 设计:
|
||||||
|
- 在 `OrderBasicInfoService` 提供统一的余额退款额度裁剪方法。
|
||||||
|
- `DelayMerchantProgramLogic.balancePaymentOrderRefund`、`NotDelayMerchantProgramLogic.balancePaymentOrderRefund`、`OrderBasicInfoServiceImpl.balancePaymentOrderRefundV2` 都复用同一套累计上限判断。
|
||||||
|
- 验收:
|
||||||
|
- 三条余额支付退款路径的已退金额口径一致。
|
||||||
|
|
||||||
|
### BR-003
|
||||||
|
|
||||||
|
- 优先级:P2
|
||||||
|
- 状态:done
|
||||||
|
- 目标:修复钱包更新事务一致性。
|
||||||
|
- 设计:
|
||||||
|
- `updateMemberBalance` 增加事务。
|
||||||
|
- `memberBasicInfoMapper.updateMemberBalance(...)` 返回 `0` 时抛异常。
|
||||||
|
- 钱包余额更新失败时不插入 `member_wallet_log`。
|
||||||
|
- 验收:
|
||||||
|
- 乐观锁失败时不会产生钱包流水。
|
||||||
|
|
||||||
|
### BR-004
|
||||||
|
|
||||||
|
- 优先级:P3
|
||||||
|
- 状态:done
|
||||||
|
- 目标:补充验证。
|
||||||
|
- 设计:
|
||||||
|
- 编译受影响模块。
|
||||||
|
- 对重复调用、目标退款金额调整、钱包乐观锁失败做回归验证。
|
||||||
|
- 验收:
|
||||||
|
- 编译通过,关键路径验证结果记录到本文档。
|
||||||
|
- 当前进展:
|
||||||
|
- 使用 JDK 8 执行 `mvn -pl jsowell-pile -am compile -DskipTests` 通过。
|
||||||
|
- 当前默认 JDK 21 执行 Maven 会在 `jsowell-common` 编译阶段触发老 Lombok/javac 兼容性错误,未进入本次改动编译。
|
||||||
|
|
||||||
|
## 执行日志
|
||||||
|
|
||||||
|
- 2026-06-15:创建优化方案文档,开始执行 BR-001。
|
||||||
|
- 2026-06-15:BR-001 已完成,`balancePaymentOrderRefundV2` 接入累计退款额度裁剪。
|
||||||
|
- 2026-06-15:开始执行 BR-002,统一新旧余额退款路径的累计校验口径。
|
||||||
|
- 2026-06-15:BR-002 已完成,延迟、非延迟和 V2 余额退款路径复用同一套额度裁剪逻辑。
|
||||||
|
- 2026-06-15:BR-003 已完成,`updateMemberBalance` 增加事务,钱包余额更新失败时抛异常并阻止插入流水。
|
||||||
|
- 2026-06-15:开始执行 BR-004,进行编译检查。
|
||||||
|
- 2026-06-15:BR-004 已完成,JDK 8 环境下受影响模块编译通过。
|
||||||
@@ -482,6 +482,13 @@ public interface OrderBasicInfoService{
|
|||||||
*/
|
*/
|
||||||
BigDecimal getOrderRefundedAmount(String orderCode);
|
BigDecimal getOrderRefundedAmount(String orderCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算余额支付订单本次允许退款金额
|
||||||
|
*/
|
||||||
|
Map<String, BigDecimal> calculateAllowableBalanceRefund(String orderCode, BigDecimal refundAmount,
|
||||||
|
BigDecimal principalPay, BigDecimal giftPay,
|
||||||
|
BigDecimal returnPrincipal, BigDecimal returnGift);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过orderCode获取退款明细列表
|
* 通过orderCode获取退款明细列表
|
||||||
* @param orderCode
|
* @param orderCode
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -213,6 +214,7 @@ public class MemberBasicInfoServiceImpl implements MemberBasicInfoService {
|
|||||||
* 接收的金额都是正数,通过操作类型判断 充值还是扣减
|
* 接收的金额都是正数,通过操作类型判断 充值还是扣减
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int updateMemberBalance(UpdateMemberBalanceDTO dto) {
|
public int updateMemberBalance(UpdateMemberBalanceDTO dto) {
|
||||||
String memberId = dto.getMemberId(); // 会员id
|
String memberId = dto.getMemberId(); // 会员id
|
||||||
BigDecimal updateGiftBalance = dto.getUpdateGiftBalance() != null ? dto.getUpdateGiftBalance() : BigDecimal.ZERO; // 更新赠送金额
|
BigDecimal updateGiftBalance = dto.getUpdateGiftBalance() != null ? dto.getUpdateGiftBalance() : BigDecimal.ZERO; // 更新赠送金额
|
||||||
@@ -412,6 +414,7 @@ public class MemberBasicInfoServiceImpl implements MemberBasicInfoService {
|
|||||||
i = memberBasicInfoMapper.updateMemberBalance(memberId, walletCode, newPrincipalBalance, newGiftBalance, walletInfo.getVersion());
|
i = memberBasicInfoMapper.updateMemberBalance(memberId, walletCode, newPrincipalBalance, newGiftBalance, walletInfo.getVersion());
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
log.info("修改用户余额-修改余额失败, memberId:{}", memberId);
|
log.info("修改用户余额-修改余额失败, memberId:{}", memberId);
|
||||||
|
throw new BusinessException("", "修改用户余额失败,请重试");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 插入 member_wallet_log 表
|
// 插入 member_wallet_log 表
|
||||||
|
|||||||
@@ -1584,32 +1584,55 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
|||||||
public void balancePaymentOrderRefundV2(OrderBasicInfo orderBasicInfo) {
|
public void balancePaymentOrderRefundV2(OrderBasicInfo orderBasicInfo) {
|
||||||
// 订单编号
|
// 订单编号
|
||||||
String orderCode = orderBasicInfo.getOrderCode();
|
String orderCode = orderBasicInfo.getOrderCode();
|
||||||
|
BigDecimal refundAmount = orderBasicInfo.getRefundAmount();
|
||||||
|
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("balancePaymentOrderRefundV2-退款金额小于等于0,orderCode:{}, 退款金额:{}", orderCode, refundAmount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 订单消费金额
|
// 订单消费金额
|
||||||
BigDecimal orderAmount = orderBasicInfo.getOrderAmount();
|
BigDecimal orderAmount = orderBasicInfo.getOrderAmount() == null ? BigDecimal.ZERO : orderBasicInfo.getOrderAmount();
|
||||||
// 查支付记录
|
// 查支付记录
|
||||||
List<OrderPayRecord> payRecordList = orderPayRecordService.getOrderPayRecordList(orderCode);
|
List<OrderPayRecord> payRecordList = orderPayRecordService.getOrderPayRecordList(orderCode);
|
||||||
|
if (CollectionUtils.isEmpty(payRecordList)) {
|
||||||
|
logger.info("balancePaymentOrderRefundV2-订单支付记录为空,orderCode:{}", orderCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Map<String, OrderPayRecord> payRecordMap = payRecordList.stream()
|
// Map<String, OrderPayRecord> payRecordMap = payRecordList.stream()
|
||||||
// .collect(Collectors.toMap(OrderPayRecord::getPayMode, Function.identity(), (k1, k2) -> k1));
|
// .collect(Collectors.toMap(OrderPayRecord::getPayMode, Function.identity(), (k1, k2) -> k1));
|
||||||
Map<String, List<OrderPayRecord>> payRecordMap = payRecordList.stream().collect(Collectors.groupingBy(OrderPayRecord::getPayMode));
|
Map<String, List<OrderPayRecord>> payRecordMap = payRecordList.stream().collect(Collectors.groupingBy(OrderPayRecord::getPayMode));
|
||||||
// 获取本金支付的记录
|
// 获取本金支付的记录
|
||||||
List<OrderPayRecord> orderPayRecordList = payRecordMap.get(Constants.ONE);
|
List<OrderPayRecord> principalPayRecordList = payRecordMap.get(OrderPayRecordEnum.PRINCIPAL_BALANCE_PAYMENT.getValue());
|
||||||
BigDecimal principalPay = orderPayRecordList.stream().map(OrderPayRecord::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
BigDecimal principalPay = null;
|
||||||
|
if (CollectionUtils.isNotEmpty(principalPayRecordList)) {
|
||||||
|
principalPay = principalPayRecordList.stream().map(OrderPayRecord::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
}
|
||||||
|
|
||||||
// 取出本金支付金额
|
// 获取赠送金支付的记录
|
||||||
// BigDecimal principalPay = null;
|
List<OrderPayRecord> giftPayRecordList = payRecordMap.get(OrderPayRecordEnum.GIFT_BALANCE_PAYMENT.getValue());
|
||||||
|
BigDecimal giftPay = null;
|
||||||
// if (principalPayRecord != null) {
|
if (CollectionUtils.isNotEmpty(giftPayRecordList)) {
|
||||||
// principalPay = principalPayRecord.getPayAmount();
|
giftPay = giftPayRecordList.stream().map(OrderPayRecord::getPayAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// 计算需要退回的金额
|
// 计算需要退回的金额
|
||||||
Map<String, BigDecimal> returnAmountMap = calculateReturnAmount(principalPay, null, orderAmount);
|
Map<String, BigDecimal> returnAmountMap = calculateReturnAmount(principalPay, giftPay, orderAmount);
|
||||||
logger.info("结算订单:{}, 剩余金额退回余额, 订单消费金额:{}, 本金支付金额:{}, 赠送支付金额:{}, 退回金额map:{}",
|
logger.info("结算订单:{}, 剩余金额退回余额, 订单消费金额:{}, 本金支付金额:{}, 赠送支付金额:{}, 退回金额map:{}",
|
||||||
orderCode, orderAmount, principalPay, null, JSON.toJSONString(returnAmountMap));
|
orderCode, orderAmount, principalPay, giftPay, JSON.toJSONString(returnAmountMap));
|
||||||
|
|
||||||
// 需要退回本金的金额
|
// 需要退回本金的金额
|
||||||
BigDecimal returnPrincipal = returnAmountMap.get("returnPrincipal");
|
BigDecimal returnPrincipal = returnAmountMap.get("returnPrincipal");
|
||||||
|
// 需要退回赠送金的金额
|
||||||
|
BigDecimal returnGift = returnAmountMap.get("returnGift");
|
||||||
|
|
||||||
|
Map<String, BigDecimal> allowableRefundMap = calculateAllowableBalanceRefund(orderCode, refundAmount,
|
||||||
|
principalPay, giftPay, returnPrincipal, returnGift);
|
||||||
|
returnPrincipal = allowableRefundMap.get("returnPrincipal");
|
||||||
|
returnGift = allowableRefundMap.get("returnGift");
|
||||||
|
if (returnPrincipal.compareTo(BigDecimal.ZERO) <= 0 && returnGift.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("balancePaymentOrderRefundV2-订单:{}本次可退金额为0,不执行退款", orderCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 更新会员钱包/余额退回到钱包
|
// 更新会员钱包/余额退回到钱包
|
||||||
UpdateMemberBalanceDTO updateMemberBalanceDTO = UpdateMemberBalanceDTO.builder()
|
UpdateMemberBalanceDTO updateMemberBalanceDTO = UpdateMemberBalanceDTO.builder()
|
||||||
@@ -1617,6 +1640,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
|||||||
.type(MemberWalletEnum.TYPE_IN.getValue()) // 进账
|
.type(MemberWalletEnum.TYPE_IN.getValue()) // 进账
|
||||||
.subType(MemberWalletEnum.SUBTYPE_ORDER_SETTLEMENT_REFUND.getValue()) // 订单结算退款
|
.subType(MemberWalletEnum.SUBTYPE_ORDER_SETTLEMENT_REFUND.getValue()) // 订单结算退款
|
||||||
.updatePrincipalBalance(returnPrincipal)
|
.updatePrincipalBalance(returnPrincipal)
|
||||||
|
.updateGiftBalance(returnGift)
|
||||||
.relatedOrderCode(orderCode)
|
.relatedOrderCode(orderCode)
|
||||||
.build();
|
.build();
|
||||||
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
||||||
@@ -5618,6 +5642,102 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
|||||||
return orderBasicInfoMapper.queryRepayOrder(memberId);
|
return orderBasicInfoMapper.queryRepayOrder(memberId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算余额支付订单本次允许退款金额
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, BigDecimal> calculateAllowableBalanceRefund(String orderCode, BigDecimal refundAmount,
|
||||||
|
BigDecimal principalPay, BigDecimal giftPay,
|
||||||
|
BigDecimal returnPrincipal, BigDecimal returnGift) {
|
||||||
|
Map<String, BigDecimal> resultMap = Maps.newHashMap();
|
||||||
|
resultMap.put("returnPrincipal", BigDecimal.ZERO);
|
||||||
|
resultMap.put("returnGift", BigDecimal.ZERO);
|
||||||
|
|
||||||
|
principalPay = positiveOrZero(principalPay);
|
||||||
|
giftPay = positiveOrZero(giftPay);
|
||||||
|
returnPrincipal = positiveOrZero(returnPrincipal);
|
||||||
|
returnGift = positiveOrZero(returnGift);
|
||||||
|
|
||||||
|
BigDecimal balancePayTotal = principalPay.add(giftPay);
|
||||||
|
BigDecimal targetRefundAmount = positiveOrZero(refundAmount);
|
||||||
|
if (StringUtils.isBlank(orderCode) || balancePayTotal.compareTo(BigDecimal.ZERO) <= 0
|
||||||
|
|| targetRefundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("余额退款额度裁剪-参数不足,orderCode:{}, 目标退款金额:{}, 余额支付总额:{}",
|
||||||
|
orderCode, targetRefundAmount, balancePayTotal);
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetRefundAmount.compareTo(balancePayTotal) > 0) {
|
||||||
|
logger.info("余额退款额度裁剪-orderCode:{}, 目标退款金额:{}大于余额支付总额:{},按余额支付总额限制",
|
||||||
|
orderCode, targetRefundAmount, balancePayTotal);
|
||||||
|
targetRefundAmount = balancePayTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BigDecimal> refundedAmountMap = getBalanceRefundedAmountByCategory(orderCode);
|
||||||
|
BigDecimal refundedPrincipal = refundedAmountMap.get("refundedPrincipal");
|
||||||
|
BigDecimal refundedGift = refundedAmountMap.get("refundedGift");
|
||||||
|
BigDecimal refundedTotal = refundedPrincipal.add(refundedGift);
|
||||||
|
BigDecimal remainingTargetAmount = targetRefundAmount.subtract(refundedTotal);
|
||||||
|
if (remainingTargetAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("余额退款额度裁剪-orderCode:{}, 目标退款金额:{}, 已退总额:{},不再退款",
|
||||||
|
orderCode, targetRefundAmount, refundedTotal);
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal remainingPrincipalPay = positiveOrZero(principalPay.subtract(refundedPrincipal));
|
||||||
|
BigDecimal remainingGiftPay = positiveOrZero(giftPay.subtract(refundedGift));
|
||||||
|
|
||||||
|
BigDecimal allowedPrincipal = min(returnPrincipal, remainingPrincipalPay, remainingTargetAmount);
|
||||||
|
remainingTargetAmount = remainingTargetAmount.subtract(allowedPrincipal);
|
||||||
|
BigDecimal allowedGift = min(returnGift, remainingGiftPay, remainingTargetAmount);
|
||||||
|
|
||||||
|
resultMap.put("returnPrincipal", allowedPrincipal);
|
||||||
|
resultMap.put("returnGift", allowedGift);
|
||||||
|
|
||||||
|
logger.info("余额退款额度裁剪-orderCode:{}, 目标退款金额:{}, 支付本金:{}, 支付赠送:{}, 已退本金:{}, 已退赠送:{}, 原计算本金:{}, 原计算赠送:{}, 本次本金:{}, 本次赠送:{}",
|
||||||
|
orderCode, targetRefundAmount, principalPay, giftPay, refundedPrincipal, refundedGift,
|
||||||
|
returnPrincipal, returnGift, allowedPrincipal, allowedGift);
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, BigDecimal> getBalanceRefundedAmountByCategory(String orderCode) {
|
||||||
|
Map<String, BigDecimal> resultMap = Maps.newHashMap();
|
||||||
|
BigDecimal refundedPrincipal = BigDecimal.ZERO;
|
||||||
|
BigDecimal refundedGift = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
List<MemberWalletLog> walletLogs = memberWalletLogService.getOrderRecord(orderCode, MemberWalletEnum.TYPE_IN.getValue());
|
||||||
|
if (CollectionUtils.isNotEmpty(walletLogs)) {
|
||||||
|
for (MemberWalletLog walletLog : walletLogs) {
|
||||||
|
String subType = walletLog.getSubType();
|
||||||
|
if (StringUtils.isNotBlank(subType)
|
||||||
|
&& !StringUtils.equals(subType, MemberWalletEnum.SUBTYPE_ORDER_SETTLEMENT_REFUND.getValue())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BigDecimal amount = walletLog.getAmount() == null ? BigDecimal.ZERO : walletLog.getAmount().abs();
|
||||||
|
if (StringUtils.equals(Constants.TWO, walletLog.getCategory())) {
|
||||||
|
refundedGift = refundedGift.add(amount);
|
||||||
|
} else {
|
||||||
|
refundedPrincipal = refundedPrincipal.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap.put("refundedPrincipal", refundedPrincipal);
|
||||||
|
resultMap.put("refundedGift", refundedGift);
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal positiveOrZero(BigDecimal amount) {
|
||||||
|
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal min(BigDecimal first, BigDecimal second, BigDecimal third) {
|
||||||
|
return first.min(second).min(third);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单已退款金额
|
* 获取订单已退款金额
|
||||||
*/
|
*/
|
||||||
@@ -6735,4 +6855,3 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService {
|
|||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1076,6 +1076,7 @@ public class DelayMerchantProgramLogic extends AbstractProgramLogic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal discountAmount = orderBasicInfo.getDiscountAmount() == null ? BigDecimal.ZERO : orderBasicInfo.getDiscountAmount();
|
BigDecimal discountAmount = orderBasicInfo.getDiscountAmount() == null ? BigDecimal.ZERO : orderBasicInfo.getDiscountAmount();
|
||||||
|
BigDecimal insuranceAmount = orderBasicInfo.getInsuranceAmount() == null ? BigDecimal.ZERO : orderBasicInfo.getInsuranceAmount();
|
||||||
|
|
||||||
// 计算需要退回的金额
|
// 计算需要退回的金额
|
||||||
Map<String, BigDecimal> returnAmountMap = calculateReturnAmount(principalPay, giftPay, orderAmount, discountAmount);
|
Map<String, BigDecimal> returnAmountMap = calculateReturnAmount(principalPay, giftPay, orderAmount, discountAmount);
|
||||||
@@ -1102,14 +1103,14 @@ public class DelayMerchantProgramLogic extends AbstractProgramLogic {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 判断消费金额,如果消费金额 - 折扣金额小于 1 元,则将保险费也进行退回
|
// 判断消费金额,如果消费金额 - 折扣金额小于 1 元,则将保险费也进行退回
|
||||||
if (orderBasicInfo.getOrderAmount().subtract(orderBasicInfo.getDiscountAmount()).compareTo(BigDecimal.ONE) < 0) {
|
if (orderAmount.subtract(discountAmount).compareTo(BigDecimal.ONE) < 0) {
|
||||||
// 判断是否需要退保险费用
|
// 判断是否需要退保险费用
|
||||||
boolean checkResult = orderBasicInfoService.checkRefundInsuranceAmount(orderBasicInfo);
|
boolean checkResult = orderBasicInfoService.checkRefundInsuranceAmount(orderBasicInfo);
|
||||||
if (checkResult) {
|
if (checkResult) {
|
||||||
// 退保险, 使用 calculateBalanceRefund 重新计算完整的退款金额(电费退款+保险费退款)
|
// 退保险, 使用 calculateBalanceRefund 重新计算完整的退款金额(电费退款+保险费退款)
|
||||||
// 注意:不能在 calculateReturnAmount 的基础上叠加保险费,因为 calculateReturnAmount 不感知保险费,
|
// 注意:不能在 calculateReturnAmount 的基础上叠加保险费,因为 calculateReturnAmount 不感知保险费,
|
||||||
// 算出的退款金额中已包含保险费部分,叠加会导致保险费被重复退还
|
// 算出的退款金额中已包含保险费部分,叠加会导致保险费被重复退还
|
||||||
Map<String, BigDecimal> refundMap = calculateBalanceRefund(principalPay, giftPay, orderBasicInfo.getOrderAmount(), orderBasicInfo.getDiscountAmount(), orderBasicInfo.getInsuranceAmount());
|
Map<String, BigDecimal> refundMap = calculateBalanceRefund(principalPay, giftPay, orderAmount, discountAmount, insuranceAmount);
|
||||||
BigDecimal refundPrincipal = refundMap.get("returnPrincipal");
|
BigDecimal refundPrincipal = refundMap.get("returnPrincipal");
|
||||||
BigDecimal refundGift = refundMap.get("returnGift");
|
BigDecimal refundGift = refundMap.get("returnGift");
|
||||||
BigDecimal returnPrincipalForInsurance = refundMap.get("returnPrincipalForInsurance");
|
BigDecimal returnPrincipalForInsurance = refundMap.get("returnPrincipalForInsurance");
|
||||||
@@ -1121,6 +1122,16 @@ public class DelayMerchantProgramLogic extends AbstractProgramLogic {
|
|||||||
updateMemberBalanceDTO.setUpdateGiftBalance(refundGift.add(returnGiftForInsurance));
|
updateMemberBalanceDTO.setUpdateGiftBalance(refundGift.add(returnGiftForInsurance));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, BigDecimal> allowableRefundMap = orderBasicInfoService.calculateAllowableBalanceRefund(orderCode, refundAmount,
|
||||||
|
principalPay, giftPay, updateMemberBalanceDTO.getUpdatePrincipalBalance(), updateMemberBalanceDTO.getUpdateGiftBalance());
|
||||||
|
updateMemberBalanceDTO.setUpdatePrincipalBalance(allowableRefundMap.get("returnPrincipal"));
|
||||||
|
updateMemberBalanceDTO.setUpdateGiftBalance(allowableRefundMap.get("returnGift"));
|
||||||
|
if (updateMemberBalanceDTO.getUpdatePrincipalBalance().compareTo(BigDecimal.ZERO) <= 0
|
||||||
|
&& updateMemberBalanceDTO.getUpdateGiftBalance().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("balancePaymentOrderRefund-订单:{}本次可退金额为0,不执行退款", orderCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 统一退款
|
// 统一退款
|
||||||
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
||||||
|
|
||||||
|
|||||||
@@ -755,6 +755,11 @@ public class NotDelayMerchantProgramLogic extends AbstractProgramLogic {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void balancePaymentOrderRefund(OrderBasicInfo orderBasicInfo) {
|
public void balancePaymentOrderRefund(OrderBasicInfo orderBasicInfo) {
|
||||||
logger.info("【{}】-余额支付订单退款start orderBasicInfo:{}", this.getClass().getSimpleName(), JSON.toJSONString(orderBasicInfo));
|
logger.info("【{}】-余额支付订单退款start orderBasicInfo:{}", this.getClass().getSimpleName(), JSON.toJSONString(orderBasicInfo));
|
||||||
|
BigDecimal refundAmount = orderBasicInfo.getRefundAmount();
|
||||||
|
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("balancePaymentOrderRefund-退款金额小于等于0,orderCode:{}, 退款金额:{}", orderBasicInfo.getOrderCode(), refundAmount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 订单编号
|
// 订单编号
|
||||||
String orderCode = orderBasicInfo.getOrderCode();
|
String orderCode = orderBasicInfo.getOrderCode();
|
||||||
// 订单消费金额
|
// 订单消费金额
|
||||||
@@ -801,6 +806,16 @@ public class NotDelayMerchantProgramLogic extends AbstractProgramLogic {
|
|||||||
.updateGiftBalance(returnGift)
|
.updateGiftBalance(returnGift)
|
||||||
.relatedOrderCode(orderCode)
|
.relatedOrderCode(orderCode)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Map<String, BigDecimal> allowableRefundMap = orderBasicInfoService.calculateAllowableBalanceRefund(orderCode, refundAmount,
|
||||||
|
principalPay, giftPay, updateMemberBalanceDTO.getUpdatePrincipalBalance(), updateMemberBalanceDTO.getUpdateGiftBalance());
|
||||||
|
updateMemberBalanceDTO.setUpdatePrincipalBalance(allowableRefundMap.get("returnPrincipal"));
|
||||||
|
updateMemberBalanceDTO.setUpdateGiftBalance(allowableRefundMap.get("returnGift"));
|
||||||
|
if (updateMemberBalanceDTO.getUpdatePrincipalBalance().compareTo(BigDecimal.ZERO) <= 0
|
||||||
|
&& updateMemberBalanceDTO.getUpdateGiftBalance().compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
logger.info("balancePaymentOrderRefund-订单:{}本次可退金额为0,不执行退款", orderCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO);
|
||||||
|
|
||||||
// 更新order_pay_record, 解冻部分
|
// 更新order_pay_record, 解冻部分
|
||||||
|
|||||||
Reference in New Issue
Block a user