Commit d0a7f4187592c5da8d593473adb62a268fb66ee4

Authored by YunaiV
1 parent 68a4ef98

mall + pay:

1. 完善微信支付的 V3 支付回调、退款回调
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -70,6 +70,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
70 70 return channelId;
71 71 }
72 72  
  73 + // ============ 支付相关 ==========
  74 +
73 75 @Override
74 76 public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
75 77 Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
... ... @@ -91,6 +93,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
91 93 protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
92 94 throws Throwable;
93 95  
  96 + // ============ 退款相关 ==========
  97 +
94 98 @Override
95 99 public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
96 100 Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
... ... @@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.codec.Base64;
5 5 import cn.hutool.core.date.LocalDateTimeUtil;
6 6 import cn.hutool.core.date.TemporalAccessorUtil;
7   -import cn.hutool.core.lang.Assert;
8 7 import cn.hutool.core.util.StrUtil;
9 8 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
10 9 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
... ... @@ -19,8 +18,11 @@ import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
19 18 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
20 19 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
21 20 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
  21 +import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
22 22 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
  23 +import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
23 24 import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
  25 +import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
24 26 import com.github.binarywang.wxpay.config.WxPayConfig;
25 27 import com.github.binarywang.wxpay.exception.WxPayException;
26 28 import com.github.binarywang.wxpay.service.WxPayService;
... ... @@ -78,6 +80,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
78 80 client.setConfig(payConfig);
79 81 }
80 82  
  83 + // ============ 支付相关 ==========
  84 +
81 85 @Override
82 86 protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
83 87 try {
... ... @@ -114,6 +118,58 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
114 118 throws WxPayException;
115 119  
116 120 @Override
  121 + public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
  122 + try {
  123 + // 微信支付 v2 回调结果处理
  124 + switch (config.getApiVersion()) {
  125 + case API_VERSION_V2:
  126 + return parseOrderNotifyV2(body);
  127 + case WxPayClientConfig.API_VERSION_V3:
  128 + return parseOrderNotifyV3(body);
  129 + default:
  130 + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
  131 + }
  132 + } catch (WxPayException e) {
  133 + log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
  134 +// throw buildPayException(e);
  135 + throw new RuntimeException(e);
  136 + // TODO 芋艿:缺一个异常翻译
  137 + }
  138 + }
  139 +
  140 + private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
  141 + // 1. 解析回调
  142 + WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
  143 + // 2. 构建结果
  144 + return PayOrderRespDTO.builder()
  145 + .outTradeNo(response.getOutTradeNo())
  146 + .channelOrderNo(response.getTransactionId())
  147 + .channelUserId(response.getOpenid())
  148 + .status(Objects.equals(response.getResultCode(), "SUCCESS") ?
  149 + PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
  150 + .successTime(parseDateV2(response.getTimeEnd()))
  151 + .rawData(response)
  152 + .build();
  153 + }
  154 +
  155 + private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
  156 + // 1. 解析回调
  157 + WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
  158 + WxPayOrderNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
  159 + // 2. 构建结果
  160 + return PayOrderRespDTO.builder()
  161 + .outTradeNo(responseResult.getOutTradeNo())
  162 + .channelOrderNo(responseResult.getTradeState())
  163 + .channelUserId(responseResult.getPayer() != null ? responseResult.getPayer().getOpenid() : null)
  164 + .status(Objects.equals(responseResult.getTradeState(), "SUCCESS") ?
  165 + PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
  166 + .successTime(parseDateV3(responseResult.getSuccessTime()))
  167 + .build();
  168 + }
  169 +
  170 + // ============ 退款相关 ==========
  171 +
  172 + @Override
