PAYATHON 2026

v3 版本 alipay.trade.page.pay 接口如何调用?

支付阿杰

结论

alipay.trade.page.pay 是电脑网站支付接口。调用 v3 版本时,应由服务端向支付宝提交订单,不能在浏览器中携带应用私钥直接发起请求。

基本流程如下:

  1. 服务端生成唯一的商户订单号。
  2. 组装订单 JSON。
  3. 使用应用私钥,按照 v3 规则生成 Authorization 请求头。
  4. 向 /v3/alipay/trade/page/pay 发送 POST 请求。
  5. 将支付宝返回的页面跳转数据传给浏览器。
  6. 以 notify_url 收到的异步通知作为最终支付结果依据。

生产环境通常使用以下请求地址:

https://openapi.alipay.com/v3/alipay/trade/page/pay

沙箱地址、签名参数和响应字段可能因接入模式及 SDK 版本而异,应以当前支付宝开放平台控制台和接口文档为准。

调用前的准备

调用接口前,需要在支付宝开放平台完成以下配置:

  • 创建网页或移动应用并取得 app_id
  • 签约并开通电脑网站支付能力
  • 配置应用公钥,并妥善保存应用私钥
  • 获取支付宝公钥,或配置支付宝公钥证书
  • 准备可从公网访问的 notify_url
  • 确认应用、商户账号及支付产品均处于可用状态

应用私钥只能保存在服务端,不能写入 JavaScript、HTML、移动端安装包或公开仓库。

请求参数

最基本的业务参数如下:

{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "99.00",
  "subject": "测试商品",
  "product_code": "FAST_INSTANT_TRADE_PAY"
}

主要参数的含义如下:

参数说明
out_trade_no商户订单号,同一商户下必须唯一
total_amount订单金额,使用字符串表示,例如 "99.00"
subject订单标题,会显示在支付页面或账单中
product_code电脑网站支付通常使用 FAST_INSTANT_TRADE_PAY

return_url 是用户完成支付后的浏览器跳转地址,notify_url 用于接收支付宝服务器发送的异步通知。使用 SDK 时,这两个地址通常通过请求对象的专用方法设置。直接调用 v3 HTTP 接口时,应按照当前接口文档规定的位置传递,不要自行猜测并放入业务 JSON。

HTTP 请求示例

以下示例用于说明请求结构。Authorization 不能直接照抄,必须根据应用私钥和本次请求的实际内容动态生成:

curl --request POST \
  'https://openapi.alipay.com/v3/alipay/trade/page/pay' \
  --header 'Content-Type: application/json; charset=UTF-8' \
  --header 'Alipay-Request-Id: 6f39f4ba-20a0-4ce2-8ef2-6bbb8df6f001' \
  --header 'Authorization: ALIPAY-SHA256withRSA app_id="你的APP_ID",timestamp="请求时间戳",nonce="随机字符串",sign="计算后的签名"' \
  --data '{
    "out_trade_no": "ORDER_202609090001",
    "total_amount": "99.00",
    "subject": "测试商品",
    "product_code": "FAST_INSTANT_TRADE_PAY"
  }'

v3 请求签名并不是只对业务 JSON 做一次 RSA 加密。待签名内容通常还包括 HTTP 方法、请求路径、时间戳、随机数和请求正文等信息。不同密钥模式还可能要求传递证书序列号,因此建议优先使用支付宝提供且明确支持该接口协议的服务端 SDK 来完成签名和验签。

Java 调用结构

如果项目暂时没有合适的 v3 SDK,也可以使用 HTTP 客户端调用,但签名部分应封装为经过验证的组件:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public class AlipayPagePayClient {

    private static final String API_PATH =
            "/v3/alipay/trade/page/pay";

    private static final String API_URL =
            "https://openapi.alipay.com" + API_PATH;

    private final HttpClient httpClient = HttpClient.newHttpClient();
    private final AlipayV3Signer signer;

    public AlipayPagePayClient(AlipayV3Signer signer) {
        this.signer = signer;
    }

    public String createPayPage() throws Exception {
        String requestBody = """
                {
                  "out_trade_no": "ORDER_202609090001",
                  "total_amount": "99.00",
                  "subject": "测试商品",
                  "product_code": "FAST_INSTANT_TRADE_PAY"
                }
                """;

        String requestId = UUID.randomUUID().toString();

        // 必须根据真实请求路径、请求正文和当前签名模式动态生成。
        String authorization = signer.createAuthorization(
                "POST",
                API_PATH,
                requestBody
        );

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(API_URL))
                .header("Content-Type", "application/json; charset=UTF-8")
                .header("Alipay-Request-Id", requestId)
                .header("Authorization", authorization)
                .POST(HttpRequest.BodyPublishers.ofString(
                        requestBody,
                        StandardCharsets.UTF_8
                ))
                .build();

        HttpResponse<String> response = httpClient.send(
                request,
                HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)
        );

        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            throw new IllegalStateException(
                    "支付宝下单失败,HTTP 状态码:" + response.statusCode()
                            + ",响应:" + response.body()
            );
        }

        return response.body();
    }

    public interface AlipayV3Signer {
        String createAuthorization(
                String method,
                String path,
                String requestBody
        );
    }
}

