PAYATHON 2026

网站应用如何生成支付宝商户二维码支付码

支付老李

结论

网站应用可以通过支付宝当面付的 alipay.trade.precreate 接口,在服务端创建预订单,再把接口返回的 qr_code 渲染成二维码供用户扫描。用户使用支付宝扫码后即可付款,当前网页无须跳转到支付宝收银台。

支付是否成功,应以支付宝异步通知或主动查询结果为准,不能只看前端页面的提示。

实现原理

支付流程如下:

  1. 网站服务端生成唯一的商户订单号。
  2. 服务端调用 alipay.trade.precreate 创建支付宝预订单。
  3. 支付宝返回 qr_code。
  4. 浏览器将该字符串渲染成二维码。
  5. 用户使用支付宝扫码并确认支付。
  6. 支付宝向商户配置的 notify_url 发送异步通知。
  7. 商户验签并核对订单信息,然后将本地订单更新为已支付。

qr_code 是生成二维码所需的内容,不一定是图片地址。前端需要使用二维码组件,将其编码成二维码图片。

接入前提

开发前需要确认以下条件:

  • 已创建并配置支付宝开放平台应用;
  • 应用已完成签约,并具备当面付相关接口权限;
  • 已配置应用私钥和支付宝公钥,或者根据当前应用配置使用证书模式;
  • 网站有一个支付宝可以访问的 HTTPS 异步通知地址;
  • 支付金额、订单号和商品信息均由服务端生成。

具体产品名称、签约条件和开放平台控制台配置可能发生调整,请以支付宝开放平台中当前应用实际拥有的接口权限为准。

服务端创建二维码订单

下面以 Java SDK 为例。网关地址、应用 ID、密钥和字符集应从安全配置中读取,不要直接写进业务代码。

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;

public class AlipayQrPayService {

    private final AlipayClient alipayClient;

    public AlipayQrPayService(
            String appId,
            String merchantPrivateKey,
            String alipayPublicKey) {

        this.alipayClient = new DefaultAlipayClient(
                "https://openapi.alipay.com/gateway.do",
                appId,
                merchantPrivateKey,
                "json",
                "UTF-8",
                alipayPublicKey,
                "RSA2"
        );
    }

    public String createQrCode(
            String outTradeNo,
            String totalAmount,
            String subject) throws Exception {

        AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
        model.setOutTradeNo(outTradeNo);
        model.setTotalAmount(totalAmount);
        model.setSubject(subject);
        model.setTimeoutExpress("10m");

        AlipayTradePrecreateRequest request =
                new AlipayTradePrecreateRequest();

        request.setBizModel(model);
        request.setNotifyUrl(
                "https://pay.example.com/api/alipay/notify"
        );

        AlipayTradePrecreateResponse response =
                alipayClient.execute(request);

        if (!response.isSuccess()) {
            throw new IllegalStateException(
                    "创建支付宝预订单失败:"
                            + response.getCode()
                            + " / "
                            + response.getSubCode()
                            + " / "
                            + response.getSubMsg()
            );
        }

        return response.getQrCode();
    }
}

服务端可以把自己的订单号和二维码内容返回给前端:

{
  "outTradeNo": "ORDER_202609130001",
  "qrCode": "支付宝接口返回的二维码内容",
  "status": "WAIT_BUYER_PAY"
}

out_trade_no 在商户系统中必须保持唯一。金额建议使用十进制字符串处理,例如 "99.00",不要用浮点数参与金额计算。

前端展示二维码

浏览器收到 qrCode 后,可以通过二维码组件直接渲染。下面以 qrcode 库为例:

<canvas id="alipay-qrcode"></canvas>

<script type="module">
  import QRCode from "https://cdn.jsdelivr.net/npm/qrcode/+esm";

  async function showPaymentQrCode(qrCode) {
    const canvas = document.getElementById("alipay-qrcode");

    await QRCode.toCanvas(canvas, qrCode, {
      width: 260,
      margin: 2,
      errorCorrectionLevel: "M"
    });
  }

  async function createPayment() {
    const response = await fetch("/api/orders/alipay-qrcode", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        productId: "SKU-1001",
        quantity: 1
      })
    });

    if (!response.ok) {
      throw new Error("创建支付订单失败");
    }

    const result = await response.json();
    await showPaymentQrCode(result.qrCode);

    watchPaymentStatus(result.outTradeNo);
  }
</script>

