reactor:【PAY 支付】优化退款回调接口,增加 merchantRefundId 退款编号参数

reactor:【MALL 商城】售后退款时,基于回调处理
This commit is contained in:
YunaiV 2025-05-10 16:43:55 +08:00
parent fe8871b5f1
commit 288d8e3132
41 changed files with 356 additions and 244 deletions

View File

@ -36,8 +36,10 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_034, "交易订单自提失败,原因:你没有核销该门店订单的权限");
ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_035, "交易订单创建失败,原因:用户积分不足");
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_034, "交易订单更新支付订单退款状态失败,原因:退款单不存在");
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_035, "交易订单更新支付订单退款状态失败,原因:退款单状态不是【退款成功】");
ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_036, "交易订单自提失败,原因:你没有核销该门店订单的权限");
ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_037, "交易订单创建失败,原因:用户积分不足");
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
@ -51,9 +53,13 @@ public interface ErrorCodeConstants {
ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】");
ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】");
ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_111, "退款失败,退款单不存在");
ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE = new ErrorCode(1_011_000_112, "退款失败,退款单未退款");
ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_011_000_113, "退款失败,退款金额不匹配");
ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_011_000_114, "退款失败,退款单不匹配");
ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后");
new ErrorCode(1_011_000_115, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_116, "订单拼团中,无法申请售后");
// ========== Cart 模块 1-011-002-000 ==========
ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
@ -101,7 +107,6 @@ public interface ErrorCodeConstants {
ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中");
ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元");
ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元");
ErrorCode BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND = new ErrorCode(1_011_008_004, "提现失败,原因:用户未绑定微信");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_011_008_005, "提现单更新转账状态失败,转账单不匹配");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_011_008_006, "提现单更新转账状态失败,转账单状态不是成功或关闭状态");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_011_008_007, "提现单更新转账状态失败,转账单金额不匹配");

View File

@ -19,7 +19,9 @@ public enum AfterSaleOperateTypeEnum {
MEMBER_DELIVERY(20, "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"),
ADMIN_AGREE_RECEIVE(21, "商家收货"),
ADMIN_DISAGREE_RECEIVE(22, "商家拒绝收货,原因:{reason}"),
ADMIN_REFUND(30, "商家退款"),
ADMIN_REFUND(30, "商家发起退款"),
SYSTEM_REFUND_SUCCESS(31, "退款成功"),
SYSTEM_REFUND_FAIL(32, "退款失败"),
MEMBER_CANCEL(40, "会员取消退款"),
;

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.controller.admin.aftersale;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@ -15,10 +16,12 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -46,6 +49,8 @@ public class AfterSaleController {
@Resource
private TradeOrderQueryService tradeOrderQueryService;
@Resource
private TradeOrderUpdateService tradeOrderUpdateService;
@Resource
private AfterSaleLogService afterSaleLogService;
@Resource
private MemberUserApi memberUserApi;
@ -133,11 +138,19 @@ public class AfterSaleController {
@PostMapping("/update-refunded")
@Operation(summary = "更新售后订单为已退款") // pay-module 支付服务进行回调可见 PayNotifyJob
@PermitAll // 无需登录安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateAfterRefund(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
// 目前业务逻辑不需要做任何事情
// 当然退款会有小概率会失败的情况可以监控失败状态进行告警
@PermitAll // 无需登录安全由 AfterSaleService 内部校验实现
public CommonResult<Boolean> updateAfterSaleRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
if (StrUtil.startWithAny(notifyReqDTO.getMerchantRefundId(), "order-")) {
tradeOrderUpdateService.updatePaidOrderRefunded(
Long.parseLong(notifyReqDTO.getMerchantRefundId()),
notifyReqDTO.getPayRefundId());
} else {
afterSaleService.updateAfterSaleRefunded(
Long.parseLong(notifyReqDTO.getMerchantRefundId()),
Long.parseLong(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayRefundId());
}
return success(true);
}

View File

@ -88,7 +88,7 @@ public class BrokerageWithdrawController {
public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
brokerageWithdrawService.updateBrokerageWithdrawTransferred(
Long.parseLong(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayTransferId());
Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
return success(true);
}

View File

@ -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 jakarta.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<Integer> AFTER_STATUS = new ThreadLocal<>();
/**
* 操作类型动态场景需要使用
*/
private static final ThreadLocal<AfterSaleOperateTypeEnum> 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<String, Object> 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();
}

View File

@ -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);
}

View File

@ -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);
/**
* 会员取消售后
*

View File

@ -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;
@ -41,13 +43,12 @@ 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 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.<String, Object>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() {
@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));
}
});
@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());
}

View File

@ -15,9 +15,6 @@ import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
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.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;
@ -71,8 +68,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Resource
private PayTransferApi payTransferApi;
@Resource
private SocialUserApi socialUserApi;
@Resource
private PayWalletApi payWalletApi;
@Resource
@ -140,13 +135,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.ALIPAY_API.getType())) {
channelCode = PayChannelEnum.ALIPAY_PC.getCode();
} else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())) {
SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
if (socialUser == null) {
throw exception(BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND);
}
channelCode = PayChannelEnum.WX_LITE.getCode();
userAccount = socialUser.getOpenid();
userAccount = withdraw.getUserAccount();
// 特殊微信需要有报备信息
channelExtras = PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("佣金提现", "佣金提现");
} else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WALLET.getType())) {
@ -158,7 +148,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
// 1.2 构建请求
PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
.setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode)
.setMerchantOrderId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
.setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
.setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP())
.setChannelExtras(channelExtras);
// 1.3 发起请求
@ -293,7 +283,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
}
// 2.3 校验转账订单匹配
if (ObjectUtil.notEqual(payTransfer.getMerchantOrderId(), withdraw.getId().toString())) {
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);

View File

@ -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);
/**
* 更新下单赠送的优惠券编号到订单
*

View File

@ -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;
@ -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<Long> giveCouponIds) {
// 1. 检验订单存在

View File

@ -25,6 +25,12 @@ public class PayRefundNotifyReqDTO {
@NotEmpty(message = "商户退款单编号不能为空")
private String merchantOrderId;
/**
* 商户退款编号
*/
@NotEmpty(message = "商户退款编号不能为空")
private String merchantRefundId;
/**
* 支付退款编号
*/

