diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java index 82e0966fd..f7e061c1a 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/client/AlipayChannelClient.java @@ -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 pay(@RequestBody AlipayPayReq req); - - /// 回调验签 - /// - /// 主应用收到支付宝异步通知后, 将原始参数转发给子应用完成 RSA2 验签与业务字段解析。 - @PostExchange("/channel/alipay/callback/verify") - DaxResult callbackVerify(@RequestBody AlipayCallbackVerifyReq req); } diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/controller/AlipayNotifyController.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/controller/AlipayNotifyController.java deleted file mode 100644 index 626ada7d3..000000000 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/controller/AlipayNotifyController.java +++ /dev/null @@ -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 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 - private Map parseFormParams(HttpServletRequest request) { - Map params = new HashMap<>(); - request.getParameterMap().forEach((k, v) -> { - if (v != null && v.length > 0) { - params.put(k, v[0]); - } - }); - return params; - } -} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyReq.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyReq.java deleted file mode 100644 index 1ebe7454a..000000000 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyReq.java +++ /dev/null @@ -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 rawParams; - - /// 通道调用配置(验签所需的支付宝公钥等) - private Map config; -} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyResp.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyResp.java deleted file mode 100644 index 6bc5837a9..000000000 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/dto/AlipayCallbackVerifyResp.java +++ /dev/null @@ -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 rawData; -} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/callback/AlipayCallbackService.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/callback/AlipayCallbackService.java deleted file mode 100644 index 897cf5251..000000000 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/callback/AlipayCallbackService.java +++ /dev/null @@ -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 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 config = alipayDirectConfigAssembler.buildConfig(trade.getMchNo()); - - // 调子应用验签 - AlipayCallbackVerifyReq req = new AlipayCallbackVerifyReq(); - req.setCallbackType("pay"); - req.setRawParams(rawParams); - req.setConfig(config); - DaxResult 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(); - } - } -} diff --git a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java index afeb0ae54..0c9efabdb 100644 --- a/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-alipay/src/main/java/cn/daxpay/open/channel/alipay/service/direct/AlipayDirectConfigAssembler.java @@ -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