在生产环境中,可以把二维码库打包进网站的静态资源,避免支付页面依赖第三方 CDN。

获取支付结果

异步通知

支付宝会向 notify_url 发送支付结果。服务端收到通知后,需要完成以下处理:

  1. 使用支付宝 SDK 验证签名;
  2. 核对 app_id、seller_id 等商户身份信息;
  3. 根据 out_trade_no 查找本地订单;
  4. 核对 total_amount 和本地订单金额;
  5. 确认交易状态符合业务要求;
  6. 以幂等方式更新订单;
  7. 按照支付宝协议返回成功响应。

示意代码如下:

import com.alipay.api.internal.util.AlipaySignature;

import java.util.Map;

public boolean verifyNotify(
        Map<String, String> params,
        String alipayPublicKey) throws Exception {

    return AlipaySignature.rsaCheckV1(
            params,
            alipayPublicKey,
            "UTF-8",
            "RSA2"
    );
}

即使验签通过,也不能直接把订单改成已支付。还要逐项核对金额、商户身份和订单当前状态。

通知处理必须支持重复调用。支付宝可能会对同一笔交易发送多次通知,重复通知不能造成重复发货、重复充值或重复记账。

前端轮询

为了及时在扫码页面显示支付结果,前端可以定时查询商户服务端的订单状态:

function watchPaymentStatus(outTradeNo) {
  const timer = setInterval(async () => {
    const response = await fetch(
      `/api/orders/${encodeURIComponent(outTradeNo)}/status`
    );

    if (!response.ok) {
      return;
    }

    const result = await response.json();

    if (result.status === "PAID") {
      clearInterval(timer);
      window.location.href =
        `/payment/success?order=${encodeURIComponent(outTradeNo)}`;
    }

    if (
      result.status === "CLOSED" ||
      result.status === "EXPIRED"
    ) {
      clearInterval(timer);
      alert("二维码已失效,请重新下单");
    }
  }, 2000);
}

这个接口应查询商户自己的订单状态。如果异步通知还没有到达,服务端也可以在必要时调用 alipay.trade.query,主动查询支付宝的交易状态。

不要让浏览器携带应用私钥直接请求支付宝,也不能把前端轮询结果作为财务入账依据。

二维码过期与订单关闭

二维码需要设置合理的有效时间,例如通过 timeout_express 限制支付期限。超过期限后,页面应停止轮询,并提示用户重新创建订单。

对于已经取消、超时或业务失败的订单,可以根据实际状态调用 alipay.trade.close,关闭尚未支付的交易。调用关闭接口前,应先确认订单没有支付成功,避免支付和关单并发引发状态判断错误。

常见问题

为什么不能直接使用网页支付接口生成二维码?

电脑网站支付通常会跳转到支付宝收银台,而 alipay.trade.precreate 用于预下单后展示二维码的扫码支付场景。两者的交互方式不同,不能简单地把跳转链接当成标准扫码支付流程。

能否由前端直接生成订单?

不可以。创建预订单需要用商户私钥签名,而私钥只能保存在服务端。否则,任何访问网页的人都可能取得商户密钥并伪造请求。

用户扫码后,网站是否还需要跳转?

支付过程中不需要离开当前页面。付款成功后,网站可以按照自身业务需要跳转到结果页,但这是商户网站内部的跳转,并不是跳转到支付宝收银台。

如何防止用户篡改金额?

前端只能提交商品编号、数量或订单标识。商品实际价格、优惠和应付金额必须由服务端重新计算,再使用服务端保存的金额调用支付宝接口。不要接受前端直接传入的最终支付金额。

注意事项

  • 正式环境使用 https://openapi.alipay.com/gateway.do,不能混用沙箱和正式环境的应用、密钥及交易数据。
  • 私钥、证书和支付宝公钥应存放在密钥管理系统或受保护的服务端配置中。
  • notify_url 必须能被支付宝服务器访问,不能使用内网地址,也不能依赖浏览器登录状态。
  • 异步通知接口不应执行耗时操作。可以先完成验签和订单落库,再异步处理发货等业务。
  • 更新订单状态时应使用事务、唯一约束或条件更新,保证幂等性。
  • 支付成功页只用于展示结果,不能触发实际发货。
  • 日志中不要记录私钥、完整签名材料或非必要的用户敏感信息。
  • 如果应用使用证书模式,应采用支付宝 SDK 对应的证书客户端初始化方式,不能混用证书模式和普通公钥模式的配置。

备注:内容仅供参考。