View File

@ -23,7 +23,7 @@ public class PayTransferNotifyReqDTO {
* 商户转账单号
*/
@NotEmpty(message = "商户转账单号不能为空")
private String merchantOrderId;
private String merchantTransferId;
/**
* 转账订单编号

View File

@ -42,9 +42,24 @@ public class PayRefundRespDTO {
* 商户订单编号
*/
private String merchantOrderId;
/**
* 商户退款编号
*/
private String merchantRefundId;
/**
* 退款成功时间
*/
private LocalDateTime successTime;
// ========== 渠道相关字段 ==========
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道的错误提示
*/
private String channelErrorMsg;
}

View File

@ -47,7 +47,7 @@ public class PayTransferCreateReqDTO {
* 商户转账单编号
*/
@NotEmpty(message = "商户转账单编号能为空")
private String merchantOrderId;
private String merchantTransferId;
/**
* 转账金额单位

View File

@ -31,7 +31,7 @@ public class PayTransferRespDTO {
/**
* 商户转账单编号
*/
private String merchantOrderId;
private String merchantTransferId;
// ========== 转账相关字段 ==========

View File

@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.pay.api.refund;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.convert.refund.PayRefundConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
/**
* 退款单 API 实现类
*
@ -23,12 +23,13 @@ public class PayRefundApiImpl implements PayRefundApi {
@Override
public Long createRefund(PayRefundCreateReqDTO reqDTO) {
return payRefundService.createPayRefund(reqDTO);
return payRefundService.createRefund(reqDTO);
}
@Override
public PayRefundRespDTO getRefund(Long id) {
return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
PayRefundDO refund = payRefundService.getRefund(id);
return BeanUtils.toBean(refund, PayRefundRespDTO.class);
}
}

View File

@ -66,7 +66,9 @@ public class PayDemoOrderController {
@Operation(summary = "更新示例订单为已退款") // pay-module 支付服务进行回调可见 PayNotifyJob
@PermitAll // 无需登录安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
payDemoOrderService.updateDemoOrderRefunded(
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getMerchantRefundId(),
notifyReqDTO.getPayRefundId());
return success(true);
}

View File

@ -55,7 +55,7 @@ public class PayDemoWithdrawController {
@Operation(summary = "更新示例提现单的转账状态") // pay-module 转账服务进行回调
@PermitAll // 无需登录安全由 PayDemoTransferService 内部校验实现
public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
notifyReqDTO.getPayTransferId());
return success(true);
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@ -11,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
@ -138,7 +138,12 @@ public class PayNotifyController {
// 拼接返回
PayAppDO app = appService.getApp(task.getAppId());
List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> {
if (app != null) {
respVO.setAppName(app.getName());
}
respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class));
}));
}
@GetMapping("/page")
@ -150,8 +155,15 @@ public class PayNotifyController {
return success(PageResult.empty());
}
// 拼接返回
Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
Map<Long, PayAppDO> apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
// 转换对象
return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> {
PayAppDO app = apps.get(order.getAppId());
if (app != null) {
order.setAppName(app.getName());
}
}));
}
}

View File

@ -1,45 +0,0 @@
package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 回调通知 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class PayNotifyTaskBaseVO {
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
private Long appId;
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Byte type;
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
private Long dataId;
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Byte status;
@Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26697")
private String merchantOrderId;
@Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime nextNotifyTime;
@Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime lastExecuteTime;
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte notifyTimes;
@Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte maxNotifyTimes;
@Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
private String notifyUrl;
}

View File

@ -13,19 +13,7 @@ import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "应用名称", example = "wx_pay")
private String appName;
public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO {
@Schema(description = "回调日志列表")
private List<Log> logs;

View File

@ -32,6 +32,12 @@ public class PayNotifyTaskPageReqVO extends PageParam {
@Schema(description = "商户订单编号", example = "26697")
private String merchantOrderId;
@Schema(description = "商户退款编号", example = "26697")
private String merchantRefundId;
@Schema(description = "商户转账编号", example = "26697")
private String merchantTransferId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;

View File

@ -6,17 +6,51 @@ import java.time.LocalDateTime;
@Schema(description = "管理后台 - 回调通知 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
public class PayNotifyTaskRespVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
private Long appId;
@Schema(description = "应用名称", example = "wx_pay")
private String appName;
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Byte type;
@Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
private Long dataId;
@Schema(description = "商户订单编号", example = "26697")
private String merchantOrderId;
@Schema(description = "商户退款编号", example = "26697")
private String merchantRefundId;
@Schema(description = "商户转账编号", example = "26697")
private String merchantTransferId;
@Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Byte status;
@Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime nextNotifyTime;
@Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime lastExecuteTime;
@Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte notifyTimes;
@Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
private Byte maxNotifyTimes;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@ -37,8 +37,7 @@ public class PayWalletRechargeController {
return success(true);
}
// TODO @jason发起退款 post 操作哈
@GetMapping("/refund")
@PostMapping("/refund")
@Operation(summary = "发起钱包充值退款")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
@ -51,7 +50,9 @@ public class PayWalletRechargeController {
@PermitAll // 无需登录 内部校验实现
public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
walletRechargeService.updateWalletRechargeRefunded(
Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
Long.valueOf(notifyReqDTO.getMerchantOrderId()),
Long.valueOf(notifyReqDTO.getMerchantRefundId()),
notifyReqDTO.getPayRefundId());
return success(true);
}

View File

@ -1,43 +0,0 @@
package cn.iocoder.yudao.module.pay.convert.notify;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* 支付通知 Convert
*
* @author 芋道源码
*/
@Mapper
public interface PayNotifyTaskConvert {
PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
PayNotifyTaskRespVO convert(PayNotifyTaskDO bean);
default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page, Map<Long, PayAppDO> appMap){
PageResult<PayNotifyTaskRespVO> result = convertPage(page);
result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
return result;
}
PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page);
default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List<PayNotifyLogDO> logs) {
PayNotifyTaskDetailRespVO respVO = convert(task, logs);
if (app != null) {
respVO.setAppName(app.getName());
}
return respVO;
}
PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List<PayNotifyLogDO> logs);
}