这里的 AlipayV3Signer 只是示意用的签名组件接口,并非支付宝 SDK 中固定存在的类。实际项目应使用官方 SDK 提供的认证模块,或者严格按照支付宝开放平台当前的 v3 签名规范实现。

不要凭经验拼接待签名字符串。字符编码、换行符、请求路径、JSON 原文或证书序列号中任何一项不一致,都可能导致验签失败。

将支付页面返回给浏览器

下单接口返回成功,并不代表用户已经完成支付。服务端还要将响应中的页面跳转数据传给浏览器。

不同 SDK 返回的结果可能是:

  • 可跳转的支付 URL
  • 自动提交的 HTML <form>
  • page_redirection_data 一类字段
  • SDK 响应对象中的页面正文

具体处理方式取决于实际响应格式。如果返回的是完整 HTML,可以由控制器直接输出:

response.setStatus(200);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(payPageHtml);

如果返回的是 HTTPS 地址,则让浏览器跳转:

response.sendRedirect(payUrl);

不要将整个支付宝 JSON 响应直接展示给用户。在没有确认内容类型时,也不要把 HTML 表单当作 URL 使用。

异步通知处理

return_url 只是浏览器跳转地址。用户可能关闭页面、断网或阻止跳转,因此不能根据同步跳转结果将订单标记为已支付。

在 notify_url 对应的服务端接口中,应执行以下处理:

  1. 读取支付宝通知参数。
  2. 使用支付宝公钥或证书验证签名。
  3. 检查 app_id、商户身份和通知来源。
  4. 检查 out_trade_no 是否存在。
  5. 核对订单金额和币种。
  6. 判断交易状态是否符合成功条件。
  7. 以幂等方式更新商户订单。
  8. 按照支付宝协议返回成功应答。

示意代码如下:

@PostMapping("/alipay/notify")
public String handleNotify(HttpServletRequest request) {
    Map<String, String> parameters = extractParameters(request);

    if (!alipayVerifier.verify(parameters)) {
        return "failure";
    }

    String outTradeNo = parameters.get("out_trade_no");
    String tradeNo = parameters.get("trade_no");
    String tradeStatus = parameters.get("trade_status");
    String totalAmount = parameters.get("total_amount");

    Order order = orderService.findByOutTradeNo(outTradeNo);
    if (order == null) {
        return "failure";
    }

    if (!order.getTotalAmount().toPlainString().equals(totalAmount)) {
        return "failure";
    }

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        orderService.markPaidIdempotently(outTradeNo, tradeNo);
    }

    return "success";
}

extractParameters、alipayVerifier 和 orderService 都是业务示意,并非支付宝 SDK 的固定 API。实际通知字段、成功状态和应答格式应以当前接口文档为准。

常见问题

签名失败

可以重点检查以下内容:

  • 使用的是应用私钥,而不是支付宝公钥
  • app_id 与私钥所属应用一致
  • 请求正文与参与签名的正文完全相同
  • JSON 在签名后没有被重新格式化或修改
  • 请求路径包含完整的 /v3/alipay/trade/page/pay
  • 时间戳和服务器时间没有明显偏差
  • 没有混用普通公钥模式和证书模式
  • 字符编码统一为 UTF-8

支付页面没有打开

先确认接口响应是 URL、HTML 表单,还是经过 JSON 包装的数据。服务端取得页面数据后,还要将其返回给浏览器或让浏览器跳转。只在后台打印响应,并不会自动打开支付页面。

重复下单

out_trade_no 必须由商户生成,并且保持唯一。用户重复点击支付按钮时,建议复用尚未失效的订单,或通过服务端幂等机制避免创建多笔业务订单。

支付成功但订单状态未更新

常见原因是只处理了 return_url,没有正确接收异步通知,或者通知验签、金额核对失败。应保存通知日志和支付宝交易号,并通过交易查询接口进行补偿核查。

注意事项

  • 金额应使用十进制字符串或 BigDecimal,不要用 double 进行金额计算。
  • notify_url 必须能被支付宝服务器从公网访问,不能使用 localhost。
  • 异步通知可能重复发送,订单更新必须支持幂等。
  • 不要只根据前端传来的金额、商品名称或订单状态创建支付请求。
  • 日志中可以记录订单号、请求 ID 和错误码,但不能记录应用私钥、完整签名材料或敏感个人信息。
  • 沙箱和生产环境的应用、密钥、网关地址及商户数据不能混用。
  • 如果当前 SDK 仍通过 gateway.do 和 pageExecute 调用,则属于另一种接入封装。不要将它的签名方式直接套用到 /v3/alipay/trade/page/pay。

备注:内容仅供参考。