mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	mall + pay:
1. 优化 PayClient 退款逻辑,返回业务失败 errorCode + errorMsg 错误码
This commit is contained in:
		@@ -1,5 +1,6 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@@ -44,4 +45,71 @@ public class PayRefundRespDTO {
 | 
			
		||||
     */
 | 
			
		||||
    private Object rawData;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 调用渠道的错误码
 | 
			
		||||
     *
 | 
			
		||||
     * 注意:这里返回的是业务异常,而是不系统异常。
 | 
			
		||||
     * 如果是系统异常,则会抛出 {@link PayException}
 | 
			
		||||
     */
 | 
			
		||||
    private String channelErrorCode;
 | 
			
		||||
    /**
 | 
			
		||||
     * 调用渠道报错时,错误信息
 | 
			
		||||
     */
 | 
			
		||||
    private String channelErrorMsg;
 | 
			
		||||
 | 
			
		||||
    private PayRefundRespDTO() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【WAITING】状态的退款返回
 | 
			
		||||
     */
 | 
			
		||||
    public static PayRefundRespDTO waitingOf(String channelRefundNo,
 | 
			
		||||
                                             String outRefundNo, Object rawData) {
 | 
			
		||||
        PayRefundRespDTO respDTO = new PayRefundRespDTO();
 | 
			
		||||
        respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus();
 | 
			
		||||
        respDTO.channelRefundNo = channelRefundNo;
 | 
			
		||||
        // 相对通用的字段
 | 
			
		||||
        respDTO.outRefundNo = outRefundNo;
 | 
			
		||||
        respDTO.rawData = rawData;
 | 
			
		||||
        return respDTO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【SUCCESS】状态的退款返回
 | 
			
		||||
     */
 | 
			
		||||
    public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime,
 | 
			
		||||
                                             String outRefundNo, Object rawData) {
 | 
			
		||||
        PayRefundRespDTO respDTO = new PayRefundRespDTO();
 | 
			
		||||
        respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus();
 | 
			
		||||
        respDTO.channelRefundNo = channelRefundNo;
 | 
			
		||||
        respDTO.successTime = successTime;
 | 
			
		||||
        // 相对通用的字段
 | 
			
		||||
        respDTO.outRefundNo = outRefundNo;
 | 
			
		||||
        respDTO.rawData = rawData;
 | 
			
		||||
        return respDTO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【FAILURE】状态的退款返回
 | 
			
		||||
     */
 | 
			
		||||
    public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) {
 | 
			
		||||
        return failureOf(null, null,
 | 
			
		||||
                outRefundNo, rawData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建【FAILURE】状态的退款返回
 | 
			
		||||
     */
 | 
			
		||||
    public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
 | 
			
		||||
                                             String outRefundNo, Object rawData) {
 | 
			
		||||
        PayRefundRespDTO respDTO = new PayRefundRespDTO();
 | 
			
		||||
        respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus();
 | 
			
		||||
        respDTO.channelErrorCode = channelErrorCode;
 | 
			
		||||
        respDTO.channelErrorMsg = channelErrorMsg;
 | 
			
		||||
        // 相对通用的字段
 | 
			
		||||
        respDTO.outRefundNo = outRefundNo;
 | 
			
		||||
        respDTO.rawData = rawData;
 | 
			
		||||
        return respDTO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 | 
			
		||||
        try {
 | 
			
		||||
            return doParseOrderNotify(params, body);
 | 
			
		||||
        } catch (Throwable ex) {
 | 
			
		||||
            log.error("[parseOrderNotify][params({}) body({}) 解析失败]", params, body, ex);
 | 
			
		||||
            log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]",
 | 
			
		||||
                    getId(), params, body, ex);
 | 
			
		||||
            throw buildPayException(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -129,6 +130,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 | 
			
		||||
 | 
			
		||||
    protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
 | 
			
		||||
        try {
 | 
			
		||||
            return doParseRefundNotify(params, body);
 | 
			
		||||
        } catch (Throwable ex) {
 | 
			
		||||
            log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]",
 | 
			
		||||
                    getId(), params, body, ex);
 | 
			
		||||
            throw buildPayException(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body)
 | 
			
		||||
            throws Throwable;
 | 
			
		||||
 | 
			
		||||
    // ========== 各种工具方法 ==========
 | 
			
		||||
 | 
			
		||||
    private PayException buildPayException(Throwable ex) {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,6 @@ import java.util.Objects;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
 | 
			
		||||
@@ -94,7 +93,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
 | 
			
		||||
     * @return 退款请求 Response
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
 | 
			
		||||
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException {
 | 
			
		||||
        // 1.1 构建 AlipayTradeRefundModel 请求
 | 
			
		||||
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
 | 
			
		||||
        model.setOutTradeNo(reqDTO.getOutTradeNo());
 | 
			
		||||
@@ -104,31 +103,22 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
 | 
			
		||||
        // 1.2 构建 AlipayTradePayRequest 请求
 | 
			
		||||
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
 | 
			
		||||
        request.setBizModel(model);
 | 
			
		||||
        try {
 | 
			
		||||
            // 2.1 执行请求
 | 
			
		||||
            AlipayTradeRefundResponse response =  client.execute(request);
 | 
			
		||||
            // 2.2 创建返回结果
 | 
			
		||||
            PayRefundRespDTO refund = new PayRefundRespDTO()
 | 
			
		||||
                    .setOutRefundNo(reqDTO.getOutRefundNo())
 | 
			
		||||
                    .setRawData(response);
 | 
			
		||||
            // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
 | 
			
		||||
            // 另外,支付宝没有退款单号,所以不用设置
 | 
			
		||||
            if (response.isSuccess()) {
 | 
			
		||||
                refund.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
 | 
			
		||||
                        .setSuccessTime(LocalDateTimeUtil.of(response.getGmtRefundPay()));
 | 
			
		||||
                Assert.notNull(refund.getSuccessTime(), "退款成功时间不能为空");
 | 
			
		||||
            } else {
 | 
			
		||||
                refund.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus());
 | 
			
		||||
            }
 | 
			
		||||
            return refund;
 | 
			
		||||
        } catch (AlipayApiException e) {
 | 
			
		||||
            log.error("[doUnifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), e);
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        // 2.1 执行请求
 | 
			
		||||
        AlipayTradeRefundResponse response =  client.execute(request);
 | 
			
		||||
        // 2.2 创建返回结果
 | 
			
		||||
        // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
 | 
			
		||||
        // 另外,支付宝没有退款单号,所以不用设置
 | 
			
		||||
        if (response.isSuccess()) {
 | 
			
		||||
            return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()),
 | 
			
		||||
                    reqDTO.getOutRefundNo(), response);
 | 
			
		||||
        } else {
 | 
			
		||||
            return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
 | 
			
		||||
    public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
 | 
			
		||||
        // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
 | 
			
		||||
        // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
 | 
			
		||||
        // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 | 
			
		||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
 | 
			
		||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 | 
			
		||||
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
 | 
			
		||||
@@ -163,9 +162,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
 | 
			
		||||
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (WxPayException e) {
 | 
			
		||||
            // todo 芋艿:异常的处理;
 | 
			
		||||
//            throw buildUnifiedOrderException(null, e);
 | 
			
		||||
            return null;
 | 
			
		||||
            String errorCode = getErrorCode(e);
 | 
			
		||||
            String errorMessage = getErrorMessage(e);
 | 
			
		||||
            return PayRefundRespDTO.failureOf(errorCode, errorMessage,
 | 
			
		||||
                    reqDTO.getOutTradeNo(), e.getXmlString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -181,17 +181,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
 | 
			
		||||
        // 2.1 执行请求
 | 
			
		||||
        WxPayRefundResult response = client.refundV2(request);
 | 
			
		||||
        // 2.2 创建返回结果
 | 
			
		||||
        PayRefundRespDTO refund = new PayRefundRespDTO()
 | 
			
		||||
                .setOutRefundNo(reqDTO.getOutRefundNo())
 | 
			
		||||
                .setRawData(response);
 | 
			
		||||
        if (Objects.equals("SUCCESS", response.getResultCode())) {
 | 
			
		||||
            refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
 | 
			
		||||
                    .setChannelRefundNo(response.getRefundId());
 | 
			
		||||
        } else {
 | 
			
		||||
            refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
 | 
			
		||||
            return PayRefundRespDTO.waitingOf(response.getRefundId(),
 | 
			
		||||
                    reqDTO.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
        // TODO 芋艿;异常的处理;
 | 
			
		||||
        return refund;
 | 
			
		||||
        return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
 | 
			
		||||
@@ -206,78 +200,51 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
 | 
			
		||||
        // 2.1 执行请求
 | 
			
		||||
        WxPayRefundV3Result response = client.refundV3(request);
 | 
			
		||||
        // 2.2 创建返回结果
 | 
			
		||||
        PayRefundRespDTO refund = new PayRefundRespDTO()
 | 
			
		||||
                .setOutRefundNo(reqDTO.getOutRefundNo())
 | 
			
		||||
                .setRawData(response);
 | 
			
		||||
        if (Objects.equals("SUCCESS", response.getStatus())) {
 | 
			
		||||
            refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
 | 
			
		||||
                    .setChannelRefundNo(response.getRefundId())
 | 
			
		||||
                    .setSuccessTime(parseDateV3(response.getSuccessTime()));
 | 
			
		||||
        } else if (Objects.equals("PROCESSING", response.getStatus())) {
 | 
			
		||||
            refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
 | 
			
		||||
                    .setChannelRefundNo(response.getRefundId());
 | 
			
		||||
        } else {
 | 
			
		||||
            refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
 | 
			
		||||
            return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
 | 
			
		||||
                    reqDTO.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
        // TODO 芋艿;异常的处理;
 | 
			
		||||
        return refund;
 | 
			
		||||
        if (Objects.equals("PROCESSING", response.getStatus())) {
 | 
			
		||||
            return PayRefundRespDTO.waitingOf(response.getRefundId(),
 | 
			
		||||
                    reqDTO.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
        return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
 | 
			
		||||
        try {
 | 
			
		||||
            // 微信支付 v2 回调结果处理
 | 
			
		||||
            switch (config.getApiVersion()) {
 | 
			
		||||
                case API_VERSION_V2:
 | 
			
		||||
                    return parseRefundNotifyV2(body);
 | 
			
		||||
                case WxPayClientConfig.API_VERSION_V3:
 | 
			
		||||
                    return parseRefundNotifyV3(body);
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (WxPayException e) {
 | 
			
		||||
            log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
            // TODO 芋艿:缺一个异常翻译
 | 
			
		||||
    public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws WxPayException {
 | 
			
		||||
        switch (config.getApiVersion()) {
 | 
			
		||||
            case API_VERSION_V2:
 | 
			
		||||
                return doParseRefundNotifyV2(body);
 | 
			
		||||
            case WxPayClientConfig.API_VERSION_V3:
 | 
			
		||||
                return parseRefundNotifyV3(body);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("DuplicatedCode")
 | 
			
		||||
    private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
 | 
			
		||||
    private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException {
 | 
			
		||||
        // 1. 解析回调
 | 
			
		||||
        WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
 | 
			
		||||
        WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
 | 
			
		||||
        WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
 | 
			
		||||
        // 2. 构建结果
 | 
			
		||||
        PayRefundRespDTO notify = new PayRefundRespDTO()
 | 
			
		||||
                .setChannelRefundNo(responseResult.getRefundId())
 | 
			
		||||
                .setOutRefundNo(responseResult.getOutRefundNo())
 | 
			
		||||
                .setRawData(response);
 | 
			
		||||
        if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
 | 
			
		||||
            notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
 | 
			
		||||
                    .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
 | 
			
		||||
        } else {
 | 
			
		||||
            notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
 | 
			
		||||
        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
 | 
			
		||||
            return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
 | 
			
		||||
                    result.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
        return notify;
 | 
			
		||||
        return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("DuplicatedCode")
 | 
			
		||||
    private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
 | 
			
		||||
        // 1. 解析回调
 | 
			
		||||
        WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
 | 
			
		||||
        WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
 | 
			
		||||
        WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
 | 
			
		||||
        // 2. 构建结果
 | 
			
		||||
        PayRefundRespDTO notify = new PayRefundRespDTO()
 | 
			
		||||
                .setChannelRefundNo(responseResult.getRefundId())
 | 
			
		||||
                .setOutRefundNo(responseResult.getOutRefundNo())
 | 
			
		||||
                .setRawData(response);
 | 
			
		||||
        if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
 | 
			
		||||
            notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
 | 
			
		||||
                    .setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
 | 
			
		||||
        } else {
 | 
			
		||||
            notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
 | 
			
		||||
        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
 | 
			
		||||
            return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
 | 
			
		||||
                    result.getOutRefundNo(), response);
 | 
			
		||||
        }
 | 
			
		||||
        return notify;
 | 
			
		||||
        return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ========== 各种工具方法 ==========
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user