Commit 1c282bd3cb7ada650947b6978d6943df392c1e13

Authored by YunaiV
1 parent 6f475f8c

mall + pay:

1. 优化 PayClient 退款逻辑,返回业务失败 errorCode + errorMsg 错误码
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
2 2  
  3 +import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
3 4 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
4 5 import lombok.Data;
5 6  
... ... @@ -44,4 +45,71 @@ public class PayRefundRespDTO {
44 45 */
45 46 private Object rawData;
46 47  
  48 + /**
  49 + * 调用渠道的错误码
  50 + *
  51 + * 注意:这里返回的是业务异常,而是不系统异常。
  52 + * 如果是系统异常,则会抛出 {@link PayException}
  53 + */
  54 + private String channelErrorCode;
  55 + /**
  56 + * 调用渠道报错时,错误信息
  57 + */
  58 + private String channelErrorMsg;
  59 +
  60 + private PayRefundRespDTO() {
  61 + }
  62 +
  63 + /**
  64 + * 创建【WAITING】状态的退款返回
  65 + */
  66 + public static PayRefundRespDTO waitingOf(String channelRefundNo,
  67 + String outRefundNo, Object rawData) {
  68 + PayRefundRespDTO respDTO = new PayRefundRespDTO();
  69 + respDTO.status = PayRefundStatusRespEnum.WAITING.getStatus();
  70 + respDTO.channelRefundNo = channelRefundNo;
  71 + // 相对通用的字段
  72 + respDTO.outRefundNo = outRefundNo;
  73 + respDTO.rawData = rawData;
  74 + return respDTO;
  75 + }
  76 +
  77 + /**
  78 + * 创建【SUCCESS】状态的退款返回
  79 + */
  80 + public static PayRefundRespDTO successOf(String channelRefundNo, LocalDateTime successTime,
  81 + String outRefundNo, Object rawData) {
  82 + PayRefundRespDTO respDTO = new PayRefundRespDTO();
  83 + respDTO.status = PayRefundStatusRespEnum.SUCCESS.getStatus();
  84 + respDTO.channelRefundNo = channelRefundNo;
  85 + respDTO.successTime = successTime;
  86 + // 相对通用的字段
  87 + respDTO.outRefundNo = outRefundNo;
  88 + respDTO.rawData = rawData;
  89 + return respDTO;
  90 + }
  91 +
  92 + /**
  93 + * 创建【FAILURE】状态的退款返回
  94 + */
  95 + public static PayRefundRespDTO failureOf(String outRefundNo, Object rawData) {
  96 + return failureOf(null, null,
  97 + outRefundNo, rawData);
  98 + }
  99 +
  100 + /**
  101 + * 创建【FAILURE】状态的退款返回
  102 + */
  103 + public static PayRefundRespDTO failureOf(String channelErrorCode, String channelErrorMsg,
  104 + String outRefundNo, Object rawData) {
  105 + PayRefundRespDTO respDTO = new PayRefundRespDTO();
  106 + respDTO.status = PayRefundStatusRespEnum.FAILURE.getStatus();
  107 + respDTO.channelErrorCode = channelErrorCode;
  108 + respDTO.channelErrorMsg = channelErrorMsg;
  109 + // 相对通用的字段
  110 + respDTO.outRefundNo = outRefundNo;
  111 + respDTO.rawData = rawData;
  112 + return respDTO;
  113 + }
  114 +
47 115 }
... ...
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
98 98 try {
99 99 return doParseOrderNotify(params, body);
100 100 } catch (Throwable ex) {
101   - log.error("[parseOrderNotify][params({}) body({}) 解析失败]", params, body, ex);
  101 + log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]",
  102 + getId(), params, body, ex);
102 103 throw buildPayException(ex);
103 104 }
104 105 }
... ... @@ -129,6 +130,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
129 130  
130 131 protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
131 132  
  133 + @Override
  134 + public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
  135 + try {
  136 + return doParseRefundNotify(params, body);
  137 + } catch (Throwable ex) {
  138 + log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]",
  139 + getId(), params, body, ex);
  140 + throw buildPayException(ex);
  141 + }
  142 + }
  143 +
  144 + protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body)
  145 + throws Throwable;
  146 +