117 173 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
118 174 try {
119 175 switch (config.getApiVersion()) {
... ... @@ -140,73 +196,48 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
140 196 .setTotalFee(reqDTO.getPayPrice())
141 197 .setNotifyUrl(reqDTO.getNotifyUrl());
142 198 // 2.1 执行请求
143   - WxPayRefundResult response = client.refundV2(request); // TODO 芋艿:可以分成 V2 和 V3 的退款接口
  199 + WxPayRefundResult response = client.refundV2(request);
144 200 // 2.2 创建返回结果
145 201 PayRefundRespDTO refund = new PayRefundRespDTO()
146 202 .setOutRefundNo(reqDTO.getOutRefundNo())
147 203 .setRawData(response);
148 204 if (Objects.equals("SUCCESS", response.getResultCode())) {
149   - refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus());
150   - refund.setChannelRefundNo(response.getRefundId());
  205 + refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
  206 + .setChannelRefundNo(response.getRefundId());
151 207 } else {
152 208 refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
153   - // TODO 芋艿;异常的处理;
154 209 }
  210 + // TODO 芋艿;异常的处理;
155 211 return refund;
156 212 }
157 213  
158 214 private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
159   - return null;
160   - }
161   -
162   - @Override
163   - public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
164   - try {
165   - // 微信支付 v2 回调结果处理
166   - switch (config.getApiVersion()) {
167   - case API_VERSION_V2:
168   - return parseOrderNotifyV2(body);
169   - case WxPayClientConfig.API_VERSION_V3:
170   - return parseOrderNotifyV3(body);
171   - default:
172   - throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
173   - }
174   - } catch (WxPayException e) {
175   - log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
176   -// throw buildPayException(e);
177   - throw new RuntimeException(e);
178   - // TODO 芋艿:缺一个异常翻译
  215 + // 1. 构建 WxPayRefundRequest 请求
  216 + WxPayRefundV3Request request = new WxPayRefundV3Request()
  217 + .setOutTradeNo(reqDTO.getOutTradeNo())
  218 + .setOutRefundNo(reqDTO.getOutRefundNo())
  219 + .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice())
  220 + .setTotal(reqDTO.getPayPrice()).setCurrency("CNY"))
  221 + .setReason(reqDTO.getReason())
  222 + .setNotifyUrl(reqDTO.getNotifyUrl());
  223 + // 2.1 执行请求
  224 + WxPayRefundV3Result response = client.refundV3(request);
  225 + // 2.2 创建返回结果
  226 + PayRefundRespDTO refund = new PayRefundRespDTO()
  227 + .setOutRefundNo(reqDTO.getOutRefundNo())
  228 + .setRawData(response);
  229 + if (Objects.equals("SUCCESS", response.getStatus())) {
  230 + refund.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
  231 + .setChannelRefundNo(response.getRefundId())
  232 + .setSuccessTime(parseDateV3(response.getSuccessTime()));
  233 + } else if (Objects.equals("PROCESSING", response.getStatus())) {
  234 + refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
  235 + .setChannelRefundNo(response.getRefundId());
  236 + } else {
  237 + refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
179 238 }
180   - }
181   -
182   - private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
183   - // 1. 解析回调
184   - WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
185   - // 2. 构建结果
186   - return PayOrderRespDTO.builder()
187   - .outTradeNo(response.getOutTradeNo())
188   - .channelOrderNo(response.getTransactionId())
189   - .channelUserId(response.getOpenid())
190   - .status(Objects.equals(response.getResultCode(), "SUCCESS") ?
191   - PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
192   - .successTime(parseDateV2(response.getTimeEnd()))
193   - .rawData(response)
194   - .build();
195   - }
196   -
197   - private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
198   - WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
199   - WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
200   - // TODO 芋艿:翻译下 state
201   - // 转换结果
202   - Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
203   - "支付结果非 SUCCESS");
204   - return PayOrderRespDTO.builder()
205   - .outTradeNo(result.getOutTradeNo())
206   - .channelOrderNo(result.getTradeState())
207   - .channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null)
208   - .successTime(parseDateV3(result.getSuccessTime()))
209   - .build();
  239 + // TODO 芋艿;异常的处理;
  240 + return refund;
210 241 }
211 242  
212 243 @Override
... ... @@ -229,27 +260,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
229 260 }
230 261 }
231 262  
  263 + @SuppressWarnings("DuplicatedCode")
232 264 private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
233 265 // 1. 解析回调
234 266 WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
235   - WxPayRefundNotifyResult.ReqInfo reqInfo = response.getReqInfo();
  267 + WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
236 268 // 2. 构建结果
237 269 PayRefundRespDTO notify = new PayRefundRespDTO()
238   - .setChannelRefundNo(reqInfo.getRefundId())
239   - .setOutRefundNo(reqInfo.getOutRefundNo())
  270 + .setChannelRefundNo(responseResult.getRefundId())
  271 + .setOutRefundNo(responseResult.getOutRefundNo())
240 272 .setRawData(response);
241   - if (Objects.equals("SUCCESS", reqInfo.getRefundStatus())) {
  273 + if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
242 274 notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
243   - .setSuccessTime(parseDateV2B(reqInfo.getSuccessTime()));
  275 + .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
244 276 } else {
245 277 notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
246 278 }
247 279 return notify;
248 280 }
249 281  
  282 + @SuppressWarnings("DuplicatedCode")
250 283 private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
251   - // TODO 芋艿:未实现
252   - return null;
  284 + // 1. 解析回调
  285 + WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
  286 + WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
  287 + // 2. 构建结果
  288 + PayRefundRespDTO notify = new PayRefundRespDTO()
  289 + .setChannelRefundNo(responseResult.getRefundId())
  290 + .setOutRefundNo(responseResult.getOutRefundNo())
  291 + .setRawData(response);
  292 + if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
  293 + notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
  294 + .setSuccessTime(parseDateV3(responseResult.getSuccessTime()));
  295 + } else {
  296 + notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
  297 + }
  298 + return notify;
253 299 }
254 300  
255 301 // ========== 各种工具方法 ==========
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxNativePayClient.java
... ... @@ -55,13 +55,13 @@ public class WxNativePayClient extends AbstractWxPayClient {
55 55 @Override
56 56 protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
57 57 // 构建 WxPayUnifiedOrderRequest 对象
58   - WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
59   - request.setOutTradeNo(reqDTO.getOutTradeNo());
60   - request.setDescription(reqDTO.getBody());
61   - request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分
62   - request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
63   - request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
64   - request.setNotifyUrl(reqDTO.getNotifyUrl());
  58 + WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request()
  59 + .setOutTradeNo(reqDTO.getOutTradeNo())
  60 + .setDescription(reqDTO.getSubject())
  61 + .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())) // 单位分
  62 + .setTimeExpire(formatDateV3(reqDTO.getExpireTime()))
  63 + .setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()))
  64 + .setNotifyUrl(reqDTO.getNotifyUrl());
65 65 // 执行请求
66 66 String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
67 67  
... ...
yudao-ui-admin/src/views/pay/demo/index.vue
... ... @@ -41,7 +41,8 @@
41 41 </el-table-column>
42 42 <el-table-column label="退款时间" align="center" prop="refundTime" width="180">
43 43 <template v-slot="scope">
44   - <span>{{ parseTime(scope.row.refundTime) }}</span>
  44 + <span v-if="scope.row.refundTime">{{ parseTime(scope.row.refundTime) }}</span>
  45 + <span v-else-if="scope.row.payRefundId">退款中,等待退款结果</span>
45 46 </template>
46 47 </el-table-column>
47 48 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
... ...