PAYATHON 2026

alipay-sdk-java 是否提供支付回调解析工具类和实体类?

支付老李

结论

alipay-sdk-java 一般不提供把 HttpServletRequest 直接解析为支付回调实体的统一工具类,也没有适用于所有支付产品的通用回调实体。

经典 Java SDK 主要负责回调验签,例如:

AlipaySignature.rsaCheckV1(...)

后台还需要自行处理以下内容:

  1. 从请求中提取参数,构造 Map<String, String>。
  2. 使用 SDK 验证支付宝签名。
  3. 验签通过后,把参数映射到自定义 DTO,或直接读取所需字段。
  4. 校验订单信息,并保证处理过程幂等。
  5. 业务处理成功后,向支付宝返回纯文本 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,这比依赖同步响应实体更清晰,也更稳定。

备注:内容仅供参考。