View File

@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
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.controller.admin.refund.vo.PayRefundDetailsRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO;
@ -42,8 +41,6 @@ public interface PayRefundConvert {
PayRefundDO convert(PayRefundCreateReqDTO bean);
PayRefundRespDTO convert02(PayRefundDO bean);
default List<PayRefundExcelVO> convertList(List<PayRefundDO> list, Map<Long, PayAppDO> appMap) {
return CollectionUtils.convertList(list, order -> {
PayRefundExcelVO excelVO = convertExcel(order);

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
@ -60,12 +61,21 @@ public class PayNotifyTaskDO extends TenantBaseDO {
*
* 1. {@link PayNotifyTypeEnum#ORDER} 关联 {@link PayOrderDO#getId()}
* 2. {@link PayNotifyTypeEnum#REFUND} 关联 {@link PayRefundDO#getId()}
* 3. {@link PayNotifyTypeEnum#TRANSFER} 关联 {@link PayTransferDO#getId()}
*/
private Long dataId;
/**
* 商户订单编号
*/
private String merchantOrderId;
/**
* 商户退款编号
*/
private String merchantRefundId;
/**
* 商户转账编号
*/
private String merchantTransferId;
/**
* 通知状态
*

View File

@ -63,7 +63,7 @@ public class PayTransferDO extends BaseDO {
*
* 例如说内部系统 A 的订单号需要保证每个 PayAppDO 唯一
*/
private String merchantOrderId;
private String merchantTransferId;
// ========== 转账相关字段 ==========

View File

@ -37,6 +37,8 @@ public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
.eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
.eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
.eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
.eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId())
.eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId())
.betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(PayNotifyTaskDO::getId));
}

View File

@ -28,7 +28,7 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
return selectOne(PayTransferDO::getAppId, appId,
PayTransferDO::getMerchantOrderId, merchantOrderId);
PayTransferDO::getMerchantTransferId, merchantOrderId);
}
default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
@ -36,7 +36,7 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
.eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
.eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId())
.eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode())
.eqIfPresent(PayTransferDO::getMerchantOrderId, reqVO.getMerchantOrderId())
.eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId())
.eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus())
.likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName())
.eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo())

