Răsfoiți Sursa

mall + pay:
1. 优化 PayClient 退款逻辑,返回业务失败 errorCode + errorMsg 错误码

YunaiV 1 an în urmă
părinte
comite
1c282bd3cb

+ 68 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java

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

+ 16 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -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) {

+ 13 - 23
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -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/扫码又没有

+ 33 - 66
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

@@ -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);
     }
 
     // ========== 各种工具方法 ==========