Commit f46a0371640d4ceb14e361a2284c9b51455c2285
1 parent
b426c63e
mall + pay:
1. 增加支付宝 Client 的查询订单接口
Showing
6 changed files
with
91 additions
and
32 deletions
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 { | @@ -27,7 +27,7 @@ public interface PayClient { | ||
| 27 | * 调用支付渠道,统一下单 | 27 | * 调用支付渠道,统一下单 |
| 28 | * | 28 | * |
| 29 | * @param reqDTO 下单信息 | 29 | * @param reqDTO 下单信息 |
| 30 | - * @return 各支付渠道的返回结果 | 30 | + * @return 支付订单信息 |
| 31 | */ | 31 | */ |
| 32 | PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); | 32 | PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); |
| 33 | 33 | ||
| @@ -40,6 +40,14 @@ public interface PayClient { | @@ -40,6 +40,14 @@ public interface PayClient { | ||
| 40 | */ | 40 | */ |
| 41 | PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body); | 41 | PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body); |
| 42 | 42 | ||
| 43 | + /** | ||
| 44 | + * 获得支付订单信息 | ||
| 45 | + * | ||
| 46 | + * @param outTradeNo 外部订单号 | ||
| 47 | + * @return 支付订单信息 | ||
| 48 | + */ | ||
| 49 | + PayOrderRespDTO getOrder(String outTradeNo); | ||
| 50 | + | ||
| 43 | // ============ 退款相关 ========== | 51 | // ============ 退款相关 ========== |
| 44 | 52 | ||
| 45 | /** | 53 | /** |
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 { | @@ -94,38 +94,40 @@ public class PayOrderRespDTO { | ||
| 94 | /** | 94 | /** |
| 95 | * 创建【SUCCESS】状态的订单返回 | 95 | * 创建【SUCCESS】状态的订单返回 |
| 96 | */ | 96 | */ |
| 97 | - public PayOrderRespDTO(String channelOrderNo, String channelUserId, LocalDateTime successTime, | ||
| 98 | - String outTradeNo, Object rawData) { | ||
| 99 | - this.status = PayOrderStatusRespEnum.SUCCESS.getStatus(); | ||
| 100 | - this.channelOrderNo = channelOrderNo; | ||
| 101 | - this.channelUserId = channelUserId; | ||
| 102 | - this.successTime = successTime; | 97 | + public static PayOrderRespDTO successOf(String channelOrderNo, String channelUserId, LocalDateTime successTime, |
| 98 | + String outTradeNo, Object rawData) { | ||
| 99 | + PayOrderRespDTO respDTO = new PayOrderRespDTO(); | ||
| 100 | + respDTO.status = PayOrderStatusRespEnum.SUCCESS.getStatus(); | ||
| 101 | + respDTO.channelOrderNo = channelOrderNo; | ||
| 102 | + respDTO.channelUserId = channelUserId; | ||
| 103 | + respDTO.successTime = successTime; | ||
| 103 | // 相对通用的字段 | 104 | // 相对通用的字段 |
| 104 | - this.outTradeNo = outTradeNo; | ||
| 105 | - this.rawData = rawData; | 105 | + respDTO.outTradeNo = outTradeNo; |
| 106 | + respDTO.rawData = rawData; | ||
| 107 | + return respDTO; | ||
| 106 | } | 108 | } |
| 107 | 109 | ||
| 108 | /** | 110 | /** |
| 109 | - * 创建【SUCCESS】或【CLOSED】状态的订单返回,适合支付渠道回调时 | 111 | + * 创建指定状态的订单返回,适合支付渠道回调时 |
| 110 | */ | 112 | */ |
| 111 | - public PayOrderRespDTO(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime, | ||
| 112 | - String outTradeNo, Object rawData) { | ||
| 113 | - this.status = status; | ||
| 114 | - this.channelOrderNo = channelOrderNo; | ||
| 115 | - this.channelUserId = channelUserId; | ||
| 116 | - this.successTime = successTime; | 113 | + public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime, |
| 114 | + String outTradeNo, Object rawData) { | ||
| 115 | + PayOrderRespDTO respDTO = new PayOrderRespDTO(); | ||
| 116 | + respDTO.status = status; | ||
| 117 | + respDTO.channelOrderNo = channelOrderNo; | ||
| 118 | + respDTO.channelUserId = channelUserId; | ||
| 119 | + respDTO.successTime = successTime; | ||
| 117 | // 相对通用的字段 | 120 | // 相对通用的字段 |
| 118 | - this.outTradeNo = outTradeNo; | ||
| 119 | - this.rawData = rawData; | 121 | + respDTO.outTradeNo = outTradeNo; |
| 122 | + respDTO.rawData = rawData; | ||
| 123 | + return respDTO; | ||
| 120 | } | 124 | } |
| 121 | 125 | ||
| 122 | /** | 126 | /** |
| 123 | * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时 | 127 | * 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时 |
| 124 | - * | ||
| 125 | - * 参数和 {@link #PayOrderRespDTO(String, String, String, Object)} 冲突,所以独立个方法出来 | ||
| 126 | */ | 128 | */ |
| 127 | - public static PayOrderRespDTO build(String channelErrorCode, String channelErrorMsg, | ||
| 128 | - String outTradeNo, Object rawData) { | 129 | + public static PayOrderRespDTO closedOf(String channelErrorCode, String channelErrorMsg, |
| 130 | + String outTradeNo, Object rawData) { | ||
| 129 | PayOrderRespDTO respDTO = new PayOrderRespDTO(); | 131 | PayOrderRespDTO respDTO = new PayOrderRespDTO(); |
| 130 | respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus(); | 132 | respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus(); |
| 131 | respDTO.channelErrorCode = channelErrorCode; | 133 | respDTO.channelErrorCode = channelErrorCode; |
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 | @@ -107,6 +107,20 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen | ||
| 107 | protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) | 107 | protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) |
| 108 | throws Throwable; | 108 | throws Throwable; |
| 109 | 109 | ||
| 110 | + @Override | ||
| 111 | + public PayOrderRespDTO getOrder(String outTradeNo) { | ||
| 112 | + try { | ||
| 113 | + return doGetOrder(outTradeNo); | ||
| 114 | + } catch (Throwable ex) { | ||
| 115 | + log.error("[getOrder][客户端({}) outTradeNo({}) 查询支付单异常]", | ||
| 116 | + getId(), outTradeNo, ex); | ||
| 117 | + throw buildPayException(ex); | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + protected abstract PayOrderRespDTO doGetOrder(String outTradeNo) | ||
| 122 | + throws Throwable; | ||
| 123 | + | ||
| 110 | // ============ 退款相关 ========== | 124 | // ============ 退款相关 ========== |
| 111 | 125 | ||
| 112 | @Override | 126 | @Override |
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; | @@ -17,9 +17,12 @@ import com.alipay.api.AlipayApiException; | ||
| 17 | import com.alipay.api.AlipayConfig; | 17 | import com.alipay.api.AlipayConfig; |
| 18 | import com.alipay.api.AlipayResponse; | 18 | import com.alipay.api.AlipayResponse; |
| 19 | import com.alipay.api.DefaultAlipayClient; | 19 | import com.alipay.api.DefaultAlipayClient; |
| 20 | +import com.alipay.api.domain.AlipayTradeQueryModel; | ||
| 20 | import com.alipay.api.domain.AlipayTradeRefundModel; | 21 | import com.alipay.api.domain.AlipayTradeRefundModel; |
| 21 | import com.alipay.api.internal.util.AlipaySignature; | 22 | import com.alipay.api.internal.util.AlipaySignature; |
| 23 | +import com.alipay.api.request.AlipayTradeQueryRequest; | ||
| 22 | import com.alipay.api.request.AlipayTradeRefundRequest; | 24 | import com.alipay.api.request.AlipayTradeRefundRequest; |
| 25 | +import com.alipay.api.response.AlipayTradeQueryResponse; | ||
| 23 | import com.alipay.api.response.AlipayTradeRefundResponse; | 26 | import com.alipay.api.response.AlipayTradeRefundResponse; |
| 24 | import lombok.SneakyThrows; | 27 | import lombok.SneakyThrows; |
| 25 | import lombok.extern.slf4j.Slf4j; | 28 | import lombok.extern.slf4j.Slf4j; |
| @@ -63,7 +66,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | @@ -63,7 +66,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | ||
| 63 | */ | 66 | */ |
| 64 | protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { | 67 | protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { |
| 65 | Assert.isFalse(response.isSuccess()); | 68 | Assert.isFalse(response.isSuccess()); |
| 66 | - return PayOrderRespDTO.build(response.getSubCode(), response.getSubMsg(), | 69 | + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), |
| 67 | reqDTO.getOutTradeNo(), response); | 70 | reqDTO.getOutTradeNo(), response); |
| 68 | } | 71 | } |
| 69 | 72 | ||
| @@ -76,10 +79,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | @@ -76,10 +79,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | ||
| 76 | 79 | ||
| 77 | // 2. 解析订单的状态 | 80 | // 2. 解析订单的状态 |
| 78 | // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 | 81 | // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 |
| 79 | - String tradeStatus = bodyObj.get("trade_status"); | ||
| 80 | - Integer status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus() | ||
| 81 | - : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus() | ||
| 82 | - : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null; | 82 | + Integer status = parseStatus(bodyObj.get("trade_status")); |
| 83 | // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功 | 83 | // 特殊逻辑: 支付宝没有退款成功的状态,所以,如果有退款金额,我们认为是退款成功 |
| 84 | if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { | 84 | if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { |
| 85 | status = PayOrderStatusRespEnum.REFUND.getStatus(); | 85 | status = PayOrderStatusRespEnum.REFUND.getStatus(); |
| @@ -87,10 +87,40 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | @@ -87,10 +87,40 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa | ||
| 87 | Assert.notNull(status, (Supplier<Throwable>) () -> { | 87 | Assert.notNull(status, (Supplier<Throwable>) () -> { |
| 88 | throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body)); | 88 | throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body)); |
| 89 | }); | 89 | }); |
| 90 | - return new PayOrderRespDTO(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), | 90 | + return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), |
| 91 | bodyObj.get("out_trade_no"), body); | 91 | bodyObj.get("out_trade_no"), body); |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | + @Override | ||
| 95 | + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { | ||
| 96 | + // 1.1 构建 AlipayTradeRefundModel 请求 | ||
| 97 | + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); | ||
| 98 | + model.setOutTradeNo(outTradeNo); | ||
| 99 | + // 1.2 构建 AlipayTradeQueryRequest 请求 | ||
| 100 | + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); | ||
| 101 | + request.setBizModel(model); | ||
| 102 | + | ||
| 103 | + // 2.1 执行请求 | ||
| 104 | + AlipayTradeQueryResponse response = client.execute(request); | ||
| 105 | + if (!response.isSuccess()) { | ||
| 106 | + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), | ||
| 107 | + outTradeNo, response); | ||
| 108 | + } | ||
| 109 | + // 2.2 解析订单的状态 | ||
| 110 | + Integer status = parseStatus(response.getTradeStatus()); | ||
| 111 | + Assert.notNull(status, (Supplier<Throwable>) () -> { | ||
| 112 | + throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); | ||
| 113 | + }); | ||
| 114 | + return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), | ||
| 115 | + outTradeNo, response); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + private Integer parseStatus(String tradeStatus) { | ||
| 119 | + return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus() | ||
| 120 | + : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus() | ||
| 121 | + : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null; | ||
| 122 | + } | ||
| 123 | + | ||
| 94 | // ============ 退款相关 ========== | 124 | // ============ 退款相关 ========== |
| 95 | 125 | ||
| 96 | /** | 126 | /** |
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 | @@ -90,7 +90,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | ||
| 90 | } catch (WxPayException e) { | 90 | } catch (WxPayException e) { |
| 91 | String errorCode = getErrorCode(e); | 91 | String errorCode = getErrorCode(e); |
| 92 | String errorMessage = getErrorMessage(e); | 92 | String errorMessage = getErrorMessage(e); |
| 93 | - return PayOrderRespDTO.build(errorCode, errorMessage, | 93 | + return PayOrderRespDTO.closedOf(errorCode, errorMessage, |
| 94 | reqDTO.getOutTradeNo(), e.getXmlString()); | 94 | reqDTO.getOutTradeNo(), e.getXmlString()); |
| 95 | } | 95 | } |
| 96 | } | 96 | } |
| @@ -133,7 +133,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | @@ -133,7 +133,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | ||
| 133 | // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 | 133 | // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 |
| 134 | Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? | 134 | Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? |
| 135 | PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); | 135 | PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); |
| 136 | - return new PayOrderRespDTO(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), | 136 | + return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), |
| 137 | response.getOutTradeNo(), body); | 137 | response.getOutTradeNo(), body); |
| 138 | } | 138 | } |
| 139 | 139 | ||
| @@ -146,10 +146,15 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | @@ -146,10 +146,15 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC | ||
| 146 | Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ? | 146 | Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ? |
| 147 | PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); | 147 | PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); |
| 148 | String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null; | 148 | String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null; |
| 149 | - return new PayOrderRespDTO(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()), | 149 | + return PayOrderRespDTO.of(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()), |
| 150 | result.getOutTradeNo(), body); | 150 | result.getOutTradeNo(), body); |
| 151 | } | 151 | } |
| 152 | 152 | ||
| 153 | + @Override | ||
| 154 | + protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Throwable { | ||
| 155 | + return null; | ||
| 156 | + } | ||
| 157 | + | ||
| 153 | // ============ 退款相关 ========== | 158 | // ============ 退款相关 ========== |
| 154 | 159 | ||
| 155 | @Override | 160 | @Override |
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 { | @@ -68,7 +68,7 @@ public class WxBarPayClient extends AbstractWxPayClient { | ||
| 68 | try { | 68 | try { |
| 69 | WxPayMicropayResult response = client.micropay(request); | 69 | WxPayMicropayResult response = client.micropay(request); |
| 70 | // 支付成功,例如说:1)用户输入了密码;2)用户免密支付 | 70 | // 支付成功,例如说:1)用户输入了密码;2)用户免密支付 |
| 71 | - return new PayOrderRespDTO(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), | 71 | + return PayOrderRespDTO.successOf(response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), |
| 72 | response.getOutTradeNo(), response) | 72 | response.getOutTradeNo(), response) |
| 73 | .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); | 73 | .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); |
| 74 | } catch (WxPayException ex) { | 74 | } catch (WxPayException ex) { |