View File

@ -59,8 +59,9 @@ public interface PayDemoOrderService {
* 更新示例订单为已退款
*
* @param id 编号
* @param refundId 退款编号
* @param payRefundId 退款订单号
*/
void updateDemoOrderRefunded(Long id, Long payRefundId);
void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId);
}

View File

@ -215,16 +215,18 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
}
@Override
public void updateDemoOrderRefunded(Long id, Long payRefundId) {
public void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId) {
// 1. 校验并获得退款订单可退款
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundId);
// 2.2 更新退款单到 demo 订单
payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
.setRefundTime(payRefund.getSuccessTime()));
}
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, String refundId, Long payRefundId) {
// 1.1 校验示例订单
// 一般来说这里应该用 refundId 来查询退款单然后再校验订单是否匹配
// 这里我们是个简单的 demo所以没有售后维权表直接使用订单 id 来查询订单
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
if (order == null) {
throw exception(DEMO_ORDER_NOT_FOUND);
@ -241,7 +243,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
if (payRefund == null) {
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
}
// 2.2
// 2.2 必须是退款成功状态
if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
}
@ -252,7 +254,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
}
// 2.4 校验退款订单匹配二次
if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
if (notEqual(payRefund.getMerchantRefundId(), id.toString() + "-refund")) {
log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({})请进行处理payRefund 数据是:{}]",
id, payRefundId, toJsonString(payRefund));
throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);

View File

@ -75,7 +75,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
// 2.1 创建支付单
PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
.setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
.setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号
.setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号
.setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息
.setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息
if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) {
@ -177,7 +177,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
}
// 2.3 校验转账订单匹配二次
if (ObjectUtil.notEqual(payTransfer.getMerchantOrderId(), withdraw.getId().toString())) {
if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({})请进行处理payTransfer 数据是:{}]",
withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);

