feat(alipay): 对接关闭订单(直连关闭策略+子应用HTTP调用)

This commit is contained in:
DaxPay Dev
2026-07-02 11:16:17 +08:00
parent 550c1cf6f2
commit aa567c242f
5 changed files with 151 additions and 0 deletions

View File

@@ -1,7 +1,9 @@
package cn.daxpay.open.channel.alipay.client;
import cn.daxpay.open.channel.alipay.client.req.AlipayCloseReq;
import cn.daxpay.open.channel.alipay.client.req.AlipayPayReq;
import cn.daxpay.open.channel.alipay.client.req.AlipaySyncReq;
import cn.daxpay.open.channel.alipay.client.resp.AlipayCloseResp;
import cn.daxpay.open.channel.alipay.client.resp.AlipayPayResp;
import cn.daxpay.open.channel.alipay.client.resp.AlipaySyncResp;
import cn.daxpay.open.payment.common.result.DaxResult;
@@ -23,4 +25,8 @@ public interface AlipayChannelClient {
/// 支付同步(查询支付宝订单状态)
@PostExchange("/channel/alipay/sync")
DaxResult<AlipaySyncResp> sync(@RequestBody AlipaySyncReq req);
/// 关闭/撤销支付宝订单
@PostExchange("/channel/alipay/close")
DaxResult<AlipayCloseResp> close(@RequestBody AlipayCloseReq req);
}

View File

@@ -0,0 +1,23 @@
package cn.daxpay.open.channel.alipay.client.req;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import lombok.Data;
/// # 支付宝通道关闭请求
///
/// 与子应用 dax-pay-channel-one 的 `AlipayCloseReq` 镜像, 经声明式 HTTP 客户端序列化传输, 字段对齐。
@Data
public class AlipayCloseReq {
/// 商户订单号(主应用支付交易号, 作为支付宝 out_trade_no)
private String outTradeNo;
/// 支付宝交易号(trade_no, 下单成功后由支付宝返回, 可选)
private String tradeNo;
/// 是否使用撤销方式关闭订单(false=交易关闭, true=交易撤销)
private boolean useCancel;
/// 通道调用凭证
private AlipaySdkCredential credential;
}

View File

@@ -0,0 +1,27 @@
package cn.daxpay.open.channel.alipay.client.resp;
import lombok.Data;
/// # 支付宝通道关闭响应
///
/// 与子应用 dax-pay-channel-one 的 `AlipayCloseResp` 镜像, 字段对齐。
/// 关闭失败且可兜底(交易已关闭/交易不存在)时由子应用内部消化为成功;
/// 真正失败时子应用抛 [cn.daxpay.open.platform.core.exception.ChannelServiceException](经 DaxResult 透传)。
@Data
public class AlipayCloseResp {
/// 商户订单号(透传 AlipayCloseReq.outTradeNo)
private String outTradeNo;
/// 支付宝交易号(trade_no)
private String tradeNo;
/// 网关返回码(code, 10000=成功)
private String code;
/// 业务返回码(sub_code, 如 ACQ.TRADE_NOT_EXIST)
private String subCode;
/// 业务返回消息(sub_msg)
private String subMsg;
}

View File

@@ -0,0 +1,47 @@
package cn.daxpay.open.channel.alipay.service.payment.close;
import cn.daxpay.open.channel.alipay.client.AlipayChannelClient;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.client.req.AlipayCloseReq;
import cn.daxpay.open.channel.alipay.client.resp.AlipayCloseResp;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/// # 支付宝支付关闭业务服务
///
/// 通过 [AlipayChannelClient] 调用子应用 dax-pay-channel-one 关闭/撤销支付宝订单。
/// 请求构建、结果判定在本类中完成; 关闭失败的网关状态兜底由子应用内部处理。
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayCloseService {
private final AlipayChannelClient alipayChannelClient;
/// 执行支付宝订单关闭/撤销
///
/// @param trade 支付订单(tradeNo 作为 out_trade_no, outOrderNo 作为 trade_no)
/// @param credential 通道调用凭证
/// @param useCancel 是否使用撤销方式关闭
/// @return 实际使用的关闭方式([CloseTypeEnum.CLOSE] / [CloseTypeEnum.CANCEL])
public CloseTypeEnum close(PayTrade trade, AlipaySdkCredential credential, boolean useCancel) {
// 构建请求
AlipayCloseReq req = new AlipayCloseReq();
req.setOutTradeNo(trade.getTradeNo());
req.setTradeNo(trade.getOutOrderNo());
req.setUseCancel(useCancel);
req.setCredential(credential);
// 调用子应用
DaxResult<AlipayCloseResp> result = alipayChannelClient.close(req);
if (result.getCode() != 0) {
throw new IllegalStateException("支付宝通道关闭失败: " + result.getMsg());
}
return useCancel ? CloseTypeEnum.CANCEL : CloseTypeEnum.CLOSE;
}
}

View File

@@ -0,0 +1,48 @@
package cn.daxpay.open.channel.alipay.strategy.direct;
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.channel.alipay.service.payment.close.AlipayCloseService;
import cn.daxpay.open.payment.pay.order.dao.NormalPayOrderManager;
import cn.daxpay.open.payment.pay.order.entity.NormalPayOrder;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.daxpay.open.payment.strategy.pay.AbsPayCloseStrategy;
import cn.daxpay.open.platform.core.enums.pay.pay.CloseTypeEnum;
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/// # 支付宝直连支付关闭策略
///
/// 支付宝直连模式(ProductEnum.ALIPAY)下的支付关闭策略。
/// 从容器层 NormalPayOrder 读取通道路由参数(channelMchNo / capability),
/// 组装通道凭证(委托 [AlipayDirectConfigAssembler]), 关闭执行委托给 [AlipayCloseService]。
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayDirectCloseStrategy extends AbsPayCloseStrategy {
private final AlipayCloseService alipayCloseService;
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
private final NormalPayOrderManager normalPayOrderManager;
@Override
public ProductEnum getProduct() {
return ProductEnum.ALIPAY;
}
@Override
public CloseTypeEnum doClose(PayTrade trade, boolean useCancel) {
// 从容器层读取通道路由参数, 用于凭证解析
NormalPayOrder normalOrder = normalPayOrderManager.findById(trade.getContainerId()).orElse(null);
String channelMchNo = normalOrder != null ? normalOrder.getChannelMchNo() : null;
String capability = normalOrder != null ? normalOrder.getCapability() : null;
// 组装通道调用凭证
AlipaySdkCredential credential = alipayDirectConfigAssembler.buildConfig(
trade.getMchNo(), channelMchNo, capability);
return alipayCloseService.close(trade, credential, useCancel);
}
}