Browse Source

mall + pay:
1. 完善微信支付的 V3 支付回调、退款回调

YunaiV 1 year ago
parent
commit
d0a7f41875

+ 4 - 0
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
         return channelId;
     }
 
+    // ============ 支付相关 ==========
+
     @Override
     public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
         Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
@@ -91,6 +93,8 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
     protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
             throws Throwable;
 
+    // ============ 退款相关 ==========
+
     @Override
     public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
         Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);

+ 108 - 62
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;
 import cn.hutool.core.codec.Base64;
 import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.date.TemporalAccessorUtil;
-import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
 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;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
 import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
 import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
@@ -78,6 +80,8 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         client.setConfig(payConfig);
     }
 
+    // ============ 支付相关 ==========
+
     @Override
     protected PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
         try {
@@ -114,6 +118,58 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
             throws WxPayException;
 
     @Override
+    public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
+        try {
+            // 微信支付 v2 回调结果处理
+            switch (config.getApiVersion()) {
+                case API_VERSION_V2:
+                    return parseOrderNotifyV2(body);
+                case WxPayClientConfig.API_VERSION_V3:
+                    return parseOrderNotifyV3(body);
+                default:
+                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+            }
+        } catch (WxPayException e) {
+            log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
+//            throw buildPayException(e);
+            throw new RuntimeException(e);
+            // TODO 芋艿:缺一个异常翻译
+        }
+    }
+
+    private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
+        // 1. 解析回调
+        WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
+        // 2. 构建结果
+        return PayOrderRespDTO.builder()
+                .outTradeNo(response.getOutTradeNo())
+                .channelOrderNo(response.getTransactionId())
+                .channelUserId(response.getOpenid())
+                .status(Objects.equals(response.getResultCode(), "SUCCESS") ?
+                        PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
+                .successTime(parseDateV2(response.getTimeEnd()))
+                .rawData(response)
+                .build();
+    }
+
+    private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
+        // 1. 解析回调
+        WxPayOrderNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
+        WxPayOrderNotifyV3Result.DecryptNotifyResult responseResult = response.getResult();
+        // 2. 构建结果
+        return PayOrderRespDTO.builder()
+                .outTradeNo(responseResult.getOutTradeNo())
+                .channelOrderNo(responseResult.getTradeState())
+                .channelUserId(responseResult.getPayer() != null ? responseResult.getPayer().getOpenid() : null)
+                .status(Objects.equals(responseResult.getTradeState(), "SUCCESS") ?
+                        PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
+                .successTime(parseDateV3(responseResult.getSuccessTime()))
+                .build();
+    }
+
+    // ============ 退款相关 ==========
+
+    @Override
     protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
         try {
             switch (config.getApiVersion()) {
@@ -140,73 +196,48 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
                 .setTotalFee(reqDTO.getPayPrice())
                 .setNotifyUrl(reqDTO.getNotifyUrl());
         // 2.1 执行请求
-        WxPayRefundResult response = client.refundV2(request); // TODO 芋艿:可以分成 V2 和 V3 的退款接口
+        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());
-            refund.setChannelRefundNo(response.getRefundId());
+            refund.setStatus(PayRefundStatusRespEnum.WAITING.getStatus())
+                    .setChannelRefundNo(response.getRefundId());
         } else {
             refund.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
-            // TODO 芋艿;异常的处理;
         }
+        // TODO 芋艿;异常的处理;
         return refund;
     }
 
     private PayRefundRespDTO doUnifiedRefundV3(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
-        return null;
-    }
-
-    @Override
-    public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
-        try {
-            // 微信支付 v2 回调结果处理
-            switch (config.getApiVersion()) {
-                case API_VERSION_V2:
-                    return parseOrderNotifyV2(body);
-                case WxPayClientConfig.API_VERSION_V3:
-                    return parseOrderNotifyV3(body);
-                default:
-                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
-            }
-        } catch (WxPayException e) {
-            log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
-//            throw buildPayException(e);
-            throw new RuntimeException(e);
-            // TODO 芋艿:缺一个异常翻译
+        // 1. 构建 WxPayRefundRequest 请求
+        WxPayRefundV3Request request = new WxPayRefundV3Request()
+                .setOutTradeNo(reqDTO.getOutTradeNo())
+                .setOutRefundNo(reqDTO.getOutRefundNo())
+                .setAmount(new WxPayRefundV3Request.Amount().setRefund(reqDTO.getRefundPrice())
+                        .setTotal(reqDTO.getPayPrice()).setCurrency("CNY"))
+                .setReason(reqDTO.getReason())
+                .setNotifyUrl(reqDTO.getNotifyUrl());
+        // 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());
         }
-    }
-
-    private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
-        // 1. 解析回调
-        WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
-        // 2. 构建结果
-        return PayOrderRespDTO.builder()
-                .outTradeNo(response.getOutTradeNo())
-                .channelOrderNo(response.getTransactionId())
-                .channelUserId(response.getOpenid())
-                .status(Objects.equals(response.getResultCode(), "SUCCESS") ?
-                        PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
-                .successTime(parseDateV2(response.getTimeEnd()))
-                .rawData(response)
-                .build();
-    }
-
-    private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
-        WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
-        WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
-        // TODO 芋艿:翻译下 state
-        // 转换结果
-        Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
-                "支付结果非 SUCCESS");
-        return PayOrderRespDTO.builder()
-                .outTradeNo(result.getOutTradeNo())
-                .channelOrderNo(result.getTradeState())
-                .channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null)
-                .successTime(parseDateV3(result.getSuccessTime()))
-                .build();
+        // TODO 芋艿;异常的处理;
+        return refund;
     }
 
     @Override
@@ -229,27 +260,42 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         }
     }
 
+    @SuppressWarnings("DuplicatedCode")
     private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
         // 1. 解析回调
         WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
-        WxPayRefundNotifyResult.ReqInfo reqInfo = response.getReqInfo();
+        WxPayRefundNotifyResult.ReqInfo responseResult = response.getReqInfo();
         // 2. 构建结果
         PayRefundRespDTO notify = new PayRefundRespDTO()
-                .setChannelRefundNo(reqInfo.getRefundId())
-                .setOutRefundNo(reqInfo.getOutRefundNo())
+                .setChannelRefundNo(responseResult.getRefundId())
+                .setOutRefundNo(responseResult.getOutRefundNo())
                 .setRawData(response);
-        if (Objects.equals("SUCCESS", reqInfo.getRefundStatus())) {
+        if (Objects.equals("SUCCESS", responseResult.getRefundStatus())) {
             notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
-                    .setSuccessTime(parseDateV2B(reqInfo.getSuccessTime()));
+                    .setSuccessTime(parseDateV2B(responseResult.getSuccessTime()));
         } else {
             notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
         }
         return notify;
     }
 
+    @SuppressWarnings("DuplicatedCode")
     private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
-        // TODO 芋艿:未实现
-        return null;
+        // 1. 解析回调
+        WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
+        WxPayRefundNotifyV3Result.DecryptNotifyResult responseResult = 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());
+        }
+        return notify;
     }
 
     // ========== 各种工具方法 ==========

+ 7 - 7
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 {
     @Override
     protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
         // 构建 WxPayUnifiedOrderRequest 对象
-        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
-        request.setOutTradeNo(reqDTO.getOutTradeNo());
-        request.setDescription(reqDTO.getBody());
-        request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分
-        request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
-        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
-        request.setNotifyUrl(reqDTO.getNotifyUrl());
+        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request()
+                .setOutTradeNo(reqDTO.getOutTradeNo())
+                .setDescription(reqDTO.getSubject())
+                .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())) // 单位分
+                .setTimeExpire(formatDateV3(reqDTO.getExpireTime()))
+                .setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()))
+                .setNotifyUrl(reqDTO.getNotifyUrl());
         // 执行请求
         String response = client.createOrderV3(TradeTypeEnum.NATIVE, request);
 

+ 2 - 1
yudao-ui-admin/src/views/pay/demo/index.vue

@@ -41,7 +41,8 @@
       </el-table-column>
       <el-table-column label="退款时间" align="center" prop="refundTime" width="180">
         <template v-slot="scope">
-          <span>{{ parseTime(scope.row.refundTime) }}</span>
+          <span v-if="scope.row.refundTime">{{ parseTime(scope.row.refundTime) }}</span>
+          <span v-else-if="scope.row.payRefundId">退款中,等待退款结果</span>
         </template>
       </el-table-column>
       <el-table-column label="操作" align="center" class-name="small-padding fixed-width">