支付宝H5支付使用certificateExecute报错
结论
java.lang.IllegalArgumentException: expected value at column 1 通常并非证书内容有误,而是响应的解析方式与实际格式不匹配。
alipay.trade.wap.pay 是页面类支付接口,支付宝网关返回 HTML 支付页面。certificateExecute(request) 会按照普通开放 API 的响应结构进行解析,通常期待 JSON。当解析器读取第 1 个字符时,如果遇到 <html>、<!DOCTYPE html> 或 <form>,就会报 expected value at column 1。
证书模式只影响验签和证书链配置,不会将 H5 支付接口返回的 HTML 转换成 JSON。WAP 支付应使用 pageExecute(request) 获取页面内容。
正确处理方式
使用证书参数初始化 AlipayClient,然后调用 pageExecute:
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
alipayConfig.setAppId(appId);
alipayConfig.setPrivateKey(appPrivateKey);
alipayConfig.setFormat("json");
alipayConfig.setCharset("UTF-8");
alipayConfig.setSignType("RSA2");
// 应用公钥证书
alipayConfig.setAppCertPath(appCertPath);
// 支付宝公钥证书
alipayConfig.setAlipayPublicCertPath(alipayPublicCertPath);
// 支付宝根证书
alipayConfig.setRootCertPath(alipayRootCertPath);
AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount(totalAmount);
model.setSubject(subject);
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
String html = response.getBody();
在 Spring MVC 中,可以直接将 HTML 写入响应:
@GetMapping(value = "/pay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String pay() throws AlipayApiException {
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
return response.getBody();
}
也可以按照实际前端架构返回 HTML 字符串,再由浏览器写入页面或执行跳转。不要对这段 HTML 再做 JSON 反序列化。
为什么报错后仍然能看到支付页面
异常日志下方打印的内容能够正常显示支付页面,说明支付宝返回的主体很可能是有效的 HTML。问题出在 SDK 或业务代码将这段 HTML 当成 JSON 解析,而非支付页面生成失败。
例如,下面的响应开头对 JSON 解析器来说并不合法:
<form name="punchout_form" method="post" action="...">
...
</form>
解析器在第 1 列期待 {、[、字符串或其他合法的 JSON 值,实际却读到了 <,于是抛出:
java.lang.IllegalArgumentException: expected value at column 1
建议的排查顺序
1. 确认调用方法
页面类接口应使用:
alipayClient.pageExecute(request);
不要使用:
alipayClient.certificateExecute(request);
也不要将 pageExecute 返回的 body 交给 JSON 解析器。
2. 查看响应正文的第一个字符
临时记录响应正文开头的一小段即可。不要在生产日志中完整输出包含支付参数的页面:
String body = response.getBody();
if (body != null) {
String preview = body.substring(0, Math.min(body.length(), 200));
log.debug("Alipay response preview: {}", preview);
}
如果内容以 <form、<html 或 <!DOCTYPE 开头,基本可以确认 HTML 被误当成了 JSON。
如果响应以 { 开头,则需要继续检查业务代码是否进行了二次解析,并根据异常堆栈确认错误来自支付宝 SDK、JSON 库,还是项目自己的封装层。
3. 确认证书模式初始化完整
证书模式通常需要配置:
- 应用私钥
- 应用公钥证书
- 支付宝公钥证书
- 支付宝根证书
RSA2- 与证书所属环境一致的支付宝网关地址
沙箱环境和正式环境的应用、证书及网关不能混用。还要确认应用进程有权限读取证书路径。
支付宝 Java SDK 不同版本的配置方式可能略有区别,应以当前依赖版本提供的 AlipayConfig 或 DefaultAlipayClient 构造方式为准。
4. 查看完整异常堆栈
不要只看最外层的 IllegalArgumentException。应重点检查堆栈中最先出现的业务类或 SDK 解析类:
Caused by: java.lang.IllegalArgumentException: expected value at column 1
如果异常来自 JSON 解析组件,而原始响应是 HTML,就可以进一步确认问题在于调用方式或二次解析。
5. 检查 SDK 版本和封装代码
如果已经使用 pageExecute(request),但仍然出现相同错误,需要检查:
- 项目是否对
AlipayClient做了统一代理或二次封装; - 是否有拦截器强制将响应转换为 JSON;
- 是否混用了多个版本的支付宝 SDK;
- 是否存在旧版 SDK 与当前证书模式配置不兼容的情况;
AlipayTradeWapPayResponse#getBody()是否被再次反序列化。
升级 SDK 前,应先核对当前版本的接口签名和升级说明,不能只靠更换版本来掩盖调用方式错误。
注意事项
pageExecute 返回支付页面,只说明已经生成了用于拉起收银台的 HTML,并不代表订单最终支付成功。支付结果应主要以支付宝异步通知为准,并在必要时主动调用交易查询接口核验状态。
处理异步通知时,还要使用证书模式对应的验签配置,校验签名、应用 ID、商户订单号、金额和交易状态。不要仅凭浏览器的同步跳转更新订单状态。
备注:内容仅供参考。