View File

@ -31,6 +31,7 @@ import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -97,19 +98,19 @@ public class PayNotifyServiceImpl implements PayNotifyService {
PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId);
task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now())
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1);
// 补充 appId + notifyUrl 字段
// 补充 appId + notifyUrl + merchant* 字段
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断有问题直接异常
task.setAppId(order.getAppId()).
setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
task.setAppId(order.getAppId()).setNotifyUrl(order.getNotifyUrl())
.setMerchantOrderId(order.getMerchantOrderId());
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
PayRefundDO refundDO = refundService.getRefund(task.getDataId());
task.setAppId(refundDO.getAppId())
.setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
PayRefundDO refund = refundService.getRefund(task.getDataId());
task.setAppId(refund.getAppId()).setNotifyUrl(refund.getNotifyUrl())
.setMerchantOrderId(refund.getMerchantOrderId()).setMerchantRefundId(refund.getMerchantRefundId());
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
PayTransferDO transfer = transferService.getTransfer(task.getDataId());
task.setAppId(transfer.getAppId())
.setMerchantOrderId(transfer.getMerchantOrderId()).setNotifyUrl(transfer.getNotifyUrl());
task.setAppId(transfer.getAppId()).setNotifyUrl(transfer.getNotifyUrl())
.setMerchantTransferId(transfer.getMerchantTransferId());
}
// 执行插入
@ -117,10 +118,13 @@ public class PayNotifyServiceImpl implements PayNotifyService {
// 必须在事务提交后在发起任务否则 PayNotifyTaskDO 还没入库就提前回调接入的业务
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
executeNotify(task);
// 异步的原因避免阻塞当前事务无需等待结果
getSelf().executeNotifyAsync(task);
}
});
}
@ -166,7 +170,17 @@ public class PayNotifyServiceImpl implements PayNotifyService {
}
/**
* 同步执行单个支付通知
* 异步执行单个支付通知
*
* @param task 通知任务
*/
@Async
public void executeNotifyAsync(PayNotifyTaskDO task) {
executeNotify(task);
}
/**
* 加锁执行单个支付通知
*
* @param task 通知任务
*/
@ -223,9 +237,10 @@ public class PayNotifyServiceImpl implements PayNotifyService {
.payOrderId(task.getDataId()).build();
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
.merchantRefundId(task.getMerchantRefundId())
.payRefundId(task.getDataId()).build();
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
request = PayTransferNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
request = PayTransferNotifyReqDTO.builder().merchantTransferId(task.getMerchantTransferId())
.payTransferId(task.getDataId()).build();
} else {
throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));

View File

@ -62,7 +62,7 @@ public interface PayRefundService {
* @param reqDTO 退款申请信息
* @return 退款单号
*/
Long createPayRefund(PayRefundCreateReqDTO reqDTO);
Long createRefund(PayRefundCreateReqDTO reqDTO);
/**
* 渠道的退款通知

View File

@ -91,7 +91,7 @@ public class PayRefundServiceImpl implements PayRefundService {
}
@Override
public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
public Long createRefund(PayRefundCreateReqDTO reqDTO) {
// 1.1 校验 App
PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
// 1.2 校验支付订单

View File

@ -116,7 +116,7 @@ public class PayTransferServiceImpl implements PayTransferService {
}
private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO reqDTO, Long appId) {
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantOrderId());
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantTransferId());
if (transfer != null) {
// 只有转账单状态为关闭才能再次发起转账
if (!PayTransferStatusEnum.isClosed(transfer.getStatus())) {

View File

@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
/**
* 钱包充值 Service 接口
@ -16,7 +15,7 @@ public interface PayWalletRechargeService {
/**
* 创建钱包充值记录发起充值
*
* @param userId 用户 id
* @param userId 用户编号
* @param userType 用户类型
* @param createReqVO 钱包充值请求 VO
* @param userIp 用户Ip
@ -40,8 +39,8 @@ public interface PayWalletRechargeService {
/**
* 更新钱包充值成功
*
* @param id 钱包充值记录 id
* @param payOrderId 支付订单 id
* @param id 钱包充值记录编号
* @param payOrderId 支付订单编号
*/
void updateWalletRechargerPaid(Long id, Long payOrderId);
@ -56,9 +55,10 @@ public interface PayWalletRechargeService {
/**
* 更新钱包充值记录为已退款
*
* @param id 钱包充值 id
* @param id 钱包充值记录编号
* @param refundId 钱包充值退款编号实际和 id 相同
* @param payRefundId 退款单id
*/
void updateWalletRechargeRefunded(Long id, Long payRefundId);
void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId);
}

