增加退款按订单号回退分销用户钱包流水及同步生产回退流水功能

This commit is contained in:
2026-05-13 16:32:09 +08:00
parent f3d6bbef63
commit c3fcb36f66
11 changed files with 237 additions and 8 deletions

View File

@@ -13,7 +13,8 @@ public enum ShopDealerTypeEnum {
FREEZE_ACCOUNT(1, "操作冻结账户余额"),
WITHDRAW_ACCOUNT(2, "操作提现账户余额【直接结算】"),
DEFROST(3, "解冻");
DEFROST(3, "解冻"),
ORDER_REFUND(4, "退单");
private final Integer code;
private final String desc;

View File

@@ -11,6 +11,7 @@ import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.shop.dto.ShopDealerRefundDto;
import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto;
import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.param.ShopDealerUserImportParam;
@@ -185,4 +186,11 @@ public class ShopDealerUserController extends BaseController {
return success(shopDealerUserService.reduceBalance(reduceDto));
}
@Operation(summary = "分销退单")
@PostMapping("/refundOrder")
@Deprecated
public ApiResult<?> refundOrder(@RequestBody ShopDealerRefundDto refundDto) {
return success(shopDealerUserService.refundOrder(refundDto));
}
}

View File

@@ -256,7 +256,8 @@ public class ShopDealerWithdrawController extends BaseController {
}
// 使用提现记录ID构造单号保持幂等微信要求 5-32 且仅字母/数字
String outBillNo = String.format("WD%03d", db.getId());
String outBillNo = db.getOrderNo();
String remark = "分销商提现";
String userName = db.getRealName();

View File

