重构退款逻辑,去掉退款后处理
This commit is contained in:
parent
054c237b1f
commit
08103685f1
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE `ruoyi-vue-pro`.`pay_order_extension`
|
||||
CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容' ;
|
||||
|
||||
ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
|
||||
CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ;
|
|
@ -2,19 +2,17 @@ package cn.iocoder.yudao.coreservice.modules.pay.convert.order;
|
|||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayRefundCoreConvert {
|
||||
|
||||
PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
|
||||
|
||||
PayRefundPostReqDTO convert(PayRefundUnifiedRespDTO respDTO);
|
||||
|
||||
|
||||
//TODO 太多需要处理了, 暂时不用
|
||||
@Mappings(value = {
|
||||
|
|
|
@ -42,9 +42,10 @@ public class PayRefundDO extends BaseDO {
|
|||
* 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
|
||||
* 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
|
||||
* 退款单请求号,根据规则生成
|
||||
*
|
||||
* 例如说,R202109181134287570000
|
||||
* 废弃,使用 merchantRefundNo 做退款请求号
|
||||
*/
|
||||
@Deprecated
|
||||
private String reqNo;
|
||||
|
||||
/**
|
||||
|
@ -82,7 +83,7 @@ public class PayRefundDO extends BaseDO {
|
|||
/**
|
||||
* 交易订单号,根据规则生成
|
||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
||||
* 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
|
||||
* 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no
|
||||
* 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
|
||||
* 这里对应 pay_extension 里面的 no
|
||||
* 例如说,P202110132239124200055
|
||||
|
@ -95,10 +96,20 @@ public class PayRefundDO extends BaseDO {
|
|||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。发送channel 使用 reqNo
|
||||
* 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
|
||||
* 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
|
||||
* TODO 芋艿:我理解 一个商户退款订单,可以对应多条退款记录, 因为有可能失败。但是 退款请求号 reqNo 必须唯一
|
||||
* 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
|
||||
* 使用商户退款单,作为退款请求号
|
||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
||||
* 退款请求号。
|
||||
* 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
|
||||
* 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
|
||||
* 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
|
||||
* 退款单请求号,根据规则生成
|
||||
* 例如说,R202109181134287570000
|
||||
*
|
||||
*/
|
||||
private String merchantRefundNo;
|
||||
|
@ -151,6 +162,9 @@ public class PayRefundDO extends BaseDO {
|
|||
*/
|
||||
private String channelOrderNo;
|
||||
/**
|
||||
* 微信中的 refund_id
|
||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
|
||||
* 支付宝没有
|
||||
* 渠道退款单号,渠道返回
|
||||
*/
|
||||
private String channelRefundNo;
|
||||
|
|
|
@ -15,4 +15,8 @@ public interface PayRefundCoreMapper extends BaseMapperX<PayRefundDO> {
|
|||
default PayRefundDO selectByReqNo(String reqNo) {
|
||||
return selectOne("req_no", reqNo);
|
||||
}
|
||||
|
||||
default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
|
||||
return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,9 +46,10 @@ public interface PayErrorCodeCoreConstants {
|
|||
ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
|
||||
ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
|
||||
ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
|
||||
ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006003, "未找到对应的退款后置处理类");
|
||||
ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
|
||||
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
|
||||
|
||||
|
||||
/**
|
||||
* ========== 支付商户信息 1-007-004-000 ==========
|
||||
*/
|
||||
|
|
|
@ -9,10 +9,6 @@ public enum PayRefundStatusEnum {
|
|||
CREATE(0, "退款订单生成"),
|
||||
SUCCESS(1, "退款成功"),
|
||||
FAILURE(2, "退款失败"),
|
||||
PROCESSING_NOTIFY(3,"退款中, 渠道通知结果"),
|
||||
PROCESSING_QUERY(4,"退款中, 系统查询结果"),
|
||||
UNKNOWN_RETRY(5,"状态未知,需要重试"),
|
||||
UNKNOWN_QUERY(6,"状态未知,系统查询结果"),
|
||||
CLOSE(99, "退款关闭");
|
||||
|
||||
private final Integer status;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
|
||||
/**
|
||||
* 支付退款订单渠道返回后 , 后置处理抽象类, 处理公用的逻辑
|
||||
* @author jason
|
||||
*/
|
||||
public abstract class PayRefundAbstractChannelPostHandler implements PayRefundChannelPostHandler {
|
||||
|
||||
private final PayOrderCoreMapper payOrderCoreMapper;
|
||||
private final PayRefundCoreMapper payRefundMapper;
|
||||
|
||||
public PayRefundAbstractChannelPostHandler(PayOrderCoreMapper payOrderCoreMapper,
|
||||
PayRefundCoreMapper payRefundMapper){
|
||||
this.payOrderCoreMapper = payOrderCoreMapper;
|
||||
this.payRefundMapper = payRefundMapper;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新退款单
|
||||
* @param refundDO 需要更新的退款单信息
|
||||
*/
|
||||
protected void updatePayRefund(PayRefundDO refundDO){
|
||||
payRefundMapper.updateById(refundDO);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新原始支付订单
|
||||
* @param payOrderDO 支付订单信息
|
||||
*/
|
||||
protected void updatePayOrder(PayOrderDO payOrderDO){
|
||||
payOrderCoreMapper.updateById(payOrderDO);
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
|
||||
/**
|
||||
* 支付退款订单 ,渠道返回后 后置处理
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
public interface PayRefundChannelPostHandler {
|
||||
|
||||
/**
|
||||
* 支持的渠道返回值
|
||||
*
|
||||
* @return 支持的渠道返回值数组
|
||||
*/
|
||||
PayChannelRespEnum[] supportHandleResp();
|
||||
|
||||
/**
|
||||
* 根据渠道返回,处理支付退款单
|
||||
*
|
||||
* @param req 退款后置处理请求
|
||||
*/
|
||||
void handleRefundChannelResp(PayRefundPostReqDTO req);
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 退款后置处理请求 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundPostReqDTO {
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的通用返回结果
|
||||
*/
|
||||
private PayChannelRespEnum respEnum;
|
||||
|
||||
|
||||
|
||||
private PayRefundTypeEnum refundTypeEnum;
|
||||
|
||||
/**
|
||||
* 已退款的总金额
|
||||
*/
|
||||
private Long refundedAmount;
|
||||
|
||||
/**
|
||||
* 本次退款金额
|
||||
*/
|
||||
private Long refundAmount;
|
||||
|
||||
/**
|
||||
* 已退款次数
|
||||
*/
|
||||
private Integer refundedTimes;
|
||||
|
||||
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 退款单编号
|
||||
*/
|
||||
private Long refundId;
|
||||
|
||||
|
||||
/**
|
||||
* 渠道退款单号
|
||||
*/
|
||||
private String channelRefundNo;
|
||||
|
||||
|
||||
/**
|
||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
|
||||
* 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
|
||||
*/
|
||||
private String payTradeNo;
|
||||
|
||||
|
||||
/**
|
||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
||||
* 退款请求单号 同一退款请求单号多次请求只退一笔。
|
||||
*/
|
||||
private String refundReqNo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 调用异常错误信息
|
||||
*/
|
||||
private String exceptionMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的错误码
|
||||
*/
|
||||
private String channelErrCode;
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的错误描述
|
||||
*/
|
||||
private String channelErrMsg;
|
||||
|
||||
|
||||
}
|
|
@ -16,6 +16,23 @@ import lombok.experimental.Accessors;
|
|||
@AllArgsConstructor
|
||||
public class PayRefundRespDTO {
|
||||
|
||||
/**
|
||||
* 渠道返回结果
|
||||
* 退款处理中和退款成功 返回 1
|
||||
* 失败和其他情况 返回 2
|
||||
*/
|
||||
private Integer channelReturnResult;
|
||||
|
||||
/**
|
||||
* 渠道返回code
|
||||
*/
|
||||
private String channelReturnCode;
|
||||
|
||||
/**
|
||||
* 渠道返回消息
|
||||
*/
|
||||
private String channelReturnMsg;
|
||||
|
||||
/**
|
||||
* 支付退款单编号, 自增
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayRefundCoreConvert;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
|
@ -14,7 +13,6 @@ import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
|||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
|
||||
|
@ -23,21 +21,18 @@ import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
|||
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -65,25 +60,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
/**
|
||||
* 处理渠道返回结果的后置处理器 集合
|
||||
*/
|
||||
@Resource
|
||||
private List<PayRefundChannelPostHandler> handlerList;
|
||||
|
||||
private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandlers = new EnumMap<>(PayChannelRespEnum.class);
|
||||
|
||||
@PostConstruct
|
||||
public void init(){
|
||||
if (Objects.nonNull(handlerList)) {
|
||||
handlerList.forEach(handler -> {
|
||||
for (PayChannelRespEnum item : handler.supportHandleResp()) {
|
||||
mapHandlers.put(item, handler);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
|
||||
|
@ -106,15 +82,35 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
|
||||
// 校验退款的条件
|
||||
validatePayRefund(req, order);
|
||||
|
||||
// 退款类型
|
||||
PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
|
||||
if (Objects.equals(req.getAmount(), order.getAmount())) {
|
||||
refundType = PayRefundTypeEnum.ALL;
|
||||
}
|
||||
// 退款单入库 退款单状态:生成, 没有和渠道产生交互
|
||||
|
||||
PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
|
||||
PayRefundDO refundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
|
||||
PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundNo());
|
||||
//构造渠道的统一的退款请求参数
|
||||
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
||||
if(Objects.nonNull(payRefundDO)){
|
||||
//退款订单已经提交过。
|
||||
//TODO 校验相同退款单的金额
|
||||
if(Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
|
||||
|| Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())){
|
||||
//已成功退款
|
||||
throw exception(PAY_REFUND_SUCCEED);
|
||||
}else{
|
||||
//保证商户退款单不变,重复向渠道发起退款。渠道保持幂等
|
||||
unifiedReqDTO.setUserIp(req.getUserIp())
|
||||
.setAmount(payRefundDO.getRefundAmount())
|
||||
.setChannelOrderNo(payRefundDO.getChannelOrderNo())
|
||||
.setPayTradeNo(payRefundDO.getTradeNo())
|
||||
.setRefundReqNo(payRefundDO.getMerchantRefundNo())
|
||||
.setReason(payRefundDO.getReason());
|
||||
}
|
||||
}else{
|
||||
//新生成退款单。 退款单入库 退款单状态:生成
|
||||
payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
|
||||
.appId(order.getAppId())
|
||||
.channelOrderNo(order.getChannelOrderNo())
|
||||
.channelCode(order.getChannelCode())
|
||||
|
@ -131,41 +127,44 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
.status(PayRefundStatusEnum.CREATE.getStatus())
|
||||
.reason(req.getReason())
|
||||
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||
.reqNo(PaySeqUtils.genRefundReqNo())
|
||||
.type(refundType.getStatus())
|
||||
.build();
|
||||
payRefundCoreMapper.insert(refundDO);
|
||||
|
||||
// 调用渠道进行退款
|
||||
PayRefundUnifiedReqDTO unifiedReqDTO = PayRefundUnifiedReqDTO.builder()
|
||||
.userIp(req.getUserIp())
|
||||
.channelOrderNo(refundDO.getChannelOrderNo())
|
||||
.payTradeNo(refundDO.getTradeNo())
|
||||
.refundReqNo(refundDO.getReqNo())
|
||||
.amount(req.getAmount())
|
||||
.reason(refundDO.getReason())
|
||||
.build();
|
||||
PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
|
||||
// 根据渠道返回,获取退款后置处理,由postHandler 进行处理
|
||||
PayRefundChannelPostHandler payRefundChannelPostHandler = mapHandlers.get(refundUnifiedRespDTO.getRespEnum());
|
||||
if (Objects.isNull(payRefundChannelPostHandler)) {
|
||||
throw exception(PAY_REFUND_POST_HANDLER_NOT_FOUND);
|
||||
payRefundCoreMapper.insert(payRefundDO);
|
||||
unifiedReqDTO.setUserIp(req.getUserIp())
|
||||
.setAmount(payRefundDO.getRefundAmount())
|
||||
.setChannelOrderNo(payRefundDO.getChannelOrderNo())
|
||||
.setPayTradeNo(payRefundDO.getTradeNo())
|
||||
.setRefundReqNo(payRefundDO.getMerchantRefundNo())
|
||||
.setReason(req.getReason());
|
||||
}
|
||||
PayRefundPostReqDTO dto = PayRefundCoreConvert.INSTANCE.convert(refundUnifiedRespDTO);
|
||||
dto.setRefundAmount(req.getAmount())
|
||||
.setRefundedAmount(order.getRefundAmount())
|
||||
.setRefundedTimes(order.getRefundTimes())
|
||||
.setRefundId(refundDO.getId())
|
||||
.setOrderId(order.getId())
|
||||
.setRefundTypeEnum(refundType);
|
||||
//调用退款的后置处理
|
||||
payRefundChannelPostHandler.handleRefundChannelResp(dto);
|
||||
|
||||
return PayRefundRespDTO.builder().refundId(refundDO.getId()).build();
|
||||
//向渠道发起退款申请
|
||||
PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
|
||||
//构造退款申请返回对象
|
||||
PayRefundRespDTO respDTO = new PayRefundRespDTO();
|
||||
if(refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS
|
||||
||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING ){
|
||||
//成功处理, 在退款通知中处理, 这里不处理
|
||||
respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus());
|
||||
respDTO.setRefundId(payRefundDO.getId());
|
||||
}else {
|
||||
//失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。
|
||||
respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus());
|
||||
//更新退款单状态
|
||||
PayRefundDO updatePayRefund = new PayRefundDO();
|
||||
updatePayRefund.setId(payRefundDO.getId())
|
||||
.setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg())
|
||||
.setChannelErrorCode(refundUnifiedRespDTO.getChannelCode())
|
||||
.setStatus(PayRefundStatusEnum.FAILURE.getStatus());
|
||||
payRefundCoreMapper.updateById(updatePayRefund);
|
||||
}
|
||||
respDTO.setChannelReturnCode(refundUnifiedRespDTO.getChannelCode())
|
||||
.setChannelReturnMsg(refundUnifiedRespDTO.getChannelMsg());
|
||||
return respDTO;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
// 校验支付渠道是否有效
|
||||
|
@ -178,11 +177,15 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
}
|
||||
// 解析渠道退款通知数据, 统一处理
|
||||
PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
|
||||
|
||||
// TODO @jason:抽一个 notifyPayRefundSuccess 方法
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
|
||||
// 退款成功。 支付宝只有退款成功才会发通知
|
||||
PayRefundDO refundDO = payRefundCoreMapper.selectByReqNo(refundNotify.getReqNo());
|
||||
payRefundSuccess(refundNotify);
|
||||
} else {
|
||||
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
|
||||
}
|
||||
}
|
||||
|
||||
private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
|
||||
PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
|
||||
if (refundDO == null) {
|
||||
log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
|
||||
throw exception(PAY_REFUND_NOT_FOUND);
|
||||
|
@ -201,7 +204,9 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
updateOrderDO.setId(refundDO.getOrderId())
|
||||
.setRefundAmount(refundedAmount + refundAmount)
|
||||
.setStatus(orderStatus.getStatus())
|
||||
.setRefundTimes(payOrderDO.getRefundTimes()+1)
|
||||
.setRefundStatus(type);
|
||||
|
||||
payOrderCoreMapper.updateById(updateOrderDO);
|
||||
|
||||
// 跟新退款订单
|
||||
|
@ -218,9 +223,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
|
|||
// TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
|
||||
payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
|
||||
} else {
|
||||
//TODO 退款失败
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 支付退款订单渠道返回失败的后置处理类
|
||||
* {@link PayChannelRespEnum#CALL_EXCEPTION}
|
||||
* {@link PayChannelRespEnum#CAN_NOT_RETRY_FAILURE}
|
||||
*/
|
||||
@Service
|
||||
public class PayRefundChannelFailedHandler extends PayRefundAbstractChannelPostHandler {
|
||||
|
||||
@Resource
|
||||
private PayNotifyCoreService payNotifyCoreService;
|
||||
|
||||
public PayRefundChannelFailedHandler(PayOrderCoreMapper payOrderCoreMapper, PayRefundCoreMapper payRefundMapper) {
|
||||
super(payOrderCoreMapper, payRefundMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelRespEnum[] supportHandleResp() {
|
||||
return new PayChannelRespEnum[] {PayChannelRespEnum.CALL_EXCEPTION, PayChannelRespEnum.CAN_NOT_RETRY_FAILURE};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleRefundChannelResp(PayRefundPostReqDTO req) {
|
||||
//退款失败
|
||||
//更新退款单表
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
|
||||
updateRefundDO.setId(req.getRefundId())
|
||||
.setStatus(PayRefundStatusEnum.FAILURE.getStatus())
|
||||
.setChannelErrorCode(req.getChannelErrCode())
|
||||
.setChannelErrorMsg(Optional.ofNullable(req.getChannelErrMsg())
|
||||
.orElse(req.getExceptionMsg()));
|
||||
updatePayRefund(updateRefundDO);
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
//更新订单表
|
||||
updateOrderDO.setId(req.getOrderId())
|
||||
.setRefundTimes(req.getRefundedTimes() + 1);
|
||||
updatePayOrder(updateOrderDO);
|
||||
// 立刻插入退款通知记录
|
||||
// TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
|
||||
payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 支付退款订单渠道返回通知 {@link PayChannelRespEnum#PROCESSING_NOTIFY},后置处理类
|
||||
* 支付宝退款单好像没有回调, 微信会触发回调
|
||||
*/
|
||||
@Service
|
||||
public class PayRefundChannelNotifyHandler extends PayRefundAbstractChannelPostHandler {
|
||||
|
||||
public PayRefundChannelNotifyHandler(PayOrderCoreMapper payOrderCoreMapper,
|
||||
PayRefundCoreMapper payRefundMapper) {
|
||||
super(payOrderCoreMapper, payRefundMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelRespEnum[] supportHandleResp() {
|
||||
return new PayChannelRespEnum[] {PayChannelRespEnum.PROCESSING_NOTIFY};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRefundChannelResp(PayRefundPostReqDTO req) {
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
//更新退款单表
|
||||
updateRefundDO.setId(req.getRefundId())
|
||||
.setStatus(PayRefundStatusEnum.PROCESSING_NOTIFY.getStatus());
|
||||
updatePayRefund(updateRefundDO);
|
||||
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
//更新订单表
|
||||
updateOrderDO.setId(req.getOrderId())
|
||||
.setRefundTimes(req.getRefundedTimes() + 1);
|
||||
updatePayOrder(updateOrderDO);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 支付退款订单渠道返回需调用查询接口的后置处理类
|
||||
* {@link PayChannelRespEnum#PROCESSING_QUERY} //TODO 芋道源码 是不是微信有这样的情况
|
||||
* {@link PayChannelRespEnum#READ_TIME_OUT_EXCEPTION}
|
||||
*/
|
||||
@Service
|
||||
public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHandler {
|
||||
|
||||
|
||||
public PayRefundChannelQueryHandler(PayOrderCoreMapper payOrderCoreMapper,
|
||||
PayRefundCoreMapper payRefundMapper) {
|
||||
super(payOrderCoreMapper, payRefundMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelRespEnum[] supportHandleResp() {
|
||||
return new PayChannelRespEnum[]{PayChannelRespEnum.PROCESSING_QUERY, PayChannelRespEnum.READ_TIME_OUT_EXCEPTION};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRefundChannelResp(PayRefundPostReqDTO req) {
|
||||
final PayChannelRespEnum respEnum = req.getRespEnum();
|
||||
PayRefundStatusEnum refundStatus =
|
||||
Objects.equals(PayChannelRespEnum.PROCESSING_QUERY, respEnum) ? PayRefundStatusEnum.PROCESSING_QUERY
|
||||
: PayRefundStatusEnum.UNKNOWN_QUERY;
|
||||
//更新退款单表
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
updateRefundDO.setId(req.getRefundId())
|
||||
.setStatus(refundStatus.getStatus())
|
||||
.setChannelErrorCode(req.getChannelErrCode())
|
||||
.setChannelErrorMsg(req.getChannelErrMsg());
|
||||
updatePayRefund(updateRefundDO);
|
||||
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
//更新订单表
|
||||
updateOrderDO.setId(req.getOrderId())
|
||||
.setRefundTimes(req.getRefundedTimes() + 1);
|
||||
updatePayOrder(updateOrderDO);
|
||||
|
||||
//TODO 发起查询任务
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 支付退款订单渠道返回重试的后置处理类
|
||||
* {@link PayChannelRespEnum#RETRY_FAILURE}
|
||||
*/
|
||||
@Service
|
||||
public class PayRefundChannelRetryHandler extends PayRefundAbstractChannelPostHandler {
|
||||
|
||||
|
||||
public PayRefundChannelRetryHandler(PayOrderCoreMapper payOrderCoreMapper,
|
||||
PayRefundCoreMapper payRefundMapper) {
|
||||
super(payOrderCoreMapper, payRefundMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelRespEnum[] supportHandleResp() {
|
||||
return new PayChannelRespEnum[] {PayChannelRespEnum.RETRY_FAILURE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRefundChannelResp(PayRefundPostReqDTO req) {
|
||||
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
//更新退款单表
|
||||
updateRefundDO.setId(req.getRefundId())
|
||||
.setStatus(PayRefundStatusEnum.UNKNOWN_RETRY.getStatus())
|
||||
.setChannelErrorCode(req.getChannelErrCode())
|
||||
.setChannelErrorMsg(req.getChannelErrMsg());
|
||||
updatePayRefund(updateRefundDO);
|
||||
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
//更新订单表
|
||||
updateOrderDO.setId(req.getOrderId())
|
||||
.setRefundTimes(req.getRefundedTimes() + 1);
|
||||
updatePayOrder(updateOrderDO);
|
||||
|
||||
//TODO 发起重试任务
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
/**
|
||||
* 支付退款订单渠道返回退款成功的后置处理类
|
||||
* {@link PayChannelRespEnum#SYNC_SUCCESS}
|
||||
*/
|
||||
@Service
|
||||
public class PayRefundChannelSuccessHandler extends PayRefundAbstractChannelPostHandler {
|
||||
|
||||
|
||||
@Resource
|
||||
private PayNotifyCoreService payNotifyCoreService;
|
||||
|
||||
|
||||
public PayRefundChannelSuccessHandler(PayOrderCoreMapper payOrderCoreMapper,
|
||||
PayRefundCoreMapper payRefundMapper) {
|
||||
super(payOrderCoreMapper, payRefundMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelRespEnum[] supportHandleResp() {
|
||||
return new PayChannelRespEnum[]{PayChannelRespEnum.SYNC_SUCCESS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRefundChannelResp(PayRefundPostReqDTO req) {
|
||||
//退款成功
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
//更新退款单表
|
||||
updateRefundDO.setId(req.getRefundId())
|
||||
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
|
||||
.setChannelRefundNo(req.getChannelRefundNo())
|
||||
.setSuccessTime(new Date());
|
||||
updatePayRefund(updateRefundDO);
|
||||
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
//更新订单表
|
||||
updateOrderDO.setId(req.getOrderId())
|
||||
.setRefundTimes(req.getRefundedTimes() + 1)
|
||||
.setRefundStatus(req.getRefundTypeEnum().getStatus())
|
||||
.setRefundAmount(req.getRefundedAmount()+ req.getRefundAmount());
|
||||
updatePayOrder(updateOrderDO);
|
||||
|
||||
// 立刻插入退款通知记录
|
||||
// TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
|
||||
payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ public class PayRefundUnifiedReqDTO {
|
|||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
||||
* 退款请求单号 同一退款请求单号多次请求只退一笔。
|
||||
* 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
|
||||
*/
|
||||
@NotEmpty(message = "退款请求单号")
|
||||
private String refundReqNo;
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.dto;
|
||||
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
/**
|
||||
* 统一退款 Response DTO
|
||||
*
|
||||
|
@ -19,55 +17,20 @@ import javax.validation.constraints.NotEmpty;
|
|||
@AllArgsConstructor
|
||||
@Data
|
||||
public class PayRefundUnifiedRespDTO {
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的退款结果
|
||||
*/
|
||||
private PayChannelRefundRespEnum channelResp;
|
||||
|
||||
/**
|
||||
* 渠道的通用返回结果
|
||||
* 渠道返回码
|
||||
*/
|
||||
private PayChannelRespEnum respEnum;
|
||||
|
||||
|
||||
private String channelCode;
|
||||
|
||||
/**
|
||||
* 渠道退款单号
|
||||
* 渠道返回信息
|
||||
*/
|
||||
private String channelRefundNo;
|
||||
|
||||
|
||||
/**
|
||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
|
||||
* 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
|
||||
*/
|
||||
private String payTradeNo;
|
||||
|
||||
|
||||
/**
|
||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
||||
* 退款请求单号 同一退款请求单号多次请求只退一笔。
|
||||
*/
|
||||
private String refundReqNo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 调用异常错误信息
|
||||
*/
|
||||
private String exceptionMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的错误码
|
||||
*/
|
||||
private String channelErrCode;
|
||||
|
||||
|
||||
/**
|
||||
* 渠道的错误描述
|
||||
*/
|
||||
private String channelErrMsg;
|
||||
private String channelMsg;
|
||||
|
||||
//TODO 退款资金渠 ???
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||
|
||||
import cn.hutool.extra.validation.ValidationUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
|
@ -8,11 +9,9 @@ import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
|
@ -108,20 +107,10 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
PayRefundUnifiedRespDTO resp;
|
||||
try {
|
||||
resp = doUnifiedRefund(reqDTO);
|
||||
} catch (SocketTimeoutException ex){
|
||||
// 网络 read time out 异常
|
||||
log.error("[unifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), ex);
|
||||
return PayRefundUnifiedRespDTO.builder()
|
||||
.exceptionMsg(ex.getMessage())
|
||||
.respEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION)
|
||||
.build();
|
||||
} catch (Throwable ex) {
|
||||
// 打印异常日志
|
||||
// 记录异常日志
|
||||
log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
|
||||
return PayRefundUnifiedRespDTO.builder()
|
||||
.exceptionMsg(ex.getMessage())
|
||||
.respEnum(PayChannelRespEnum.CALL_EXCEPTION)
|
||||
.build();
|
||||
throw exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import cn.hutool.core.date.DateUtil;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayConfig;
|
||||
|
@ -17,7 +17,6 @@ import com.alipay.api.response.AlipayTradeRefundResponse;
|
|||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -118,43 +117,21 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
//退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知,
|
||||
//退款导致触发的异步通知是发送到支付接口中设置的notify_url
|
||||
//TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?
|
||||
respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
|
||||
respDTO.setChannelResp(PayChannelRefundRespEnum.SUCCESS)
|
||||
.setChannelCode(response.getCode())
|
||||
.setChannelMsg(response.getMsg());
|
||||
}else{
|
||||
//特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务
|
||||
//沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain
|
||||
if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
|
||||
respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
|
||||
.setChannelErrMsg(response.getSubMsg())
|
||||
.setChannelErrCode(response.getSubCode());
|
||||
}else{
|
||||
//交易已关闭,需要查询确认退款是否已经完成
|
||||
if("ACQ.TRADE_HAS_CLOSE".equals(response.getSubCode())){
|
||||
respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_QUERY)
|
||||
.setChannelErrMsg(response.getSubMsg())
|
||||
.setChannelErrCode(response.getSubCode());
|
||||
}else {
|
||||
//其他当做不可以重试的错误
|
||||
respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
|
||||
.setChannelErrCode(response.getSubCode())
|
||||
.setChannelErrMsg(response.getSubMsg());
|
||||
}
|
||||
}
|
||||
respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
|
||||
.setChannelCode(response.getSubCode())
|
||||
.setChannelMsg(response.getSubMsg());
|
||||
}
|
||||
return respDTO;
|
||||
} catch (AlipayApiException e) {
|
||||
//TODO 记录异常日志
|
||||
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
|
||||
Throwable cause = e.getCause();
|
||||
//网络 read time out 异常, 退款状态未知
|
||||
if (cause instanceof SocketTimeoutException) {
|
||||
respDTO.setExceptionMsg(e.getMessage())
|
||||
.setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
|
||||
}else{
|
||||
respDTO.setExceptionMsg(e.getMessage())
|
||||
.setChannelErrCode(e.getErrCode())
|
||||
.setChannelErrMsg(e.getErrMsg())
|
||||
.setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
|
||||
}
|
||||
respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
|
||||
.setChannelCode(e.getErrCode())
|
||||
.setChannelMsg(e.getErrMsg());
|
||||
return respDTO;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 渠道统一的退款返回结果
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayChannelRefundRespEnum {
|
||||
SUCCESS(1, "退款成功"),
|
||||
FAILURE(2, "退款失败"),
|
||||
PROCESSING(3,"退款处理中"),
|
||||
CLOSED(4, "退款关闭");
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.enums;
|
||||
|
||||
// TODO @芋艿:感觉情况有点多,得讨论下
|
||||
/**
|
||||
* 统一的渠道返回结果
|
||||
* @author jason
|
||||
*/
|
||||
public enum PayChannelRespEnum {
|
||||
|
||||
/**
|
||||
* 接口通讯正常返回, 并明确处理成功 ,不需要通过查询或者回调接口 进行下一步处理
|
||||
*/
|
||||
SYNC_SUCCESS,
|
||||
|
||||
/**
|
||||
* 接口通讯正常返回, 但返回错误,并且不能通过重试解决的错误,
|
||||
* 如提交失败, 业务错误(余额不足), 或者参数错误, 签名错误, 需要干预后才能处理
|
||||
*/
|
||||
CAN_NOT_RETRY_FAILURE,
|
||||
|
||||
|
||||
/**
|
||||
* 接口通讯正常返回,
|
||||
* 可以通过重试解决的错误. 如系统超时, 系统繁忙。状态未知 不能改变请求参数,如退款单请求号,重发请求
|
||||
*/
|
||||
RETRY_FAILURE,
|
||||
|
||||
|
||||
/**
|
||||
* 接口通讯正常返回,但是处理结果 需要渠道回调进行下一步处理
|
||||
*/
|
||||
PROCESSING_NOTIFY,
|
||||
|
||||
|
||||
/**
|
||||
* 接口通讯正常返回, 但是处理结果,需要调用查询接口 进行查询
|
||||
*/
|
||||
PROCESSING_QUERY,
|
||||
|
||||
|
||||
/**
|
||||
* 本系统调用渠道接口异常, 渠道接口请求未正常发送, 本系统不可预知的异常,较少发生, 可认为失败。 不用重试.
|
||||
*/
|
||||
CALL_EXCEPTION,
|
||||
|
||||
|
||||
/**
|
||||
* 本系统调用渠道接口成功, 但是未接受到请求结果,较少发生(需合理设置read time out ) 结果未知。 需要调用查询接口进行查询
|
||||
*/
|
||||
READ_TIME_OUT_EXCEPTION;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.userserver.modules.pay.controller.order;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
|
||||
|
@ -36,8 +37,9 @@ public class PayRefundController {
|
|||
PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
|
||||
req.setUserIp(getClientIP());
|
||||
//TODO 测试暂时模拟生成商户退款订单
|
||||
if(StrUtil.isEmpty(reqVO.getMerchantRefundNo())) {
|
||||
req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
|
||||
//req.setMerchantRefundNo("MO202111210814084370000");
|
||||
}
|
||||
return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req)));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,23 @@ import lombok.experimental.Accessors;
|
|||
@AllArgsConstructor
|
||||
public class PayRefundRespVO {
|
||||
|
||||
/**
|
||||
* 渠道返回结果
|
||||
* 退款处理中和退款成功 返回 1
|
||||
* 失败和其他情况 返回 2
|
||||
*/
|
||||
private Integer channelReturnResult;
|
||||
|
||||
/**
|
||||
* 渠道返回code
|
||||
*/
|
||||
private String channelReturnCode;
|
||||
|
||||
/**
|
||||
* 渠道返回消息
|
||||
*/
|
||||
private String channelReturnMsg;
|
||||
|
||||
/**
|
||||
* 支付退款单编号, 自增
|
||||
*/
|
||||
|
|
|
@ -19,5 +19,5 @@ public interface PayRefundConvert {
|
|||
|
||||
PayRefundReqDTO convert(PayRefundReqVO reqVO);
|
||||
|
||||
PayRefundRespVO convert(PayRefundRespDTO respBO);
|
||||
PayRefundRespVO convert(PayRefundRespDTO req);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue