浏览代码

mall + pay:
1. 增加支付宝 Client 的查询订单接口

YunaiV 2 年之前
父节点
当前提交
f46a037164

+ 9 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java

@@ -27,7 +27,7 @@ public interface PayClient {
      * 调用支付渠道,统一下单
      *
      * @param reqDTO 下单信息
-     * @return 各支付渠道的返回结果
+     * @return 支付订单信息
      */
     PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
 
@@ -40,6 +40,14 @@ public interface PayClient {
      */
     PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body);
 
+    /**
+     * 获得支付订单信息
+     *
+     * @param outTradeNo 外部订单号
+     * @return 支付订单信息
+     */
+    PayOrderRespDTO getOrder(String outTradeNo);
+
     // ============ 退款相关 ==========
 
     /**

+ 23 - 21
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderRespDTO.java

@@ -94,38 +94,40 @@ public class PayOrderRespDTO {
     /**
      * 创建【SUCCESS】状态的订单返回
      */
-    public PayOrderRespDTO(String channelOrderNo, String channelUserId, LocalDateTime successTime,
-                           String outTradeNo, Object rawData) {
-        this.status = PayOrderStatusRespEnum.SUCCESS.getStatus();
-        this.channelOrderNo = channelOrderNo;
-        this.channelUserId = channelUserId;
-        this.successTime = successTime;
+    public static PayOrderRespDTO successOf(String channelOrderNo, String channelUserId, LocalDateTime successTime,
+                                            String outTradeNo, Object rawData) {
+        PayOrderRespDTO respDTO = new PayOrderRespDTO();
+        respDTO.status = PayOrderStatusRespEnum.SUCCESS.getStatus();
+        respDTO.channelOrderNo = channelOrderNo;
+        respDTO.channelUserId = channelUserId;
+        respDTO.successTime = successTime;
         // 相对通用的字段
-        this.outTradeNo = outTradeNo;
-        this.rawData = rawData;
+        respDTO.outTradeNo = outTradeNo;
+        respDTO.rawData = rawData;
+        return respDTO;
     }
 
     /**
-     * 创建【SUCCESS】或【CLOSED】状态的订单返回,适合支付渠道回调时
+     * 创建指定状态的订单返回,适合支付渠道回调时
      */
-    public PayOrderRespDTO(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime,
-                           String outTradeNo, Object rawData) {
-        this.status = status;
-        this.channelOrderNo = channelOrderNo;
-        this.channelUserId = channelUserId;
-        this.successTime = successTime;
+    public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime,
+                                     String outTradeNo, Object rawData) {
+        PayOrderRespDTO respDTO = new PayOrderRespDTO();
+        respDTO.status = status;
+        respDTO.channelOrderNo = channelOrderNo;
+        respDTO.channelUserId = channelUserId;
+        respDTO.successTime = successTime;
         // 相对通用的字段
-        this.outTradeNo = outTradeNo;
-        this.rawData = rawData;
+        respDTO.outTradeNo = outTradeNo;
+        respDTO.rawData = rawData;
+        return respDTO;
     }
 
     /**
      * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时
-     *
-     * 参数和 {@link #PayOrderRespDTO(String, String, String, Object)} 冲突,所以独立个方法出来
      */
-    public static PayOrderRespDTO build(String channelErrorCode, String channelErrorMsg,
-                                        String outTradeNo, Object rawData) {
+    public static PayOrderRespDTO closedOf(String channelErrorCode, String channelErrorMsg,
+                                           String outTradeNo, Object rawData) {
         PayOrderRespDTO respDTO = new PayOrderRespDTO();
         respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus();
         respDTO.channelErrorCode = channelErrorCode;

+ 14 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -107,6 +107,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
     protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body)
             throws Throwable;
 
+    @Override
+    public PayOrderRespDTO getOrder(String outTradeNo) {
+        try {
+            return doGetOrder(outTradeNo);
+        } catch (Throwable ex) {
+            log.error("[getOrder][客户端({}) outTradeNo({}) 查询支付单异常]",
+                    getId(), outTradeNo, ex);
+            throw buildPayException(ex);
+        }
+    }
+
+    protected abstract PayOrderRespDTO doGetOrder(String outTradeNo)
+            throws Throwable;
+
     // ============ 退款相关 ==========
 
     @Override

+ 36 - 6
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -17,9 +17,12 @@ import com.alipay.api.AlipayApiException;
 import com.alipay.api.AlipayConfig;
 import com.alipay.api.AlipayResponse;
 import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeQueryModel;
 import com.alipay.api.domain.AlipayTradeRefundModel;
 import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeQueryRequest;
 import com.alipay.api.request.AlipayTradeRefundRequest;
+import com.alipay.api.response.AlipayTradeQueryResponse;
 import com.alipay.api.response.AlipayTradeRefundResponse;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
@@ -63,7 +66,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
      */
     protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) {
         Assert.isFalse(response.isSuccess());
-        return PayOrderRespDTO.build(response.getSubCode(), response.getSubMsg(),
+        return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
                 reqDTO.getOutTradeNo(), response);
     }
 
@@ -76,10 +79,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
 
         // 2. 解析订单的状态
         // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
-        String tradeStatus = bodyObj.get("trade_status");
-        Integer status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus()
-                : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus()
-                : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null;
+        Integer status = parseStatus(bodyObj.get("trade_status"));
         // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功
         if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) {
             status = PayOrderStatusRespEnum.REFUND.getStatus();
@@ -87,10 +87,40 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
         Assert.notNull(status, (Supplier<Throwable>) () -> {
             throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
         });
-        return new PayOrderRespDTO(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")),
+        return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")),
                 bodyObj.get("out_trade_no"), body);
     }
 
+    @Override
+    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
+        // 1.1 构建 AlipayTradeRefundModel 请求
+        AlipayTradeQueryModel model = new AlipayTradeQueryModel();
+        model.setOutTradeNo(outTradeNo);
+        // 1.2 构建 AlipayTradeQueryRequest 请求
+        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+        request.setBizModel(model);
+
+        // 2.1 执行请求
+        AlipayTradeQueryResponse response =  client.execute(request);
+        if (!response.isSuccess()) {
+            return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
+                    outTradeNo, response);
+        }
+        // 2.2 解析订单的状态
+        Integer status = parseStatus(response.getTradeStatus());
+        Assert.notNull(status, (Supplier<Throwable>) () -> {
+            throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody()));
+        });
+        return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()),
+                outTradeNo, response);
+    }
+
+    private Integer parseStatus(String tradeStatus) {
+        return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus()
+                : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus()
+                : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null;
+    }
+
     // ============ 退款相关 ==========
 
     /**

+ 8 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

@@ -90,7 +90,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         } catch (WxPayException e) {
             String errorCode = getErrorCode(e);
             String errorMessage = getErrorMessage(e);
-            return PayOrderRespDTO.build(errorCode, errorMessage,
+            return PayOrderRespDTO.closedOf(errorCode, errorMessage,
                     reqDTO.getOutTradeNo(), e.getXmlString());
         }
     }
@@ -133,7 +133,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂
         Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
                 PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
-        return new PayOrderRespDTO(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
+        return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
                 response.getOutTradeNo(), body);
     }
 
@@ -146,10 +146,15 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ?
                 PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus();
         String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
-        return new PayOrderRespDTO(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()),
+        return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()),
                 result.getOutTradeNo(), body);
     }
 
+    @Override
+    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable {
+        return null;
+    }
+
     // ============ 退款相关 ==========
 
     @Override

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/WxBarPayClient.java

@@ -68,7 +68,7 @@ public class WxBarPayClient extends AbstractWxPayClient {
             try {
                 WxPayMicropayResult response = client.micropay(request);
                 // 支付成功,例如说:1)用户输入了密码;2)用户免密支付
-                return new PayOrderRespDTO(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
+                return PayOrderRespDTO.successOf(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
                         response.getOutTradeNo(), response)
                         .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode());
             } catch (WxPayException ex) {