@@ -17,6 +17,7 @@ import com.gxwebsoft.common.core.web.BatchParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.redis.OrderNoUtils;
import com.gxwebsoft.glt.service.GltTicketIssueService;
import com.gxwebsoft.glt.service.GltTicketRevokeService;
import com.gxwebsoft.payment.dto.PaymentResponse;
@@ -111,6 +112,8 @@ public class ShopOrderController extends BaseController {
private ShopDealerCommissionRollbackService shopDealerCommissionRollbackService;
@Resource
private GltTicketIssueService gltTicketIssueService;
@Resource
private OrderNoUtils orderNoUtils;
@Operation(summary = "分页查询订单")
@GetMapping("/page")
@@ -493,12 +496,12 @@ public class ShopOrderController extends BaseController {
if (!Boolean.TRUE.equals(current.getPayStatus())) {
return fail("订单未支付,无法退款");
}
if (StrUtil.isNotBlank(current.getRefundOrder())) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", current.getOrderNo(), current.getRefundOrder());
if (StrUtil.isNotBlank(current.getRefundOrder()) || current.getOrderStatus() == 6) {
logger.warn("订单已经退款过,订单号: {}, 退款单号: {}", current.getOrderNo(), current.getRefundOrder() != null ? current.getRefundOrder() : "");
return fail("订单已退款,请勿重复操作");
}
String refundNo = "RF" + IdUtil.getSnowflakeNextId();
String refundNo = orderNoUtils.generate("RF");
BigDecimal refundAmount = req.getRefundMoney();
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
@@ -572,7 +575,7 @@ public class ShopOrderController extends BaseController {
rollbackOrder.setOrderNo(current.getOrderNo());
rollbackOrder.setPayPrice(current.getPayPrice());
rollbackOrder.setTotalPrice(current.getTotalPrice());
boolean rollbackOk = shopDealerCommissionRollbackService.rollbackOnOrderRefund(rollbackOrder, refundAmount);
boolean rollbackOk = shopDealerCommissionRollbackService.rollbackOnOrderRefundV2(rollbackOrder, refundAmount);
if (!rollbackOk) {
logger.error("退款成功但回退分红/分润/佣金失败 - tenantId={}, orderId={}, orderNo={}",
tenantId, current.getOrderId(), current.getOrderNo());

View File

@@ -0,0 +1,28 @@
package com.gxwebsoft.shop.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 分销订单退款
* @author xm
* @since 2026-05-13
*/
@Data
@Schema(name = "ShopDealerUserReduceDto", description = "分销订单退款")
public class ShopDealerRefundDto {
@Schema(description = "订单号")
@NotNull(message = "变订单号不可为空!")
private String orderNo;
@Schema(description = "租户ID")
private Integer tenantId;
@Schema(description = "退款金额")
private BigDecimal refundAmount;
}

View File

@@ -35,7 +35,7 @@ public class ShopDealerCapital implements Serializable {
@Schema(description = "分销商用户ID")
private Integer userId;
@Schema(description = "变动类型 1-操作冻结账户余额 2-操作提现账户余额【直接结算】 3-解冻")
@Schema(description = "变动类型 1-操作冻结账户余额 2-操作提现账户余额【直接结算】 3-解冻 4-退款")
private Integer type;
@Schema(description = "分销商昵称")
@@ -49,7 +49,7 @@ public class ShopDealerCapital implements Serializable {
@TableField(exist = false)
private Integer orderStatus;
@Schema(description = "资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入 50佣金解冻 60配送奖励 70佣金退回【退单】)")
@Schema(description = "资金流动类型 (10分销收入 11团队管理津贴收入 12分红收入 13现场推广收入 20提现支出 30转账支出 40转账收入 50佣金解冻 60配送奖励 70佣金退回【退单】)")
private Integer flowType;
@Schema(description = "变更金额")

View File

@@ -17,5 +17,7 @@ public interface ShopDealerCommissionRollbackService {
* @return true=执行成功或无可回退数据false=执行失败
*/
boolean rollbackOnOrderRefund(ShopOrder order, BigDecimal refundAmount);
boolean rollbackOnOrderRefundV2(ShopOrder order, BigDecimal refundAmount);
}

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.shop.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.shop.dto.ShopDealerRefundDto;
import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto;
import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.param.ShopDealerUserParam;
@@ -50,4 +51,11 @@ public interface ShopDealerUserService extends IService<ShopDealerUser> {
* @return
*/
Boolean reduceBalance(ShopDealerUserReduceDto reduceDto);
/**
* 分销订单退款
* @param refundDto
* @return
*/
Boolean refundOrder(ShopDealerRefundDto refundDto);
}

View File

@@ -1,8 +1,10 @@
package com.gxwebsoft.shop.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.gxwebsoft.shop.dto.ShopDealerRefundDto;
import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerOrder;
import com.gxwebsoft.shop.entity.ShopDealerUser;
@@ -137,6 +139,59 @@ public class ShopDealerCommissionRollbackServiceImpl implements ShopDealerCommis
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean rollbackOnOrderRefundV2(ShopOrder order, BigDecimal refundAmount) {
if (order == null || order.getTenantId() == null || StrUtil.isEmpty(order.getOrderNo())) {
return true;
}
Integer tenantId = order.getTenantId();
String orderNo = order.getOrderNo();
//判断退款金额不能大于实付金额
if(refundAmount.compareTo(order.getPayPrice()) > 0){
throw new RuntimeException("退款金额不能大于订单实付金额!");
}
//执行分销订单退款业务
ShopDealerRefundDto refundDto = new ShopDealerRefundDto();
refundDto.setOrderNo(orderNo);
refundDto.setTenantId(tenantId);
refundDto.setRefundAmount(refundAmount);
Boolean refundResult = shopDealerUserService.refundOrder(refundDto);
if(refundResult){
//退款金额与实付金额一致,标记分销订单失效
if (order.getPayPrice().compareTo(refundAmount) == 0) {
markDealerOrderInvalid(tenantId, orderNo);
}else {
BigDecimal rate = refundAmount.divide(order.getPayPrice(), 3, RoundingMode.HALF_UP);
BigDecimal remainRate = BigDecimal.ONE.subtract(rate);
LambdaQueryWrapper<ShopDealerOrder> orderLambdaQueryWrapper = new LambdaQueryWrapper<ShopDealerOrder>().eq(ShopDealerOrder::getOrderNo, orderNo);
ShopDealerOrder shopDealerOrder = shopDealerOrderService.getOne(orderLambdaQueryWrapper);
if(shopDealerOrder != null){
if(shopDealerOrder.getFirstMoney().compareTo(BigDecimal.ZERO) > 0){
shopDealerOrder.setFirstMoney(shopDealerOrder.getFirstMoney().multiply(remainRate));
}
if(shopDealerOrder.getSecondMoney().compareTo(BigDecimal.ZERO) > 0){
shopDealerOrder.setSecondMoney(shopDealerOrder.getSecondMoney().multiply(remainRate));
}
if(shopDealerOrder.getFirstDividend().compareTo(BigDecimal.ZERO) > 0){
shopDealerOrder.setFirstDividend(shopDealerOrder.getFirstDividend().multiply(remainRate));
}
if(shopDealerOrder.getSecondDividend().compareTo(BigDecimal.ZERO) > 0){
shopDealerOrder.setSecondDividend(shopDealerOrder.getSecondDividend().multiply(remainRate));
}
shopDealerOrder.setUpdateTime(LocalDateTime.now());
shopDealerOrderService.updateById(shopDealerOrder);
}
}
return Boolean.TRUE;
}else {
return Boolean.FALSE;
}
}
private boolean hasUnfreezeMarker(Integer tenantId, ShopDealerCapital cap) {
if (tenantId == null || cap == null || cap.getId() == null || cap.getUserId() == null || cap.getOrderNo() == null) {
return false;

View File

@@ -10,12 +10,16 @@ import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.common.system.redis.OrderNoUtils;
import com.gxwebsoft.shop.dto.ShopDealerRefundDto;
import com.gxwebsoft.shop.dto.ShopDealerUserReduceDto;
import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerUser;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.mapper.ShopDealerCapitalMapper;
import com.gxwebsoft.shop.mapper.ShopDealerUserMapper;
import com.gxwebsoft.shop.mapper.ShopOrderMapper;
import com.gxwebsoft.shop.param.ShopDealerUserParam;
import com.gxwebsoft.shop.service.ShopDealerCapitalService;
import com.gxwebsoft.shop.service.ShopDealerUserService;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
@@ -23,7 +27,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -44,6 +51,10 @@ public class ShopDealerUserServiceImpl extends ServiceImpl<ShopDealerUserMapper,
private ShopDealerCapitalMapper shopDealerCapitalMapper;
private ShopDealerCapitalService shopDealerCapitalService;
private ShopOrderMapper shopOrderMapper;
private static final int TENANT_ID = 10584;
@Override
@@ -246,6 +257,113 @@ public class ShopDealerUserServiceImpl extends ServiceImpl<ShopDealerUserMapper,
return Boolean.TRUE;
}
@Override
@Transactional
public Boolean refundOrder(ShopDealerRefundDto entity) {
//1.查询订单分销数据
List<ShopDealerCapital> dealerCapitalList = shopDealerCapitalMapper.selectList(
new LambdaQueryWrapper<ShopDealerCapital>()
.eq(ShopDealerCapital::getTenantId, entity.getTenantId())
.eq(ShopDealerCapital::getOrderNo, entity.getOrderNo())
.in(ShopDealerCapital::getFlowType, Arrays.asList(ShopDealerCapitalUpdateEnum.DISTRIBUTION_INCOME.getType(),
ShopDealerCapitalUpdateEnum.MANAGEMENT_INCOME.getType(), ShopDealerCapitalUpdateEnum.DIVIDEND_INCOME.getType(),
ShopDealerCapitalUpdateEnum.PROMOTION_INCOME.getType(), ShopDealerCapitalUpdateEnum.FREEZE_MONEY_THAW.getType(),
ShopDealerCapitalUpdateEnum.DELIVERY_INCOME.getType()))
.isNotNull(ShopDealerCapital::getUserId)
);
if(CollectionUtils.isNotEmpty(dealerCapitalList)){
//2.查询订单信息
ShopOrder order = shopOrderMapper.selectOne(new LambdaQueryWrapper<ShopOrder>().select(ShopOrder::getOrderId, ShopOrder::getPayPrice, ShopOrder::getUserId)
.eq(ShopOrder::getOrderNo, entity.getOrderNo()));
//3.订单不存在则回退
if(order == null){
throw new RuntimeException("退款查询订单信息失败!");
}
//4.订单实付金额为0则不执行退款业务
if(order.getPayPrice().compareTo(BigDecimal.ZERO) <= 0){
return Boolean.TRUE;
}
//5.退款金额不能大于实付金额
if(entity.getRefundAmount().compareTo(order.getPayPrice()) > 0){
throw new RuntimeException("退款金额不能大于实付金额!");
}
//6.查询分销用户信息
List<ShopDealerUser> shopDealerUserList = new ArrayList<>();
List<Integer> userIdList = dealerCapitalList.stream().map(ShopDealerCapital::getUserId).distinct().collect(Collectors.toList());
if(CollectionUtils.isNotEmpty(userIdList)){
shopDealerUserList = lambdaQuery().in(ShopDealerUser::getUserId, userIdList).list();
}
//7.创建批量更新用户钱包、批量信息分销明细集合对象
List<ShopDealerUser> shopDealerUserUpdateList = new ArrayList<>();
List<ShopDealerCapital> newCapitalList = new ArrayList<>();
//8.以用户分组,对结算订单进行业务操作
Map<Integer, List<ShopDealerCapital>> userDealerMap = dealerCapitalList.stream().collect(Collectors.groupingBy(ShopDealerCapital::getUserId));
//9.计算退款比率【退款金额/实付金额】
BigDecimal rate = entity.getRefundAmount().divide(order.getPayPrice(), 3, RoundingMode.HALF_UP);;
//10.遍历用户分销对象数据执行业务
List<ShopDealerUser> finalShopDealerUserList = shopDealerUserList;
BigDecimal finalRate = rate;
userDealerMap.forEach((k, value) ->{
BigDecimal money = value.stream().map(ShopDealerCapital::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add).multiply(finalRate);
BigDecimal freezeMoney = value.stream().map(ShopDealerCapital::getFreezeMoney).reduce(BigDecimal.ZERO, BigDecimal::add).multiply(finalRate);
ShopDealerUser dealerUser = finalShopDealerUserList.stream().filter(shopDealerUser -> k.equals(shopDealerUser.getUserId())).findFirst().orElse(null);
if(dealerUser != null){
//11.修改分销用户账户信息
dealerUser.setMoney(dealerUser.getMoney().subtract(money));
dealerUser.setFreezeMoney(dealerUser.getFreezeMoney().subtract(freezeMoney));
dealerUser.setTotalMoney(dealerUser.getTotalMoney().subtract(money.add(freezeMoney)));
dealerUser.setUpdateTime(LocalDateTime.now());
shopDealerUserUpdateList.add(dealerUser);
//12.新增退单分销记录
ShopDealerCapital capital = new ShopDealerCapital();
String no = orderNoUtils.generate("R");
capital.setNo(no);
capital.setUserId(k);
capital.setType(ShopDealerTypeEnum.ORDER_REFUND.getCode());
capital.setOrderNo(entity.getOrderNo());
capital.setFlowType(ShopDealerCapitalUpdateEnum.ORDER_REFUND.getType());
capital.setMoney(money.negate());
capital.setMoneyAfter(dealerUser.getMoney());
capital.setFreezeMoney(freezeMoney.negate());
capital.setFreezeMoneyAfter(dealerUser.getFreezeMoney());
capital.setComments(ShopDealerCapitalUpdateEnum.ORDER_REFUND.getDescription());
capital.setToUserId(order.getUserId());
capital.setTenantId(entity.getTenantId());
User loginUser = LoginUserUtil.getLoginUser();
if(loginUser != null){
capital.setCreator(loginUser.getUserId());
}
capital.setCreateTime(LocalDateTime.now());
newCapitalList.add(capital);
}
});
//13.批量修改分销用户钱包余额
if(CollectionUtils.isNotEmpty(shopDealerUserUpdateList)){
updateBatchById(shopDealerUserUpdateList);
}
//14.批量生成分销记录数据
if(CollectionUtils.isNotEmpty(newCapitalList)){
shopDealerCapitalService.saveBatch(newCapitalList);
}
return Boolean.TRUE;
}else {
return Boolean.TRUE;
}
}
public ShopDealerUser getDealerUser(Integer userId){
ShopDealerUser shopDealerUser = baseMapper.selectOne(new LambdaQueryWrapper<ShopDealerUser>().eq(ShopDealerUser::getUserId, userId));
if(shopDealerUser == null){

View File

@@ -18,6 +18,11 @@ spring:
host: localhost
port: 6379
# database: 0
# host: 8.134.55.105
# port: 16379
# password: redis_t74P8C
# 日志配置
logging:
level: