mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-11-01 03:28:41 +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
	 YunaiV
					YunaiV