diff --git a/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java b/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java index fdda29b..25b2b37 100644 --- a/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java +++ b/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java @@ -146,7 +146,9 @@ public class DealerOrderSettlement10584Task { .eq(ShopOrder::getTenantId, TENANT_ID) .eq(ShopOrder::getDeleted, 0) .eq(ShopOrder::getPayStatus, true) - .eq(ShopOrder::getIsSettled, 0); + .eq(ShopOrder::getIsSettled, 0) + // 退款/取消订单不结算,避免“退款后仍发放分红/分润/佣金” + .and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus)); if (waterFormIds != null && !waterFormIds.isEmpty()) { qw.and(w -> w.eq(ShopOrder::getDeliveryStatus, 20).or().in(ShopOrder::getFormId, waterFormIds)); @@ -162,7 +164,9 @@ public class DealerOrderSettlement10584Task { LambdaUpdateWrapper uw = new LambdaUpdateWrapper() .eq(ShopOrder::getOrderId, orderId) .eq(ShopOrder::getTenantId, TENANT_ID) - .eq(ShopOrder::getIsSettled, 0); + .eq(ShopOrder::getIsSettled, 0) + // 二次防御:退款/取消订单不允许被“认领结算” + .and(w -> w.notIn(ShopOrder::getOrderStatus, 2, 4, 5, 6, 7).or().isNull(ShopOrder::getOrderStatus)); if (waterFormIds != null && !waterFormIds.isEmpty()) { uw.and(w -> w.eq(ShopOrder::getDeliveryStatus, 20).or().in(ShopOrder::getFormId, waterFormIds)); diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java index 508ec67..ae553dc 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopOrderController.java @@ -105,6 +105,8 @@ public class ShopOrderController extends BaseController { private ShopStoreFenceService shopStoreFenceService; @Resource private GltTicketRevokeService gltTicketRevokeService; + @Resource + private ShopDealerCommissionRollbackService shopDealerCommissionRollbackService; @Operation(summary = "分页查询订单") @GetMapping("/page") @@ -559,6 +561,24 @@ public class ShopOrderController extends BaseController { current.getTenantId(), current.getOrderId(), current.getOrderNo(), e); } + // 退款成功后回退分红/分润/佣金(从 ShopDealerUser 中扣回;以 ShopDealerCapital 明细为准) + try { + Integer tenantId = ObjectUtil.defaultIfNull(current.getTenantId(), getTenantId()); + ShopOrder rollbackOrder = new ShopOrder(); + rollbackOrder.setTenantId(tenantId); + rollbackOrder.setOrderNo(current.getOrderNo()); + rollbackOrder.setPayPrice(current.getPayPrice()); + rollbackOrder.setTotalPrice(current.getTotalPrice()); + boolean rollbackOk = shopDealerCommissionRollbackService.rollbackOnOrderRefund(rollbackOrder, refundAmount); + if (!rollbackOk) { + logger.error("退款成功但回退分红/分润/佣金失败 - tenantId={}, orderId={}, orderNo={}", + tenantId, current.getOrderId(), current.getOrderNo()); + } + } catch (Exception e) { + logger.error("退款成功但回退分红/分润/佣金异常 - tenantId={}, orderId={}, orderNo={}", + current.getTenantId(), current.getOrderId(), current.getOrderNo(), e); + } + logger.info("订单退款请求成功 - 订单号: {}, 退款单号: {}, 微信退款单号: {}", current.getOrderNo(), refundNo, refundResponse.getTransactionId()); return success("退款成功"); diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopDealerCommissionRollbackService.java b/src/main/java/com/gxwebsoft/shop/service/ShopDealerCommissionRollbackService.java new file mode 100644 index 0000000..e0fca92 --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/ShopDealerCommissionRollbackService.java @@ -0,0 +1,21 @@ +package com.gxwebsoft.shop.service; + +import com.gxwebsoft.shop.entity.ShopOrder; + +import java.math.BigDecimal; + +/** + * 分销/分红/分润:订单退款回退 + */ +public interface ShopDealerCommissionRollbackService { + + /** + * 订单退款成功后,按订单号回退已入账(冻结/可提现)的分销佣金/分红/分润等金额。 + * + * @param order 订单(必须包含 tenantId、orderNo) + * @param refundAmount 退款金额(允许为空;为空则按全额退款处理) + * @return true=执行成功或无可回退数据;false=执行失败 + */ + boolean rollbackOnOrderRefund(ShopOrder order, BigDecimal refundAmount); +} + diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCommissionRollbackServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCommissionRollbackServiceImpl.java new file mode 100644 index 0000000..d43001a --- /dev/null +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopDealerCommissionRollbackServiceImpl.java @@ -0,0 +1,189 @@ +package com.gxwebsoft.shop.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.gxwebsoft.shop.entity.ShopDealerCapital; +import com.gxwebsoft.shop.entity.ShopDealerOrder; +import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.service.ShopDealerCapitalService; +import com.gxwebsoft.shop.service.ShopDealerCommissionRollbackService; +import com.gxwebsoft.shop.service.ShopDealerOrderService; +import com.gxwebsoft.shop.service.ShopDealerUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class ShopDealerCommissionRollbackServiceImpl implements ShopDealerCommissionRollbackService { + + private static final int FLOW_TYPE_COMMISSION_INCOME = 10; + private static final int FLOW_TYPE_COMMISSION_UNFREEZE_MARKER = 50; + private static final int FLOW_TYPE_DELIVERY_REWARD = 60; + + @Resource + private ShopDealerCapitalService shopDealerCapitalService; + + @Resource + private ShopDealerUserService shopDealerUserService; + + @Resource + private ShopDealerOrderService shopDealerOrderService; + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean rollbackOnOrderRefund(ShopOrder order, BigDecimal refundAmount) { + if (order == null || order.getTenantId() == null || order.getOrderNo() == null || order.getOrderNo().isBlank()) { + return true; + } + + Integer tenantId = order.getTenantId(); + String orderNo = order.getOrderNo(); + + BigDecimal orderBaseAmount = ObjectUtil.defaultIfNull(order.getPayPrice(), order.getTotalPrice()); + BigDecimal ratio = resolveRefundRatio(orderBaseAmount, refundAmount); + + List capitals = shopDealerCapitalService.list( + new LambdaQueryWrapper() + .eq(ShopDealerCapital::getTenantId, tenantId) + .eq(ShopDealerCapital::getOrderNo, orderNo) + .in(ShopDealerCapital::getFlowType, FLOW_TYPE_COMMISSION_INCOME, FLOW_TYPE_DELIVERY_REWARD) + .isNotNull(ShopDealerCapital::getUserId) + .isNotNull(ShopDealerCapital::getMoney) + .gt(ShopDealerCapital::getMoney, BigDecimal.ZERO) + ); + + if (capitals == null || capitals.isEmpty()) { + // 仍标记分销订单失效,避免后续统计误判 + markDealerOrderInvalid(tenantId, orderNo); + return true; + } + + Map freezeDeductByUser = new HashMap<>(); + Map moneyDeductByUser = new HashMap<>(); + Map totalDeductByUser = new HashMap<>(); + + for (ShopDealerCapital cap : capitals) { + Integer dealerUserId = cap.getUserId(); + BigDecimal amount = cap.getMoney(); + if (dealerUserId == null || amount == null || amount.signum() <= 0) { + continue; + } + + BigDecimal rollbackAmount = amount.multiply(ratio).setScale(2, RoundingMode.HALF_UP); + if (rollbackAmount.signum() <= 0) { + continue; + } + + totalDeductByUser.merge(dealerUserId, rollbackAmount, BigDecimal::add); + + Integer flowType = cap.getFlowType(); + if (flowType != null && flowType == FLOW_TYPE_DELIVERY_REWARD) { + moneyDeductByUser.merge(dealerUserId, rollbackAmount, BigDecimal::add); + continue; + } + + // 佣金收入:若已解冻(有 flowType=50 marker),则从可提现扣回;否则从冻结扣回 + boolean unfrozen = hasUnfreezeMarker(tenantId, cap); + if (unfrozen) { + moneyDeductByUser.merge(dealerUserId, rollbackAmount, BigDecimal::add); + } else { + freezeDeductByUser.merge(dealerUserId, rollbackAmount, BigDecimal::add); + } + } + + LocalDateTime now = LocalDateTime.now(); + for (Map.Entry entry : totalDeductByUser.entrySet()) { + Integer dealerUserId = entry.getKey(); + BigDecimal totalDeduct = entry.getValue(); + if (dealerUserId == null || totalDeduct == null || totalDeduct.signum() <= 0) { + continue; + } + + BigDecimal freezeDeduct = freezeDeductByUser.getOrDefault(dealerUserId, BigDecimal.ZERO); + BigDecimal moneyDeduct = moneyDeductByUser.getOrDefault(dealerUserId, BigDecimal.ZERO); + + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() + .eq(ShopDealerUser::getTenantId, tenantId) + .eq(ShopDealerUser::getUserId, dealerUserId) + .setSql("total_money = IFNULL(total_money,0) - " + totalDeduct.toPlainString()) + .set(ShopDealerUser::getUpdateTime, now); + + if (freezeDeduct.signum() > 0) { + uw.setSql("freeze_money = IFNULL(freeze_money,0) - " + freezeDeduct.toPlainString()); + } + if (moneyDeduct.signum() > 0) { + uw.setSql("money = IFNULL(money,0) - " + moneyDeduct.toPlainString()); + } + + boolean updated = shopDealerUserService.update(uw); + if (!updated) { + log.warn("订单退款扣回分销金额失败:未找到分销账户 - tenantId={}, orderNo={}, dealerUserId={}, totalDeduct={}, freezeDeduct={}, moneyDeduct={}", + tenantId, orderNo, dealerUserId, totalDeduct, freezeDeduct, moneyDeduct); + } + } + + markDealerOrderInvalid(tenantId, orderNo); + return true; + } + + private boolean hasUnfreezeMarker(Integer tenantId, ShopDealerCapital cap) { + if (tenantId == null || cap == null || cap.getId() == null || cap.getUserId() == null || cap.getOrderNo() == null) { + return false; + } + String markerComment = buildUnfreezeMarkerComment(cap.getId()); + return shopDealerCapitalService.count( + new LambdaQueryWrapper() + .eq(ShopDealerCapital::getTenantId, tenantId) + .eq(ShopDealerCapital::getFlowType, FLOW_TYPE_COMMISSION_UNFREEZE_MARKER) + .eq(ShopDealerCapital::getUserId, cap.getUserId()) + .eq(ShopDealerCapital::getOrderNo, cap.getOrderNo()) + .eq(ShopDealerCapital::getComments, markerComment) + ) > 0; + } + + private String buildUnfreezeMarkerComment(Integer capitalId) { + return "佣金解冻(capitalId=" + capitalId + ")"; + } + + private BigDecimal resolveRefundRatio(BigDecimal orderBaseAmount, BigDecimal refundAmount) { + if (refundAmount == null || refundAmount.signum() <= 0) { + return BigDecimal.ONE; + } + if (orderBaseAmount == null || orderBaseAmount.signum() <= 0) { + return BigDecimal.ONE; + } + if (refundAmount.compareTo(orderBaseAmount) >= 0) { + return BigDecimal.ONE; + } + return refundAmount.divide(orderBaseAmount, 10, RoundingMode.HALF_UP); + } + + private void markDealerOrderInvalid(Integer tenantId, String orderNo) { + if (tenantId == null || orderNo == null || orderNo.isBlank()) { + return; + } + try { + shopDealerOrderService.update( + new LambdaUpdateWrapper() + .eq(ShopDealerOrder::getTenantId, tenantId) + .eq(ShopDealerOrder::getOrderNo, orderNo) + .set(ShopDealerOrder::getIsInvalid, 1) + .set(ShopDealerOrder::getUpdateTime, LocalDateTime.now()) + ); + } catch (Exception e) { + log.warn("订单退款标记分销订单失效失败 - tenantId={}, orderNo={}", tenantId, orderNo, e); + } + } +} +