支付宝手机网站支付验签一直无法通过
结论
out_trade_no = "weixingzhifuceshi001" 本身不会导致验签失败。这个值只包含英文字母和数字,通常不会涉及字符集或 URL 编码问题。
持续验签失败,常见原因包括:
- 密钥用错,例如把应用公钥当成支付宝公钥。
sign_type、charset与实际请求不一致。- 验签前修改、筛选或重新编码了支付宝返回的参数。
- 同步跳转参数、异步通知参数或接口响应体使用了不匹配的验签方法。
- 混用了沙箱和正式环境的网关、
app_id或密钥配置。 - 手工拼接待验签字符串时,参数排序、空值处理或 URL 解码方式有误。
建议使用支付宝对应语言的官方 SDK 完成签名和验签,不要自行拼接待签名字符串。
先确认验签对象
手机网站支付通常涉及三类数据:
- 调用支付接口时提交的请求参数。
- 支付完成后的同步跳转参数。
- 支付宝服务器发送的异步通知参数。
这三类数据的处理方式不同。验证异步通知时,必须使用收到的完整通知参数,不能只拿 out_trade_no、trade_status 等业务字段验签。
如果要验证 alipay.trade.wap.pay 接口的返回内容,应使用 SDK 针对接口响应提供的处理方式,不能直接套用异步通知的验签代码。
检查密钥是否正确
验证支付宝返回的数据时,应使用“支付宝公钥”,不能使用以下密钥:
- 应用私钥
- 应用公钥
- 商户私钥
- 其他应用的支付宝公钥
签名方向可以简单理解为:
- 商户向支付宝发送请求时,使用应用私钥签名。
- 商户验证支付宝返回的数据时,使用支付宝公钥验签。
如果使用证书模式,还要按证书模式初始化客户端和验签配置,不能把证书内容作为普通公钥字符串使用。
还要确认当前 app_id 对应的应用公钥已经正确配置到支付宝开放平台。更换应用密钥后,不能混用旧私钥和新的支付宝公钥。
使用 SDK 验证异步通知
以下代码以 Java 和普通公钥模式为例。不同依赖版本的方法签名可能略有差异,请以项目实际使用的支付宝 SDK 为准。
import com.alipay.api.internal.util.AlipaySignature;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
public boolean verifyAlipayNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
String[] values = entry.getValue();
String value = String.join(",", values);
params.put(entry.getKey(), value);
}
String alipayPublicKey = "支付宝公钥内容";
String charset = "UTF-8";
String signType = "RSA2";
return AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
charset,
signType
);
}
验签成功后,再检查业务字段:
if (!verifyAlipayNotify(request)) {
response.getWriter().print("failure");
return;
}
String appId = request.getParameter("app_id");
String outTradeNo = request.getParameter("out_trade_no");
String tradeNo = request.getParameter("trade_no");
String tradeStatus = request.getParameter("trade_status");
String totalAmount = request.getParameter("total_amount");
// 继续核对 app_id、商户订单号、订单金额和收款方信息
// 确认无误后再更新订单状态
response.getWriter().print("success");
异步通知处理成功后,应按接口约定返回纯文本 success。如果业务处理失败或验签未通过,不要返回成功响应。
不要在验签前修改参数
以下操作可能破坏签名:
// 错误示例:只挑选部分业务参数进行验签
Map<String, String> params = new HashMap<>();
params.put("out_trade_no", request.getParameter("out_trade_no"));
params.put("trade_status", request.getParameter("trade_status"));
// 错误示例:先对参数做 trim、JSON 重组或二次 URL 解码
String value = URLDecoder.decode(request.getParameter("subject"), "UTF-8").trim();
应直接从 HTTP 请求中取得支付宝发送的全部参数,再交给 SDK。不要进行以下处理:
- 删除看似无用的字段。
- 修改参数值中的空格、加号或特殊字符。
- 对参数二次 URL 解码。
- 把参数转换成 JSON 后再验签。
- 自行删除
sign、sign_type后手工拼接,除非实现方式完全符合对应接口的签名规范。
Web 框架通常已经对表单参数做过一次 URL 解码。如果业务代码再次调用 URLDecoder.decode(),包含 +、% 或中文的参数很容易发生变化。
核对 sign_type 和字符集
请求签名、支付宝返回参数及验签配置必须一致。例如,返回参数是:
sign_type=RSA2
charset=utf-8
验签时却使用:
String signType = "RSA";
String charset = "GBK";
这种情况一定无法通过验签。
如果通知参数中包含 sign_type,可以先确认其取值合法,再使用该值:
String signType = request.getParameter("sign_type");
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
signType
);
不要仅凭项目的默认配置推测支付宝实际使用的签名类型。应记录收到的 sign_type,并与创建支付请求时的配置核对。
检查环境是否混用
沙箱和正式环境需要分别核对以下配置:
- 支付宝网关地址
app_id- 应用私钥
- 支付宝公钥或应用证书
- 支付宝根证书
- 支付宝公钥证书
例如,通过沙箱网关发起交易,却使用正式应用的支付宝公钥验签,结果一定无法通过。同一台服务器部署多个支付宝应用时,也不要让所有通知共用一套全局密钥。
服务端可以根据通知中的 app_id 选择对应配置,但只能从预先配置的应用列表中选择。不能直接信任通知参数并从外部加载密钥。
检查公钥格式
在普通公钥模式下,SDK 通常需要的是公钥正文,不能误传公钥文件路径、证书内容或私钥内容。例如:
String alipayPublicKey =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...";
部分 SDK 或密钥加载工具要求包含 PEM 头尾:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
具体格式取决于所用 SDK 的方法。如果密钥来自配置文件或环境变量,还要检查:
- 是否带有多余引号。
- 换行符是否保存成了字面量
\n。 - 前后是否有空格。
- 配置中心是否截断了长字符串。
- 是否误用了 PKCS#1 私钥格式。
- 是否混用了普通公钥模式和证书模式。
不要在生产日志中输出完整私钥。排查密钥是否一致时,可以记录密钥的安全摘要,不要记录原文:
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(alipayPublicKey.getBytes(StandardCharsets.UTF_8));
String fingerprint = HexFormat.of().formatHex(hash);
logger.info("Alipay public key fingerprint: {}", fingerprint);
out_trade_no 需要检查什么
当前订单号是:
weixingzhifuceshi001
从签名角度看,这个值没有明显问题。但仍需确认:
- 创建支付请求时发送的值与通知中收到的值完全一致。
- 中间层没有自动添加前缀、后缀或空格。
- 没有混用微信支付和支付宝的订单号字段。
- 同一商户订单号没有用于不同金额的订单。
- 数据库字段长度足够,没有截断订单号。
订单号重复、订单不存在或金额不一致属于业务校验问题,一般不会造成密码学意义上的“验签失败”。因此,“签名验证”和“订单业务验证”应分开记录。
推荐的排查顺序
- 确认待验签数据来自同步跳转、异步通知还是接口响应。
- 记录收到的参数名、
app_id、sign_type、charset及参数值长度,同时避免泄露敏感信息。 - 确认使用的是当前应用、当前环境对应的支付宝公钥。
- 确认没有混用普通公钥模式和证书模式。
- 直接把原始请求参数映射交给 SDK,不做筛选、拼接或二次解码。
- 核对 SDK 验签配置中的
charset和sign_type。 - 验签通过后,再检查
out_trade_no、金额、收款方和交易状态。 - 如果仍然失败,对比应用实际加载的公钥指纹与开放平台配置,排除配置未生效或读取旧配置的情况。
应优先检查支付宝公钥是否正确,以及参数在验签前是否被修改。持续验签失败通常与这两类问题有关,weixingzhifuceshi001 这个订单号本身一般不是原因。
备注:内容仅供参考。
