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