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 | 27 | * 调用支付渠道,统一下单 |
| 28 | 28 | * |
| 29 | 29 | * @param reqDTO 下单信息 |
| 30 | - * @return 各支付渠道的返回结果 | |
| 30 | + * @return 支付订单信息 | |
| 31 | 31 | */ |
| 32 | 32 | PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO); |
| 33 | 33 | |
| ... | ... | @@ -40,6 +40,14 @@ public interface PayClient { |
| 40 | 40 | */ |
| 41 | 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 | 94 | /** |
| 95 | 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 | 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 | 131 | PayOrderRespDTO respDTO = new PayOrderRespDTO(); |
| 130 | 132 | respDTO.status = PayOrderStatusRespEnum.CLOSED.getStatus(); |
| 131 | 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 | 107 | protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) |
| 108 | 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 | 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 | 17 | import com.alipay.api.AlipayConfig; |
| 18 | 18 | import com.alipay.api.AlipayResponse; |
| 19 | 19 | import com.alipay.api.DefaultAlipayClient; |
| 20 | +import com.alipay.api.domain.AlipayTradeQueryModel; | |
| 20 | 21 | import com.alipay.api.domain.AlipayTradeRefundModel; |
| 21 | 22 | import com.alipay.api.internal.util.AlipaySignature; |
| 23 | +import com.alipay.api.request.AlipayTradeQueryRequest; | |
| 22 | 24 | import com.alipay.api.request.AlipayTradeRefundRequest; |
| 25 | +import com.alipay.api.response.AlipayTradeQueryResponse; | |
| 23 | 26 | import com.alipay.api.response.AlipayTradeRefundResponse; |
| 24 | 27 | import lombok.SneakyThrows; |
| 25 | 28 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -63,7 +66,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa |
| 63 | 66 | */ |
| 64 | 67 | protected PayOrderRespDTO buildClosedPayOrderRespDTO(PayOrderUnifiedReqDTO reqDTO, AlipayResponse response) { |
| 65 | 68 | Assert.isFalse(response.isSuccess()); |
| 66 | - return PayOrderRespDTO.build(response.getSubCode(), response.getSubMsg(), | |
| 69 | + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), | |
| 67 | 70 | reqDTO.getOutTradeNo(), response); |
| 68 | 71 | } |
| 69 | 72 | |
| ... | ... | @@ -76,10 +79,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa |
| 76 | 79 | |
| 77 | 80 | // 2. 解析订单的状态 |
| 78 | 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 | 84 | if (MapUtil.getDouble(bodyObj, "refund_fee", 0D) > 0) { |
| 85 | 85 | status = PayOrderStatusRespEnum.REFUND.getStatus(); |
| ... | ... | @@ -87,10 +87,40 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa |
| 87 | 87 | Assert.notNull(status, (Supplier<Throwable>) () -> { |
| 88 | 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 | 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 | 90 | } catch (WxPayException e) { |
| 91 | 91 | String errorCode = getErrorCode(e); |
| 92 | 92 | String errorMessage = getErrorMessage(e); |
| 93 | - return PayOrderRespDTO.build(errorCode, errorMessage, | |
| 93 | + return PayOrderRespDTO.closedOf(errorCode, errorMessage, | |
| 94 | 94 | reqDTO.getOutTradeNo(), e.getXmlString()); |
| 95 | 95 | } |
| 96 | 96 | } |
| ... | ... | @@ -133,7 +133,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC |
| 133 | 133 | // 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂 |
| 134 | 134 | Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ? |
| 135 | 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 | 137 | response.getOutTradeNo(), body); |
| 138 | 138 | } |
| 139 | 139 | |
| ... | ... | @@ -146,10 +146,15 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC |
| 146 | 146 | Integer status = Objects.equals(result.getTradeState(), "SUCCESS") ? |
| 147 | 147 | PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus(); |
| 148 | 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 | 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 | 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 | 68 | try { |
| 69 | 69 | WxPayMicropayResult response = client.micropay(request); |
| 70 | 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 | 72 | response.getOutTradeNo(), response) |
| 73 | 73 | .setDisplayMode(PayOrderDisplayModeEnum.BAR_CODE.getMode()); |
| 74 | 74 | } catch (WxPayException ex) { | ... | ... |