支付宝个人协议页面签约接口如何生成签约二维码?
支付小周
结论
支付宝个人协议页面签约接口是页面类接口,返回用于跳转到支付宝签约页面的 HTML 表单,并非 JSON 业务数据,因此不能用 JSON 工具将返回内容转换为实体类。
正确的处理方式如下:
- 使用支付宝 SDK 的
pageExecute调用接口。 - 读取响应中的 HTML 内容。
- 服务端将 HTML 原样输出给浏览器,由表单自动提交并跳转到支付宝签约页面。
- 如果产品需要二维码,应将本系统的签约入口 URL 生成二维码。用户扫码访问该 URL 后,服务端再调用支付宝接口并输出 HTML。
转换实体类为什么会报错
页面类接口通常返回类似下面的内容:
<form name="punchout_form"
method="post"
action="https://openapi.alipay.com/gateway.do?...">
<input type="hidden" name="biz_content" value="...">
</form>
<script>
document.forms[0].submit();
</script>
这是一段可以执行页面跳转的内容,不是下面这种 JSON:
{
"code": "10000",
"msg": "Success"
}
如果对这段 HTML 执行 JSON.parseObject()、Jackson readValue() 或类似的实体转换操作,解析器会在 <html>、<form> 等标签处报错。
推荐实现方式
1. 创建本系统的签约入口
前端请求本系统接口,例如:
https://example.com/agreement/sign?token=一次性业务令牌
二维码中也应保存这个地址,而不是支付宝接口返回的整段 HTML。
token 需要关联用户和签约业务信息。建议为它设置有效期和使用次数限制,不要在二维码中直接暴露用户编号、协议号等敏感参数。
2. 服务端调用支付宝页面签约接口
下面以支付宝 Java SDK 的常见调用方式为例。具体请求类和业务参数,需要以当前接入产品的接口文档及项目使用的 SDK 版本为准。
@GetMapping(value = "/agreement/sign", produces = "text/html;charset=UTF-8")
@ResponseBody
public String sign(@RequestParam String token) throws AlipayApiException {
// 1. 校验一次性 token,并从服务端查询对应的用户和签约信息
AgreementContext context = agreementService.verifyToken(token);
// 2. 构造个人协议页面签约请求
AlipayUserAgreementPageSignRequest request =
new AlipayUserAgreementPageSignRequest();
request.setNotifyUrl("https://example.com/alipay/agreement/notify");
request.setReturnUrl("https://example.com/agreement/result");
AlipayUserAgreementPageSignModel model =
new AlipayUserAgreementPageSignModel();
// 以下字段仅表示设置业务参数的位置。
// 实际必填字段、字段名称和取值规则以对应接口文档为准。
// model.setPersonalProductCode(...);
// model.setSignScene(...);
// model.setExternalAgreementNo(...);
request.setBizModel(model);
// 3. 页面类接口应使用 pageExecute
AlipayUserAgreementPageSignResponse response =
alipayClient.pageExecute(request);
if (response == null || response.getBody() == null) {
throw new IllegalStateException("支付宝签约页面生成失败");
}
// 4. 直接返回 HTML,不要转换为实体类
return response.getBody();
}
部分支付宝 Java SDK 版本的 pageExecute 可能直接返回 String,对应写法如下:
String html = alipayClient.pageExecute(request);
return html;
具体应以项目实际引入的 SDK 方法签名为准。无论使用哪个版本,处理原则都一样:取得 HTML 后以 text/html 输出,不要按 JSON 解析。
如果需要直接操作 HttpServletResponse,可以这样处理:
@GetMapping("/agreement/sign")
public void sign(
@RequestParam String token,
HttpServletResponse response
) throws Exception {
AgreementContext context = agreementService.verifyToken(token);
AlipayUserAgreementPageSignRequest request =
new AlipayUserAgreementPageSignRequest();
request.setNotifyUrl("https://example.com/alipay/agreement/notify");
request.setReturnUrl("https://example.com/agreement/result");
AlipayUserAgreementPageSignModel model =
new AlipayUserAgreementPageSignModel();
// 按接口文档设置实际业务参数
request.setBizModel(model);
AlipayUserAgreementPageSignResponse alipayResponse =
alipayClient.pageExecute(request);
String html = alipayResponse.getBody();
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(html);
response.getWriter().flush();
}
3. 将本系统签约入口生成二维码
可以使用 ZXing 将入口 URL 转成二维码:
public BufferedImage createQrCode(String signEntryUrl) throws WriterException {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
hints.put(EncodeHintType.MARGIN, 1);
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
BitMatrix matrix = new QRCodeWriter().encode(
signEntryUrl,
BarcodeFormat.QR_CODE,
320,
320,
hints
);
return MatrixToImageWriter.toBufferedImage(matrix);
}
输出 PNG:
@GetMapping(value = "/agreement/qrcode", produces = MediaType.IMAGE_PNG_VALUE)
public void qrcode(HttpServletResponse response) throws Exception {
String token = agreementService.createOneTimeToken();
String signEntryUrl =
"https://example.com/agreement/sign?token="
+ URLEncoder.encode(token, StandardCharsets.UTF_8);
BufferedImage image = createQrCode(signEntryUrl);
response.setContentType(MediaType.IMAGE_PNG_VALUE);
ImageIO.write(image, "PNG", response.getOutputStream());
}
完整流程如下:
生成一次性 token
↓
组成本站签约入口 URL
↓
将 URL 生成二维码
↓
用户使用支付宝或浏览器扫码
↓
访问本站签约入口
↓
服务端调用 pageExecute
↓
向浏览器输出支付宝返回的 HTML
↓
自动跳转到支付宝签约页面
为什么不建议解析 HTML 后生成支付宝地址
理论上可以从 HTML 中提取 <form> 的 action 和隐藏字段,再自行拼接请求地址,但不建议这样处理,原因如下:
- 表单可能通过
POST提交,无法简单转换成二维码 URL。 - 参数可能包含特殊字符,手动拼接容易出现编码错误。
- HTML 结构是 SDK 和网关的输出细节,后续可能变化。
- 漏传、修改或重复编码参数,都可能导致验签失败。
除非接口文档明确提供了可直接扫码访问的签约 URL 字段,否则应将本站入口 URL 放入二维码,再由本站输出支付宝返回的 HTML。
注意事项
notify_url用于接收支付宝服务端的异步通知,不能只根据return_url判断签约是否成功。- 收到异步通知后必须验签,并核对应用 ID、协议号、外部业务号和签约状态等信息。
- 二维码入口应使用 HTTPS,并通过一次性令牌关联业务数据。
- 不要将应用私钥、完整业务参数或用户敏感信息放入二维码。
- 令牌需要设置有效期和防重放机制,并在使用后失效或标记状态。
- 不要把支付宝返回的 HTML 存进 JSON 字段,再交给前端解析。如果由前端发起请求,应让浏览器直接访问签约入口。
- 如果前端通过 Ajax 请求签约接口,即使取得了 HTML,页面通常也不会自动完成顶层跳转。应使用
window.location.href、普通链接或表单访问本站签约入口。 - 沙箱环境和生产环境的支付宝网关、应用配置、回调地址及密钥必须相互对应,不能混用。
备注:内容仅供参考。
