mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-10 14:56:06 +08:00
refactor(channel): 移除支付宝异步回调验签链路
删除 AlipayNotifyController/AlipayCallbackService/AlipayCallbackVerifyReq/Resp, AlipayChannelClient 去掉 callbackVerify 方法(仅保留 pay); AlipayDirectConfigAssembler 注释同步去掉回调引用。支付宝暂仅保留支付下单链路, 回调后续再补
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user