132 147 // ========== 各种工具方法 ==========
133 148  
134 149 private PayException buildPayException(Throwable ex) {
... ...
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;
29 29 import java.util.function.Supplier;
30 30  
31 31 import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
32   -import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
33 32  
34 33 /**
35 34 * 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
... ... @@ -94,7 +93,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
94 93 * @return 退款请求 Response
95 94 */
96 95 @Override
97   - protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  96 + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws AlipayApiException {
98 97 // 1.1 构建 AlipayTradeRefundModel 请求
99 98 AlipayTradeRefundModel model = new AlipayTradeRefundModel();
100 99 model.setOutTradeNo(reqDTO.getOutTradeNo());
... ... @@ -104,31 +103,22 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
104 103 // 1.2 构建 AlipayTradePayRequest 请求
105 104 AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
106 105 request.setBizModel(model);
107   - try {
108   - // 2.1 执行请求
109   - AlipayTradeRefundResponse response = client.execute(request);
110   - // 2.2 创建返回结果
111   - PayRefundRespDTO refund = new PayRefundRespDTO()
112   - .setOutRefundNo(reqDTO.getOutRefundNo())
113   - .setRawData(response);
114   - // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
115   - // 另外,支付宝没有退款单号,所以不用设置
116   - if (response.isSuccess()) {
117   - refund.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
118   - .setSuccessTime(LocalDateTimeUtil.of(response.getGmtRefundPay()));
119   - Assert.notNull(refund.getSuccessTime(), "退款成功时间不能为空");
120   - } else {
121   - refund.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus());
122   - }
123   - return refund;
124   - } catch (AlipayApiException e) {
125   - log.error("[doUnifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), e);
126   - return null;
  106 +
  107 + // 2.1 执行请求
  108 + AlipayTradeRefundResponse response = client.execute(request);
  109 + // 2.2 创建返回结果
  110 + // 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
  111 + // 另外,支付宝没有退款单号,所以不用设置
  112 + if (response.isSuccess()) {
  113 + return PayRefundRespDTO.successOf(null, LocalDateTimeUtil.of(response.getGmtRefundPay()),
  114 + reqDTO.getOutRefundNo(), response);
  115 + } else {
  116 + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
127 117 }
128 118 }
129 119  
130 120 @Override
131   - public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
  121 + public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
132 122 // 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
133 123 // ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
134 124 // ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
... ...
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;
12 12 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
13 13 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
14 14 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
15   -import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
16 15 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
17 16 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
18 17 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
... ... @@ -163,9 +162,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
163 162 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
164 163 }
165 164 } catch (WxPayException e) {
166   - // todo 芋艿:异常的处理;
167   -// throw buildUnifiedOrderException(null, e);
168   - return null;
  165 + String errorCode = getErrorCode(e);
  166 + String errorMessage = getErrorMessage(e);
  167 + return PayRefundRespDTO.failureOf(errorCode, errorMessage,
  168 + reqDTO.getOutTradeNo(), e.getXmlString());
169 169 }
170 170 }
171 171  
... ... @@ -181,17 +181,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
181 181 // 2.1 执行请求
182 182 WxPayRefundResult response = client.refundV2(request);
183 183 // 2.2 创建返回结果
184   - PayRefundRespDTO refund = new PayRefundRespDTO()
185   - .setOutRefundNo(reqDTO.getOutRefundNo())
186   - .setRawData(response);
187 184 if (Objects.equals("SUCCESS", response.getResultCode())) {
188   - refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
189   - .setChannelRefundNo(response.getRefundId());
190   - } else {
191   - refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
  185 + return PayRefundRespDTO.waitingOf(response.getRefundId(),
  186 + reqDTO.getOutRefundNo(), response);
192 187 }
193   - // TODO 芋艿;异常的处理;
194   - return refund;
  188 + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
