result = convertPage02(pageResult);
- for (AppBrokerageWithdrawRespVO vo : result.getList()) {
- vo.setStatusName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS, vo.getStatus()));
- }
- return result;
- }
-
- BrokerageWithdrawPageReqVO convert(AppBrokerageWithdrawPageReqVO pageReqVO, Long userId);
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java
index f31c238001..0b996eadb4 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java
@@ -57,13 +57,25 @@ public class BrokerageWithdrawDO extends BaseDO {
private Integer type;
/**
- * 真实姓名
+ * 提现姓名
+ * 1. {@link BrokerageWithdrawTypeEnum#BANK}:银行开户人
+ * 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}:微信真名
+ * 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}:支付宝真名
*/
- private String name;
+ private String userName;
/**
- * 账号
+ * 提现账号
+ * 1. {@link BrokerageWithdrawTypeEnum#BANK}:银行账号
+ * 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}:微信 openid
+ * 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}:支付宝账号
*/
- private String accountNo;
+ private String userAccount;
+
+ /**
+ * 收款码
+ */
+ private String qrCodeUrl;
+
/**
* 银行名称
*/
@@ -72,10 +84,7 @@ public class BrokerageWithdrawDO extends BaseDO {
* 开户地址
*/
private String bankAddress;
- /**
- * 收款码
- */
- private String accountQrCodeUrl;
+
/**
* 状态
*
@@ -95,4 +104,27 @@ public class BrokerageWithdrawDO extends BaseDO {
*/
private String remark;
+ // ========== 转账相关字段 ==========
+
+ /**
+ * 转账单编号
+ *
+ * 关联 {@link cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO#getId()}
+ */
+ private Long payTransferId;
+ /**
+ * 转账渠道
+ *
+ * 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum}
+ */
+ private String transferChannelCode;
+ /**
+ * 转账成功时间
+ */
+ private LocalDateTime transferTime;
+ /**
+ * 转账错误提示
+ */
+ private String transferErrorMsg;
+
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
index 1942cc42bb..71828f7c08 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
@@ -27,18 +27,18 @@ public interface BrokerageWithdrawMapper extends BaseMapperX()
.eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId())
.eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType())
- .likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName())
- .eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo())
+ .likeIfPresent(BrokerageWithdrawDO::getUserName, reqVO.getUserName())
+ .likeIfPresent(BrokerageWithdrawDO::getUserAccount, reqVO.getUserAccount())
.likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName())
.eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus())
.betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime())
- .orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
+ .orderByDesc(BrokerageWithdrawDO::getId));
}
- default int updateByIdAndStatus(Long id, Integer status, BrokerageWithdrawDO updateObj) {
+ default int updateByIdAndStatus(Long id, Integer whereStatus, BrokerageWithdrawDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper()
.eq(BrokerageWithdrawDO::getId, id)
- .eq(BrokerageWithdrawDO::getStatus, status));
+ .eq(BrokerageWithdrawDO::getStatus, whereStatus));
}
default List selectCountAndSumPriceByUserIdAndStatus(Collection userIds,
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
index f7b3f7e90f..c56f33afbe 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
@@ -1,18 +1,20 @@
package cn.iocoder.yudao.module.trade.framework.aftersale.core.aop;
+import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderLogDO;
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
import cn.iocoder.yudao.module.trade.framework.aftersale.core.annotations.AfterSaleLog;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
import cn.iocoder.yudao.module.trade.service.aftersale.bo.AfterSaleLogCreateReqBO;
+import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
-import javax.annotation.Resource;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@@ -50,6 +52,10 @@ public class AfterSaleLogAspect {
* 操作后的状态
*/
private static final ThreadLocal AFTER_STATUS = new ThreadLocal<>();
+ /**
+ * 操作类型(仅“动态场景”需要使用)
+ */
+ private static final ThreadLocal OPERATE_TYPE = new ThreadLocal<>();
/**
* 拓展参数 Map,用于格式化操作内容
*/
@@ -69,6 +75,7 @@ public class AfterSaleLogAspect {
if (afterSaleId == null) { // 如果未设置,只有注解,说明不需要记录日志
return;
}
+ AfterSaleOperateTypeEnum operateType = ObjUtil.defaultIfNull(OPERATE_TYPE.get(), afterSaleLog.operateType());
Integer beforeStatus = BEFORE_STATUS.get();
Integer afterStatus = AFTER_STATUS.get();
Map exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap());
@@ -78,7 +85,7 @@ public class AfterSaleLogAspect {
AfterSaleLogCreateReqBO createBO = new AfterSaleLogCreateReqBO()
.setUserId(userId).setUserType(userType)
.setAfterSaleId(afterSaleId).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus)
- .setOperateType(afterSaleLog.operateType().getType()).setContent(content);
+ .setOperateType(operateType.getType()).setContent(content);
afterSaleLogService.createAfterSaleLog(createBO);
} catch (Exception exception) {
log.error("[doAfterReturning][afterSaleLog({}) 日志记录错误]", toJsonString(afterSaleLog), exception);
@@ -116,6 +123,10 @@ public class AfterSaleLogAspect {
EXTS.set(exts);
}
+ public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+ OPERATE_TYPE.set(operateType);
+ }
+
public static void setUserInfo(Long userId, Integer userType) {
USER_ID.set(userId);
USER_TYPE.set(userType);
@@ -127,6 +138,7 @@ public class AfterSaleLogAspect {
AFTER_SALE_ID.remove();
BEFORE_STATUS.remove();
AFTER_STATUS.remove();
+ OPERATE_TYPE.remove();
EXTS.remove();
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java
index 3f9fc5d74b..14f97547bf 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.framework.aftersale.core.utils;
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
import cn.iocoder.yudao.module.trade.framework.aftersale.core.aop.AfterSaleLogAspect;
import java.util.Map;
@@ -13,6 +14,10 @@ import java.util.Map;
*/
public class AfterSaleLogUtils {
+ public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+ AfterSaleLogAspect.setAfterSaleOperateType(operateType);
+ }
+
public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus) {
setAfterSaleInfo(id, beforeStatus, afterStatus, null);
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
index 486a68b7c0..d2c0c3c849 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
@@ -108,6 +108,15 @@ public interface AfterSaleService {
*/
void refundAfterSale(Long userId, String userIp, Long id);
+ /**
+ * 更新售后订单为已退款
+ *
+ * @param id 售后编号
+ * @param orderId 订单编号
+ * @param payRefundId 支付退款编号
+ */
+ void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId);
+
/**
* 【会员】取消售后
*
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
index dde7abc4bb..be57920559 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
@@ -40,14 +42,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
@@ -184,8 +185,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
// 记录售后日志
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), null,
AfterSaleStatusEnum.APPLY.getStatus());
-
- // TODO 发送售后消息
return afterSale;
}
@@ -206,8 +205,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
// 记录售后日志
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
-
- // TODO 发送售后消息
}
@Override
@@ -226,8 +223,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
// 记录售后日志
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
- // TODO 发送售后消息
-
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
@@ -281,8 +276,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(),
MapUtil.builder().put("deliveryName", express.getName())
.put("logisticsNo", deliveryReqVO.getLogisticsNo()).build());
-
- // TODO 发送售后消息
}
@Override
@@ -299,8 +292,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
// 记录售后日志
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
AfterSaleStatusEnum.WAIT_REFUND.getStatus());
-
- // TODO 发送售后消息
}
@Override
@@ -326,8 +317,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
AfterSaleStatusEnum.SELLER_REFUSE.getStatus(),
MapUtil.of("reason", refuseReqVO.getRefuseMemo()));
- // TODO 发送售后消息
-
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
@@ -365,33 +354,90 @@ public class AfterSaleServiceImpl implements AfterSaleService {
// 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起
createPayRefund(userIp, afterSale);
- // 更新售后单的状态为【已完成】
- updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
- .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
-
// 记录售后日志
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
- AfterSaleStatusEnum.COMPLETE.getStatus());
-
- // TODO 发送售后消息
-
- // 更新交易订单项的售后状态为【已完成】
- tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+ afterSale.getStatus()); // 特殊:这里状态不变,而是最终 updateAfterSaleRefunded 处理!!!
}
private void createPayRefund(String userIp, AfterSaleDO afterSale) {
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+ // 创建退款单
+ PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
+ .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
+ Long payRefundId = payRefundApi.createRefund(createReqDTO);
- @Override
- public void afterCommit() {
- // 创建退款单
- PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
- .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
- Long payRefundId = payRefundApi.createRefund(createReqDTO);
- // 更新售后单的退款单号
- tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
- }
- });
+ // 更新售后单的退款单号
+ tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.SYSTEM_REFUND_SUCCESS)
+ public void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId) {
+ // 1. 校验售后单的状态,并状态待退款
+ AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
+ if (afterSale == null) {
+ throw exception(AFTER_SALE_NOT_FOUND);
+ }
+ if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.WAIT_REFUND.getStatus())) {
+ throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND);
+ }
+
+ // 2. 校验退款单
+ PayRefundRespDTO payRefund = validatePayRefund(afterSale, payRefundId);
+
+ // 3. 处理退款结果
+ if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+ // 【情况一:退款成功】
+ updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
+ .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
+
+ // 记录售后日志
+ AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), AfterSaleStatusEnum.COMPLETE.getStatus());
+
+ // 更新交易订单项的售后状态为【已完成】
+ tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+ // 【情况二:退款失败】
+ } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+ // 记录售后日志
+ AfterSaleLogUtils.setAfterSaleOperateType(AfterSaleOperateTypeEnum.SYSTEM_REFUND_FAIL);
+ AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), afterSale.getStatus());
+ }
+ }
+
+ /**
+ * 校验退款单的合法性
+ *
+ * @param afterSale 售后单
+ * @param payRefundId 退款单编号
+ * @return 退款单
+ */
+ private PayRefundRespDTO validatePayRefund(AfterSaleDO afterSale, Long payRefundId) {
+ // 1. 校验退款单是否存在
+ PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+ if (payRefund == null) {
+ log.error("[validatePayRefund][afterSale({}) payRefund({}) 不存在,请进行处理!]", afterSale.getId(), payRefundId);
+ throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND);
+ }
+ // 2.1 校验退款单无退款结果(成功、失败)
+ if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())
+ && !PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+ log.error("[validatePayRefund][afterSale({}) payRefund({}) 无退款结果,请进行处理!payRefund 数据是:{}]",
+ afterSale.getId(), payRefundId, toJsonString(payRefund));
+ throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE);
+ }
+ // 2.2 校验退款金额一致
+ if (ObjectUtil.notEqual(payRefund.getRefundPrice(), afterSale.getRefundPrice())) {
+ log.error("[validatePayRefund][afterSale({}) payRefund({}) 退款金额不匹配,请进行处理!afterSale 数据是:{},payRefund 数据是:{}]",
+ afterSale.getId(), payRefundId, toJsonString(afterSale), toJsonString(payRefund));
+ throw exception(AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+ }
+ // 2.3 校验退款订单匹配(二次)
+ if (ObjectUtil.notEqual(payRefund.getMerchantRefundId(), afterSale.getId().toString())) {
+ log.error("[validatePayRefund][afterSale({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
+ afterSale.getId(), payRefundId, toJsonString(payRefund));
+ throw exception(AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+ }
+ return payRefund;
}
@Override
@@ -417,8 +463,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
AfterSaleStatusEnum.BUYER_CANCEL.getStatus());
- // TODO 发送售后消息
-
// 更新交易订单项的售后状态为【未申请】
tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
}
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
index 6ffb5ebd62..a873927f6c 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
@@ -1,25 +1,24 @@
package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
+import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
+import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
-import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
-import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
-import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
-import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
-import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
-import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
-import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
@@ -29,21 +28,24 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import com.google.common.base.Objects;
+import jakarta.annotation.Resource;
+import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
-import javax.annotation.Resource;
-import javax.validation.Validator;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
@@ -64,13 +66,9 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Resource
private TradeConfigService tradeConfigService;
- @Resource
- private NotifyMessageSendApi notifyMessageSendApi;
@Resource
private PayTransferApi payTransferApi;
@Resource
- private SocialUserApi socialUserApi;
- @Resource
private PayWalletApi payWalletApi;
@Resource
@@ -84,15 +82,24 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) {
// 1.1 校验存在
BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
+ // 1.2 特殊:【重新转账】如果是提现失败,并且状态是审核中,那么更新状态为审核中,并且清空 transferErrorMsg
+ if (BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus().equals(withdraw.getStatus())) {
+ int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(),
+ new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg(""));
+ if (updateCount == 0) {
+ throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
+ }
+ withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg("");
+ }
// 1.2 校验状态为审核中
if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
}
// 2. 更新状态
- int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(),
+ int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(),
new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
- if (rows == 0) {
+ if (updateCount == 0) {
throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
}
@@ -109,44 +116,48 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
}
private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) {
- // 1.1 钱包
- if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
- payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO()
- .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
- .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
- .setPrice(withdraw.getPrice()));
- // 1.2 微信 API
- } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
- // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
- Long payTransferId = createPayTransfer(withdraw);
- // 1.3 剩余类型,都是手动打款,所以不处理
- } else {
- // TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款
- log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType());
+ // 情况一:通过 API 转账
+ if (BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
+ createPayTransfer(withdraw);
+ return;
}
- // 2. 非支付 API,则直接体现成功
- if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
- brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
- new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
- }
+ // 情况二:非 API 转账(手动打款)
+ brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
+ new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
}
- private Long createPayTransfer(BrokerageWithdrawDO withdraw) {
- // 1.1 获取微信 openid
- SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
- UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
- // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈;
+ private void createPayTransfer(BrokerageWithdrawDO withdraw) {
+ // 1.1 获取基础信息
+ String userAccount = withdraw.getUserAccount();
+ String userName = withdraw.getUserName();
+ String channelCode = null;
+ Map channelExtras = null;
+ if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.ALIPAY_API.getType())) {
+ channelCode = PayChannelEnum.ALIPAY_PC.getCode();
+ } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())) {
+ channelCode = withdraw.getTransferChannelCode();
+ userAccount = withdraw.getUserAccount();
+ // 特殊:微信需要有报备信息
+ channelExtras = PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("佣金提现", "佣金提现");
+ } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WALLET.getType())) {
+ PayWalletRespDTO wallet = payWalletApi.getOrCreateWallet(withdraw.getUserId(), UserTypeEnum.MEMBER.getValue());
+ Assert.notNull(wallet, "钱包不存在");
+ channelCode = PayChannelEnum.WALLET.getCode();
+ userAccount = wallet.getId().toString();
+ }
// 1.2 构建请求
- PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()
- .setAppKey(tradeOrderProperties.getPayAppKey())
- .setChannelCode("wx_lite").setType(PayTransferTypeEnum.WX_BALANCE.getType())
- .setMerchantTransferId(withdraw.getId().toString())
- .setPrice(withdraw.getPrice())
- .setSubject("佣金提现")
- .setOpenid(socialUser.getOpenid()).setUserIp(getClientIP());
- // 2. 发起请求
- return payTransferApi.createTransfer(payTransferCreateReqDTO);
+ PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
+ .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode)
+ .setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
+ .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP())
+ .setChannelExtras(channelExtras);
+ // 1.3 发起请求
+ PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO);
+
+ // 2. 更新提现记录
+ brokerageWithdrawMapper.updateById(new BrokerageWithdrawDO().setId(withdraw.getId())
+ .setPayTransferId(transferRespDTO.getId()).setTransferChannelCode(channelCode));
}
private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
@@ -178,7 +189,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
// 2.1 计算手续费
Integer feePrice = calculateFeePrice(createReqVO.getPrice(), tradeConfig.getBrokerageWithdrawFeePercent());
// 2.2 创建佣金提现记录
- BrokerageWithdrawDO withdraw = BrokerageWithdrawConvert.INSTANCE.convert(createReqVO, userId, feePrice);
+ BrokerageWithdrawDO withdraw = BeanUtils.toBean(createReqVO, BrokerageWithdrawDO.class)
+ .setUserId(userId).setFeePrice(feePrice);
brokerageWithdrawMapper.insert(withdraw);
// 3. 创建用户佣金记录
@@ -220,22 +232,71 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) {
- BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
- PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId);
- // TODO @luchi:建议参考支付那,即使成功的情况下,也要各种校验;金额是否匹配、转账单号是否匹配、是否重复调用;
- if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) {
- withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
- // TODO @luchi:发送站内信
- } else if (PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) {
- // TODO @luchi:这里,是不是不用更新哈?
- withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
- } else {
- withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
- // 3.2 驳回时需要退还用户佣金
- brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
- String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
+ // 1.1 校验提现单是否存在
+ BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
+ if (withdraw == null) {
+ log.error("[updateBrokerageWithdrawTransferred][withdraw({}) payTransfer({}) 不存在提现单,请进行处理!]", id, payTransferId);
+ throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
}
- brokerageWithdrawMapper.updateById(withdraw);
+ // 1.2 校验提现单已经结束(成功或失败)
+ if (ObjectUtils.equalsAny(withdraw.getStatus(), BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus(),
+ BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus())) {
+ // 特殊:转账单编号相同,直接返回,说明重复回调
+ if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
+ log.warn("[updateBrokerageWithdrawTransferred][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId);
+ return;
+ }
+ // 异常:转账单编号不同,说明转账单编号错误
+ log.error("[updateBrokerageWithdrawTransferred][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
+ throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
+ }
+
+ // 2. 校验转账单的合法性
+ PayTransferRespDTO payTransfer = validateBrokerageTransferStatusCanUpdate(withdraw, payTransferId);
+
+ // 3. 更新提现单状态
+ Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus() :
+ PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus() : null;
+ Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());
+ brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
+ new BrokerageWithdrawDO().setStatus(newStatus)
+ .setTransferTime(payTransfer.getSuccessTime())
+ .setTransferErrorMsg(payTransfer.getChannelErrorMsg()));
+ }
+
+ private PayTransferRespDTO validateBrokerageTransferStatusCanUpdate(BrokerageWithdrawDO withdraw, Long payTransferId) {
+ // 1. 校验转账单是否存在
+ PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId);
+ if (payTransfer == null) {
+ log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId);
+ throw exception(PAY_TRANSFER_NOT_FOUND);
+ }
+
+ // 2.1 校验转账单已成功或关闭
+ if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
+ log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束,请进行处理!payTransfer 数据是:{}]",
+ withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
+ throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
+ }
+ // 2.2 校验转账金额一致
+ if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) {
+ log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
+ withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
+ throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
+ }
+ // 2.3 校验转账订单匹配
+ if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
+ log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
+ withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
+ throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
+ }
+ // 2.4 校验转账渠道一致
+ if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) {
+ log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]",
+ withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
+ throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH);
+ }
+ return payTransfer;
}
@Override
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
index cd1cee7267..bec67ef12c 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
@@ -209,6 +209,14 @@ public interface TradeOrderUpdateService {
*/
void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
+ /**
+ * 取消支付订单的退款回调
+ *
+ * @param id 订单编号
+ * @param payRefundId 支付退款编号
+ */
+ void updatePaidOrderRefunded(Long id, Long payRefundId);
+
/**
* 更新下单赠送的优惠券编号到订单
*
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
index ce7830e4ce..75b3a547df 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -20,7 +20,9 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@@ -280,7 +282,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
TradeOrderDO order = validateOrderExists(id);
// 1.2 校验订单已支付
if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
- // 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
+ // 特殊:支付单号相同,直接返回,说明重复回调
if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) {
log.warn("[updateOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId);
return;
@@ -938,10 +940,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
.setAppKey(tradeOrderProperties.getPayAppKey()) // 支付应用
.setUserIp(NetUtil.getLocalhostStr()) // 使用本机 IP,因为是服务器发起退款的
.setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
- .setMerchantRefundId(String.valueOf(order.getId()))
+ // 特殊:因为订单支持 AfterSale 单个售后退款,也支持整单退款,所以需要通过 order- 进行下区分
+ // 具体可见 AfterSaleController 的 updateAfterSaleRefunded 方法
+ .setMerchantRefundId("order-" + order.getId())
.setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息
}
+ @Override
+ public void updatePaidOrderRefunded(Long id, Long payRefundId) {
+ PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+ if (payRefund == null) {
+ throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND);
+ }
+ // 特殊:因为在 cancelPaidOrder 已经进行订单的取消,所以这里必须退款成功!!!
+ if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+ throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS);
+ }
+ }
+
@Override
public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) {
// 1. 检验订单存在
diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java
index 1adaf17421..07ed55aa79 100644
--- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java
+++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java
@@ -57,8 +57,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到
o.setUserId(null);
o.setType(null);
- o.setName(null);
- o.setAccountNo(null);
+ o.setUserName(null);
+ o.setUserAccount(null);
o.setBankName(null);
o.setStatus(null);
o.setCreateTime(null);
@@ -69,9 +69,9 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
// 测试 type 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null)));
// 测试 name 不匹配
- brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null)));
+ brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserName(null)));
// 测试 accountNo 不匹配
- brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null)));
+ brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserAccount(null)));
// 测试 bankName 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null)));
// 测试 status 不匹配
@@ -88,8 +88,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO();
reqVO.setUserId(null);
reqVO.setType(null);
- reqVO.setName(null);
- reqVO.setAccountNo(null);
+ reqVO.setUserName(null);
+ reqVO.setUserAccount(null);
reqVO.setBankName(null);
reqVO.setStatus(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
index 270e01b289..0dba813b83 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
@@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.pay.api.notify.dto;
-import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -26,6 +25,12 @@ public class PayRefundNotifyReqDTO {
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
+ /**
+ * 商户退款编号
+ */
+ @NotEmpty(message = "商户退款编号不能为空")
+ private String merchantRefundId;
+
/**
* 支付退款编号
*/
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
index e79e686394..f3a92d8da9 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
@@ -1,9 +1,12 @@
package cn.iocoder.yudao.module.pay.api.notify.dto;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Data;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.NoArgsConstructor;
/**
* 转账单的通知 Request DTO
@@ -11,9 +14,11 @@ import javax.validation.constraints.NotNull;
* @author jason
*/
@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
public class PayTransferNotifyReqDTO {
- // TODO 芋艿:要不要改成 orderId 待定;
/**
* 商户转账单号
*/
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
index 0065cb4934..725f3911b4 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
@@ -42,9 +42,24 @@ public class PayRefundRespDTO {
* 商户订单编号
*/
private String merchantOrderId;
+ /**
+ * 商户退款编号
+ */
+ private String merchantRefundId;
/**
* 退款成功时间
*/
private LocalDateTime successTime;
+ // ========== 渠道相关字段 ==========
+
+ /**
+ * 调用渠道的错误码
+ */
+ private String channelErrorCode;
+ /**
+ * 调用渠道的错误提示
+ */
+ private String channelErrorMsg;
+
}
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
index 29ab90ab00..e3227ebe09 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
@@ -1,9 +1,9 @@
package cn.iocoder.yudao.module.pay.api.transfer;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
-
-import javax.validation.Valid;
+import jakarta.validation.Valid;
/**
* 转账单 API 接口
@@ -16,9 +16,9 @@ public interface PayTransferApi {
* 创建转账单
*
* @param reqDTO 创建请求
- * @return 转账单编号
+ * @return 创建结果
*/
- Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
+ PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
/**
* 获得转账单
diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
index 92d51cdcd2..1ac5c04af6 100644
--- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
+++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
@@ -1,13 +1,15 @@
package cn.iocoder.yudao.module.pay.api.transfer.dto;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
import lombok.Data;
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -24,6 +26,9 @@ public class PayTransferCreateReqDTO {
@NotNull(message = "应用标识不能为空")
private String appKey;
+ /**
+ * 转账渠道
+ */
@NotEmpty(message = "转账渠道不能为空")
private String channelCode;
@@ -32,17 +37,12 @@ public class PayTransferCreateReqDTO {
*/
private Map channelExtras;
+ /**
+ * 用户 IP
+ */
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
- /**
- * 类型
- */
- @NotNull(message = "转账类型不能为空")
- @InEnum(PayTransferTypeEnum.class)
- private Integer type;
-
-
/**
* 商户转账单编号
*/
@@ -62,16 +62,59 @@ public class PayTransferCreateReqDTO {
@NotEmpty(message = "转账标题不能为空")
private String subject;
+ /**
+ * 收款人账号
+ *
+ * 微信场景下:openid
+ * 支付宝场景下:支付宝账号
+ */
+ @NotEmpty(message = "收款人账号不能为空")
+ private String userAccount;
/**
* 收款人姓名
*/
- @NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class})
private String userName;
- @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class})
- private String alipayLogonId;
+ /**
+ * 【微信】现金营销场景
+ *
+ * @param activityName 活动名称
+ * @param rewardDescription 奖励说明
+ * @return channelExtras
+ */
+ public static Map buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
+ return buildWeiXinChannelExtra(1000,
+ "活动名称", activityName,
+ "奖励说明", rewardDescription);
+ }
+
+ /**
+ * 【微信】企业报销场景
+ *
+ * @param expenseType 报销类型
+ * @param expenseDescription 报销说明
+ * @return channelExtras
+ */
+ public static Map buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
+ return buildWeiXinChannelExtra(1006,
+ "报销类型", expenseType,
+ "报销说明", expenseDescription);
+ }
+
+ private static Map buildWeiXinChannelExtra(Integer sceneId, String... values) {
+ Map channelExtras = new HashMap<>();
+ // 构建场景报备信息列表
+ List