refactor(channel): 移除支付宝异步回调验签链路

删除 AlipayNotifyController/AlipayCallbackService/AlipayCallbackVerifyReq/Resp, AlipayChannelClient 去掉 callbackVerify 方法(仅保留 pay); AlipayDirectConfigAssembler 注释同步去掉回调引用。支付宝暂仅保留支付下单链路, 回调后续再补
This commit is contained in:
DaxPay Dev
2026-06-23 20:24:34 +08:00
parent b74022878a
commit e86c4b1e06
6 changed files with 1 additions and 227 deletions

View File

@@ -1,7 +1,5 @@
package cn.daxpay.open.channel.alipay.client;
import cn.daxpay.open.channel.alipay.dto.AlipayCallbackVerifyReq;
import cn.daxpay.open.channel.alipay.dto.AlipayCallbackVerifyResp;
import cn.daxpay.open.channel.alipay.dto.AlipayPayReq;
import cn.daxpay.open.channel.alipay.dto.AlipayPayResp;
import cn.daxpay.open.payment.common.result.DaxResult;
@@ -19,10 +17,4 @@ public interface AlipayChannelClient {
/// 支付下单
@PostExchange("/channel/pay")
DaxResult<AlipayPayResp> pay(@RequestBody AlipayPayReq req);
/// 回调验签
///
/// 主应用收到支付宝异步通知后, 将原始参数转发给子应用完成 RSA2 验签与业务字段解析。
@PostExchange("/channel/alipay/callback/verify")
DaxResult<AlipayCallbackVerifyResp> callbackVerify(@RequestBody AlipayCallbackVerifyReq req);
}

View File

@@ -1,60 +0,0 @@
package cn.daxpay.open.channel.alipay.controller;
import cn.daxpay.open.channel.alipay.service.callback.AlipayCallbackService;
import cn.daxpay.open.payment.pay.service.PayCallbackService;
import cn.daxpay.open.platform.core.annotation.IgnoreAuth;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/// # 支付宝异步通知接收 Controller
///
/// 暴露 `/notify/alipay` 供支付宝异步回调。接收原始 form 参数后委托子应用完成 RSA2 验签,
/// 验签通过则由 [PayCallbackService] 完成订单状态更新, 并返回 `success` 给支付宝。
///
/// 鉴权: 支付宝回调不携带平台凭证, 使用 `@IgnoreAuth` 放行, 安全性由 RSA2 验签保障。
@Slf4j
@IgnoreAuth
@RestController
@RequestMapping("/notify")
@RequiredArgsConstructor
public class AlipayNotifyController {
private final AlipayCallbackService alipayCallbackService;
private final PayCallbackService payCallbackService;
/// 支付宝支付异步通知
@PostMapping("/alipay")
public String alipayNotify(HttpServletRequest request) {
// 支付宝以 application/x-www-form-urlencoded 提交参数
Map<String, String> rawParams = parseFormParams(request);
log.info("接收支付宝异步通知: out_trade_no={}, trade_status={}",
rawParams.get("out_trade_no"), rawParams.get("trade_status"));
// 验签 + 填充回调上下文(失败直接回 fail, 让支付宝重试)
boolean verified = alipayCallbackService.verifyAndFillContext(rawParams);
if (!verified) {
return "fail";
}
// 统一回调处理(更新订单状态等)
payCallbackService.payCallback();
return "success";
}
/// 解析 form 表单参数为 Map<String, String>
private Map<String, String> parseFormParams(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((k, v) -> {
if (v != null && v.length > 0) {
params.put(k, v[0]);
}
});
return params;
}
}

View File

@@ -1,22 +0,0 @@
package cn.daxpay.open.channel.alipay.dto;
import lombok.Data;
import java.util.Map;
/// # 支付宝通道回调验签请求
///
/// 与子应用 `ChannelCallbackVerifyReq` 字段一致, 通过 Jackson 序列化传输。
/// 主应用收到支付宝异步通知后, 组装本对象调用子应用 `/channel/callback/verify` 完成验签。
@Data
public class AlipayCallbackVerifyReq {
/// 回调类型(pay-支付回调 / refund-退款回调)
private String callbackType;
/// 三方原始通知参数(支付宝 form 参数)
private Map<String, String> rawParams;
/// 通道调用配置(验签所需的支付宝公钥等)
private Map<String, Object> config;
}

View File

@@ -1,36 +0,0 @@
package cn.daxpay.open.channel.alipay.dto;
import lombok.Data;
import java.util.Map;
/// # 支付宝通道回调验签响应
///
/// 与子应用 `ChannelCallbackVerifyResp` 字段一致, 子应用完成验签与字段解析后返回。
@Data
public class AlipayCallbackVerifyResp {
/// 是否验签通过
private Boolean verified;
/// 业务订单号(对应支付宝 out_trade_no)
private String bizOrderNo;
/// 通道订单号(支付宝 trade_no)
private String outOrderNo;
/// 交易状态(success/fail)
private String status;
/// 金额(分)
private Long amount;
/// 完成时间
private String finishTime;
/// 付款用户ID
private String buyerId;
/// 原始数据
private Map<String, Object> rawData;
}

View File