195 189 }
196 190  
197 191 private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
... ... @@ -206,78 +200,51 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
206 200 // 2.1 执行请求
207 201 WxPayRefundV3Result response = client.refundV3(request);
208 202 // 2.2 创建返回结果
209   - PayRefundRespDTO refund = new PayRefundRespDTO()
210   - .setOutRefundNo(reqDTO.getOutRefundNo())
211   - .setRawData(response);
212 203 if (Objects.equals("SUCCESS", response.getStatus())) {
213   - refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
214   - .setChannelRefundNo(response.getRefundId())
215   - .setSuccessTime(parseDateV3(response.getSuccessTime()));
216   - } else if (Objects.equals("PROCESSING", response.getStatus())) {
217   - refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
218   - .setChannelRefundNo(response.getRefundId());
219   - } else {
220   - refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
  204 + return PayRefundRespDTO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
  205 + reqDTO.getOutRefundNo(), response);
221 206 }
222   - // TODO 芋艿;异常的处理;
223   - return refund;
  207 + if (Objects.equals("PROCESSING", response.getStatus())) {
  208 + return PayRefundRespDTO.waitingOf(response.getRefundId(),
  209 + reqDTO.getOutRefundNo(), response);
  210 + }
  211 + return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
224 212 }
225 213  
226 214 @Override
227   - public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
228   - try {
229   - // 微信支付 v2 回调结果处理
230   - switch (config.getApiVersion()) {
231   - case API_VERSION_V2:
232   - return parseRefundNotifyV2(body);
233   - case WxPayClientConfig.API_VERSION_V3:
234   - return parseRefundNotifyV3(body);
235   - default:
236   - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
237   - }
238   - } catch (WxPayException e) {
239   - log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
240   - throw new RuntimeException(e);
241   - // TODO 芋艿:缺一个异常翻译
  215 + public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws WxPayException {
  216 + switch (config.getApiVersion()) {
  217 + case API_VERSION_V2:
  218 + return doParseRefundNotifyV2(body);
  219 + case WxPayClientConfig.API_VERSION_V3:
  220 + return parseRefundNotifyV3(body);
  221 + default:
  222 + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
242 223 }
243 224 }
244 225  
245   - @SuppressWarnings("DuplicatedCode")
246   - private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
  226 + private PayRefundRespDTO doParseRefundNotifyV2(String body) throws WxPayException {
247 227 // 1. 解析回调
248 228 WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
249   - WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
  229 + WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
250 230 // 2. 构建结果
251   - PayRefundRespDTO notify = new PayRefundRespDTO()
252   - .setChannelRefundNo(responseResult.getRefundId())
253   - .setOutRefundNo(responseResult.getOutRefundNo())
254   - .setRawData(response);
255   - if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
256   - notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
257   - .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
258   - } else {
259   - notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
  231 + if (Objects.equals("SUCCESS", result.getRefundStatus())) {
  232 + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
  233 + result.getOutRefundNo(), response);
260 234 }
261   - return notify;
  235 + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
262 236 }
263 237  
264   - @SuppressWarnings("DuplicatedCode")
265 238 private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
266 239 // 1. 解析回调
267 240 WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
268   - WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
  241 + WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
269 242 // 2. 构建结果
270   - PayRefundRespDTO notify = new PayRefundRespDTO()
271   - .setChannelRefundNo(responseResult.getRefundId())
272   - .setOutRefundNo(responseResult.getOutRefundNo())
273   - .setRawData(response);
274   - if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
275   - notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
276   - .setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
277   - } else {
278   - notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
  243 + if (Objects.equals("SUCCESS", result.getRefundStatus())) {
  244 + return PayRefundRespDTO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
  245 + result.getOutRefundNo(), response);
279 246 }
280   - return notify;
  247 + return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
281 248 }
282 249  
283 250 // ========== 各种工具方法 ==========
... ...