View File

@ -8,10 +8,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
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.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import jakarta.annotation.Resource;
@ -61,13 +61,15 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
private PayWalletService payWalletService;
@Resource
private PayOrderService payOrderService;
@Resource
private PayRefundService payRefundService;
// @Resource
// private PayRefundService payRefundService;
@Resource
private PayWalletRechargePackageService payWalletRechargePackageService;
@Resource
public SocialClientApi socialClientApi;
@Resource
private PayRefundApi payRefundApi;
@Resource
private PayProperties payProperties;
@ -186,7 +188,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
// 3. 创建退款单
String walletRechargeId = String.valueOf(id);
String refundId = walletRechargeId + "-refund";
Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
.setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
.setMerchantOrderId(walletRechargeId)
.setMerchantRefundId(refundId)
@ -199,18 +201,20 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWalletRechargeRefunded(Long id, Long payRefundId) {
public void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId) {
// 1.1 获取钱包充值记录
// 说明因为 id refundId 是相同的所以直接使用 id 查询即可
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
if (walletRecharge == null) {
log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
throw exception(WALLET_RECHARGE_NOT_FOUND);
}
// 1.2 校验钱包充值是否可以更新已退款
PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
PayRefundRespDTO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
// 2. 处理退款结果
PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id);
// 退款成功
// 情况一退款成功
if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
// 2.1 更新钱包余额
payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
@ -219,19 +223,19 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
.setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice())
.setRefundBonusPrice(walletRecharge.getBonusPrice());
}
// 退款失败
if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
// 情况二退款失败
} else if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
// 2.2 解冻余额
payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
updateObj.setRefundStatus(FAILURE.getStatus());
}
// 3. 更新钱包充值的退款字段
walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
}
private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
private PayRefundRespDTO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
// 1. 校验退款订单匹配
if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]",
@ -240,7 +244,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
}
// 2.1 校验退款订单
PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
if (payRefund == null) {
log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId);
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
@ -252,7 +256,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
}
// 2.3 校验退款订单商户订单是否匹配
if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) {
if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) {
log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({})请进行处理payRefund 数据是:{}]",
walletRecharge.getId(), payRefundId, toJsonString(payRefund));
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);

View File

@ -215,7 +215,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
when(appService.validPayApp(eq("demo"))).thenReturn(app);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
PAY_ORDER_NOT_FOUND);
}
@ -241,7 +241,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
}
@ -260,7 +260,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
REFUND_PRICE_EXCEED);
}
@ -283,7 +283,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
refundMapper.insert(refund);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
REFUND_PRICE_EXCEED);
}
@ -307,7 +307,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
CHANNEL_NOT_FOUND);
}
@ -339,7 +339,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
refundMapper.insert(refund);
// 调用并断言异常
assertServiceException(() -> refundService.createPayRefund(reqDTO),
assertServiceException(() -> refundService.createRefund(reqDTO),
REFUND_EXISTS);
}
@ -369,7 +369,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException());
// 调用
Long refundId = refundService.createPayRefund(reqDTO);
Long refundId = refundService.createRefund(reqDTO);
// 断言
PayRefundDO refundDO = refundMapper.selectById(refundId);
assertPojoEquals(reqDTO, refundDO);
@ -422,7 +422,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
}))).thenReturn(refundRespDTO);
// 调用
Long refundId = refundService.createPayRefund(reqDTO);
Long refundId = refundService.createRefund(reqDTO);
// 断言
PayRefundDO refundDO = refundMapper.selectById(refundId);
assertPojoEquals(reqDTO, refundDO);