alipay-sdk-java 是否提供支付回调解析工具类和实体类?
结论
alipay-sdk-java 一般不提供把 HttpServletRequest 直接解析为支付回调实体的统一工具类,也没有适用于所有支付产品的通用回调实体。
经典 Java SDK 主要负责回调验签,例如:
AlipaySignature.rsaCheckV1(...)
后台还需要自行处理以下内容:
- 从请求中提取参数,构造
Map<String, String>。 - 使用 SDK 验证支付宝签名。
- 验签通过后,把参数映射到自定义 DTO,或直接读取所需字段。
- 校验订单信息,并保证处理过程幂等。
- 业务处理成功后,向支付宝返回纯文本
success。
支付宝各类产品的异步通知字段并不完全一致,还可能带有可选字段和扩展字段。因此,不适合直接把支付接口的 Response 类当作回调实体使用。
回调参数解析与验签
下面的示例适用于传统 Servlet 接口。HttpServletRequest 的具体包名需要根据项目使用的是 javax.servlet 还是 jakarta.servlet 进行调整。
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import java.util.LinkedHashMap;
import java.util.Map;
public final class AlipayNotifyUtils {
private AlipayNotifyUtils() {
}
public static Map<String, String> getParams(HttpServletRequest request) {
Map<String, String> params = new LinkedHashMap<>();
request.getParameterMap().forEach((name, values) -> {
if (values == null || values.length == 0) {
params.put(name, "");
return;
}
// 支付宝通知参数正常情况下是单值参数。
// 如果业务网关允许同名多值参数,应明确制定处理规则。
params.put(name, values[0]);
});
return params;
}
public static boolean verify(
Map<String, String> params,
String alipayPublicKey,
String charset,
String signType) throws AlipayApiException {
return AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
charset,
signType
);
}
}
调用方式如下:
Map<String, String> params = AlipayNotifyUtils.getParams(request);
boolean verified = AlipayNotifyUtils.verify(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().print("failure");
return;
}
alipayPublicKey 必须填写支付宝公钥,不能使用应用公钥或应用私钥。如果项目采用支付宝公钥证书模式,需要改用当前 SDK 版本提供的证书验签方法,例如 rsaCertCheckV1。不同版本的方法签名可能不同,应以项目实际依赖的版本为准。
定义回调实体
完成验签后,可以把业务所需字段映射到自定义实体。建议先保留字符串形式,检查格式后再转换金额和时间,以免自动类型转换掩盖非法输入。
public class AlipayTradeNotify {
private String notifyTime;
private String notifyType;
private String notifyId;
private String appId;
private String sellerId;
private String buyerId;
private String outTradeNo;
private String tradeNo;
private String tradeStatus;
private String totalAmount;
private String receiptAmount;
private String buyerPayAmount;
public String getNotifyTime() {
return notifyTime;
}
public void setNotifyTime(String notifyTime) {
this.notifyTime = notifyTime;
}
public String getNotifyType() {
return notifyType;
}
public void setNotifyType(String notifyType) {
this.notifyType = notifyType;
}
public String getNotifyId() {
return notifyId;
}
public void setNotifyId(String notifyId) {
this.notifyId = notifyId;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getSellerId() {
return sellerId;
}
public void setSellerId(String sellerId) {
this.sellerId = sellerId;
}
public String getBuyerId() {
return buyerId;
}
public void setBuyerId(String buyerId) {
this.buyerId = buyerId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getTradeNo() {
return tradeNo;
}
public void setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
}
public String getTradeStatus() {
return tradeStatus;
}
public void setTradeStatus(String tradeStatus) {
this.tradeStatus = tradeStatus;
}
public String getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(String totalAmount) {
this.totalAmount = totalAmount;
}
public String getReceiptAmount() {
return receiptAmount;
}
public void setReceiptAmount(String receiptAmount) {
this.receiptAmount = receiptAmount;
}
public String getBuyerPayAmount() {
return buyerPayAmount;
}
public void setBuyerPayAmount(String buyerPayAmount) {
this.buyerPayAmount = buyerPayAmount;
}
}
再编写一个显式映射方法:
public static AlipayTradeNotify toNotify(Map<String, String> params) {
AlipayTradeNotify notify = new AlipayTradeNotify();
notify.setNotifyTime(params.get("notify_time"));
notify.setNotifyType(params.get("notify_type"));
notify.setNotifyId(params.get("notify_id"));
notify.setAppId(params.get("app_id"));
notify.setSellerId(params.get("seller_id"));
notify.setBuyerId(params.get("buyer_id"));
notify.setOutTradeNo(params.get("out_trade_no"));
notify.setTradeNo(params.get("trade_no"));
notify.setTradeStatus(params.get("trade_status"));
notify.setTotalAmount(params.get("total_amount"));
notify.setReceiptAmount(params.get("receipt_amount"));
notify.setBuyerPayAmount(params.get("buyer_pay_amount"));
return notify;
}
完整的处理流程可以这样写:
Map<String, String> params = AlipayNotifyUtils.getParams(request);
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
response.getWriter().print("failure");
return;
}
AlipayTradeNotify notify = toNotify(params);
// 以下校验不能仅依赖验签结果
if (!expectedAppId.equals(notify.getAppId())) {
response.getWriter().print("failure");
return;
}
if (!expectedSellerId.equals(notify.getSellerId())) {
response.getWriter().print("failure");
return;
}
// 根据 out_trade_no 查询本地订单,并校验金额、订单归属和交易状态。
// 同一通知可能被重复发送,更新订单时必须保证幂等。
switch (notify.getTradeStatus()) {
case "TRADE_SUCCESS":
case "TRADE_FINISHED":
// 幂等更新订单状态
break;
default:
// 根据当前支付产品的通知协议处理其他状态
break;
}
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().print("success");
为什么不建议直接使用支付 Response 类
AlipayTradePagePayResponse、AlipayTradeAppPayResponse 等类描述的是主动调用支付宝接口后得到的同步响应,与支付宝服务器发送的异步通知不是同一类数据。
即使部分字段名称相同,两者的字段范围、含义和产生时机也可能不同。直接复用可能带来这些问题:
- 回调字段无法完整映射;
- 可选字段被忽略;
- 同步响应与异步通知的业务含义混淆;
- SDK 升级后出现难以发现的兼容问题。
更合适的做法是按照实际接入产品的异步通知文档定义 DTO,同时保留原始 Map<String, String>,用于验签、日志排查和读取扩展字段。
注意事项
- 验签时必须使用未经业务修改的完整参数集合。不要提前删除空字段、修改金额格式或重新格式化时间。
- 验签前不要信任
out_trade_no、trade_status、total_amount等字段。 - 验签通过不代表订单一定有效。还需要核对
app_id、seller_id、商户订单号、订单金额和币种等信息。 - 比较金额时应使用
BigDecimal.compareTo(),不要使用double。 - 异步通知可能重复发送,订单更新必须具备幂等性。
- 只有业务处理确实成功后,才能返回纯文本
success。否则,支付宝可能继续重试。 - 回调地址应读取表单参数。如果接入协议明确使用
application/x-www-form-urlencoded,不要按 JSON 请求体解析。 - 日志中不要记录买家账号、手机号等敏感信息,也不要输出应用私钥。
alipay-sdk-java 可以完成签名验证,但请求参数提取和通知实体映射通常仍需业务系统自行实现。可以在项目内部把这两部分分别封装为 AlipayNotifyUtils 和产品级 DTO,这比依赖同步响应实体更清晰,也更稳定。
备注:内容仅供参考。