@@ -1,99 +0,0 @@
package cn.daxpay.open.channel.alipay.service.callback;
import cn.daxpay.open.channel.alipay.client.AlipayChannelClient;
import cn.daxpay.open.channel.alipay.dto.AlipayCallbackVerifyReq;
import cn.daxpay.open.channel.alipay.dto.AlipayCallbackVerifyResp;
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
import cn.daxpay.open.payment.common.context.CallbackInfo;
import cn.daxpay.open.payment.common.context.PaymentContext;
import cn.daxpay.open.payment.common.result.DaxResult;
import cn.daxpay.open.payment.pay.order.dao.PayTradeManager;
import cn.daxpay.open.payment.pay.order.entity.PayTrade;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/// # 支付宝回调验签业务服务
///
/// 主应用收到支付宝异步通知后的处理流程:
/// 1. 凭 `out_trade_no`(下单时透传的 tradeNo) 反查 [PayTrade]
/// 2. 复用 [AlipayDirectConfigAssembler] 组装通道配置(含验签所需支付宝公钥)
/// 3. 通过 [AlipayChannelClient] 转发原始参数到子应用完成 RSA2 验签与字段解析
/// 4. 验签通过则填充 [PaymentContext] 的回调上下文, 供 [cn.daxpay.open.payment.pay.service.PayCallbackService] 使用
///
/// 订单状态不在本类更新(由主应用 [cn.daxpay.open.payment.pay.service.PayCallbackService] 统一维护)。
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayCallbackService {
private final PayTradeManager payTradeManager;
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
private final AlipayChannelClient alipayChannelClient;
private final PaymentContext paymentContext;
/// 处理支付宝异步通知: 反查订单 -> 组装配置 -> 调子应用验签 -> 填充回调上下文
///
/// @param rawParams 支付宝 POST 过来的原始 form 参数
/// @return 验签是否通过(通过则回调上下文已填充)
public boolean verifyAndFillContext(Map<String, String> rawParams) {
// 下单时透传给支付宝的 out_trade_no = 本系统的支付交易号 tradeNo
String tradeNo = rawParams.get("out_trade_no");
if (StrUtil.isBlank(tradeNo)) {
log.warn("支付宝回调缺少 out_trade_no 参数");
return false;
}
// 回调为系统级调用, 无商户上下文, 忽略租户查询订单
PayTrade trade = payTradeManager.findByTradeNoNotTenant(tradeNo).orElse(null);
if (trade == null) {
log.warn("支付宝回调对应的订单不存在: tradeNo={}", tradeNo);
return false;
}
// 组装通道配置(复用下单配置, 含验签所需的支付宝公钥)
Map<String, Object> config = alipayDirectConfigAssembler.buildConfig(trade.getMchNo());
// 调子应用验签
AlipayCallbackVerifyReq req = new AlipayCallbackVerifyReq();
req.setCallbackType("pay");
req.setRawParams(rawParams);
req.setConfig(config);
DaxResult<AlipayCallbackVerifyResp> result = alipayChannelClient.callbackVerify(req);
if (result.getCode() != 0) {
log.warn("子应用回调验签调用失败: tradeNo={}, msg={}", tradeNo, result.getMsg());
return false;
}
AlipayCallbackVerifyResp resp = result.getData();
if (!Boolean.TRUE.equals(resp.getVerified())) {
log.warn("支付宝回调验签未通过: tradeNo={}", tradeNo);
return false;
}
// 填充回调上下文, 供 PayCallbackService 使用
CallbackInfo callbackInfo = paymentContext.getCallbackInfo();
callbackInfo.setTradeNo(resp.getBizOrderNo());
callbackInfo.setOutTradeNo(resp.getOutOrderNo());
callbackInfo.setTradeStatus(resp.getStatus());
// 完成时间(支付宝 gmt_payment 格式 yyyy-MM-dd HH:mm:ss, GMT+8)
if (StrUtil.isNotBlank(resp.getFinishTime())) {
callbackInfo.setFinishTime(parseAlipayTime(resp.getFinishTime()));
}
return true;
}
/// 解析支付宝时间(yyyy-MM-dd HH:mm:ss, GMT+8) 为 OffsetDateTime
private OffsetDateTime parseAlipayTime(String time) {
try {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return OffsetDateTime.parse(time, fmt.withZone(ZoneOffset.ofHours(8)));
} catch (Exception e) {
log.warn("解析支付宝时间失败: {}", time, e);
return OffsetDateTime.now();
}
}
}

View File

@@ -16,8 +16,7 @@ import java.util.Map;
/// 从进件商户对象([AlipayDirectApp] + [AlipayDirectAppKeyConfig])读取密钥/证书,
/// 并补充主应用回调地址(notifyUrl)与沙箱标志(sandbox), 组装为下发给子应用的 config Map。
///
/// 供支付策略([cn.daxpay.open.channel.alipay.strategy.direct.AlipayDirectPayStrategy])
/// 与回调验签([cn.daxpay.open.channel.alipay.service.callback.AlipayCallbackService])复用, 避免重复读取配置。
/// 供支付策略([cn.daxpay.open.channel.alipay.strategy.direct.AlipayDirectPayStrategy])组装通道调用配置。
@Slf4j
@Service
@RequiredArgsConstructor