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,6 +70,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
70 return channelId; 70 return channelId;
71 } 71 }
72 72
  73 + // ============ 支付相关 ==========
  74 +
73 @Override 75 @Override
74 public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { 76 public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
75 Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); 77 Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
@@ -91,6 +93,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen @@ -91,6 +93,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
91 protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) 93 protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
92 throws Throwable; 94 throws Throwable;
93 95
  96 + // ============ 退款相关 ==========
  97 +
94 @Override 98 @Override
95 public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { 99 public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
96 Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO); 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,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.codec.Base64; 4 import cn.hutool.core.codec.Base64;
5 import cn.hutool.core.date.LocalDateTimeUtil; 5 import cn.hutool.core.date.LocalDateTimeUtil;
6 import cn.hutool.core.date.TemporalAccessorUtil; 6 import cn.hutool.core.date.TemporalAccessorUtil;
7 -import cn.hutool.core.lang.Assert;  
8 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
9 import cn.iocoder.yudao.framework.common.util.io.FileUtils; 8 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
10 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; 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,8 +18,11 @@ import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
19 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; 18 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
20 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; 19 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
21 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; 20 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
  21 +import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
22 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; 22 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
  23 +import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
23 import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; 24 import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
  25 +import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
24 import com.github.binarywang.wxpay.config.WxPayConfig; 26 import com.github.binarywang.wxpay.config.WxPayConfig;
25 import com.github.binarywang.wxpay.exception.WxPayException; 27 import com.github.binarywang.wxpay.exception.WxPayException;
26 import com.github.binarywang.wxpay.service.WxPayService; 28 import com.github.binarywang.wxpay.service.WxPayService;
@@ -78,6 +80,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -78,6 +80,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
78 client.setConfig(payConfig); 80 client.setConfig(payConfig);
79 } 81 }
80 82
  83 + // ============ 支付相关 ==========
  84 +
81 @Override 85 @Override
82 protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception { 86 protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
83 try { 87 try {
@@ -114,6 +118,58 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC @@ -114,6 +118,58 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
114 throws WxPayException; 118 throws WxPayException;
115 119
116 @Override 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 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 173 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
118 try { 174 try {
119 switch (config.getApiVersion()) { 175 switch (config.getApiVersion()) {
@@ -140,73 +196,48 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC @@ -140,73 +196,48 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
140 .setTotalFee(reqDTO.getPayPrice()) 196 .setTotalFee(reqDTO.getPayPrice())
141 .setNotifyUrl(reqDTO.getNotifyUrl()); 197 .setNotifyUrl(reqDTO.getNotifyUrl());
142 // 2.1 执行请求 198 // 2.1 执行请求
143 - WxPayRefundResult response = client.refundV2(request); // TODO 芋艿:可以分成 V2 和 V3 的退款接口 199 + WxPayRefundResult response = client.refundV2(request);
144 // 2.2 创建返回结果 200 // 2.2 创建返回结果
145 PayRefundRespDTO refund = new PayRefundRespDTO() 201 PayRefundRespDTO refund = new PayRefundRespDTO()
146 .setOutRefundNo(reqDTO.getOutRefundNo()) 202 .setOutRefundNo(reqDTO.getOutRefundNo())
147 .setRawData(response); 203 .setRawData(response);
148 if (Objects.equals("SUCCESS", response.getResultCode())) { 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 } else { 207 } else {
152 refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()); 208 refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
153 - // TODO 芋艿;异常的处理;  
154 } 209 }
  210 + // TODO 芋艿;异常的处理;
155 return refund; 211 return refund;
156 } 212 }
157 213
158 private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 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 @Override 243 @Override
@@ -229,27 +260,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC @@ -229,27 +260,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
229 } 260 }
230 } 261 }
231 262
  263 + @SuppressWarnings("DuplicatedCode")
232 private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException { 264 private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
233 // 1. 解析回调 265 // 1. 解析回调
234 WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body); 266 WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
235 - WxPayRefundNotifyResult.ReqInfo reqInfo = response.getReqInfo(); 267 + WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
236 // 2. 构建结果 268 // 2. 构建结果
237 PayRefundRespDTO notify = new PayRefundRespDTO() 269 PayRefundRespDTO notify = new PayRefundRespDTO()
238 - .setChannelRefundNo(reqInfo.getRefundId())  
239 - .setOutRefundNo(reqInfo.getOutRefundNo()) 270 + .setChannelRefundNo(responseResult.getRefundId())
  271 + .setOutRefundNo(responseResult.getOutRefundNo())
240 .setRawData(response); 272 .setRawData(response);
241 - if (Objects.equals("SUCCESS", reqInfo.getRefundStatus())) { 273 + if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
242 notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()) 274 notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
243 - .setSuccessTime(parseDateV2B(reqInfo.getSuccessTime())); 275 + .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
244 } else { 276 } else {
245 notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()); 277 notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
246 } 278 }
247 return notify; 279 return notify;
248 } 280 }
249 281
  282 + @SuppressWarnings("DuplicatedCode")
250 private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException { 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,13 +55,13 @@ public class WxNativePayClient extends AbstractWxPayClient {
55 @Override 55 @Override
56 protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { 56 protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
57 // 构建 WxPayUnifiedOrderRequest 对象 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 String response = client.createOrderV3(TradeTypeEnum.NATIVE, request); 66 String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
67 67
yudao-ui-admin/src/views/pay/demo/index.vue
@@ -41,7 +41,8 @@ @@ -41,7 +41,8 @@
41 </el-table-column> 41 </el-table-column>
42 <el-table-column label="退款时间" align="center" prop="refundTime" width="180"> 42 <el-table-column label="退款时间" align="center" prop="refundTime" width="180">
43 <template v-slot="scope"> 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 </template> 46 </template>
46 </el-table-column> 47 </el-table-column>
47 <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> 48 <el-table-column label="操作" align="center" class-name="small-padding fixed-width">