网站应用如何生成支付宝商户二维码支付码
结论
网站应用可以通过支付宝当面付的 alipay.trade.precreate 接口,在服务端创建预订单,再把接口返回的 qr_code 渲染成二维码供用户扫描。用户使用支付宝扫码后即可付款,当前网页无须跳转到支付宝收银台。
支付是否成功,应以支付宝异步通知或主动查询结果为准,不能只看前端页面的提示。
实现原理
支付流程如下:
- 网站服务端生成唯一的商户订单号。
- 服务端调用
alipay.trade.precreate创建支付宝预订单。 - 支付宝返回
qr_code。 - 浏览器将该字符串渲染成二维码。
- 用户使用支付宝扫码并确认支付。
- 支付宝向商户配置的
notify_url发送异步通知。 - 商户验签并核对订单信息,然后将本地订单更新为已支付。
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 发送支付结果。服务端收到通知后,需要完成以下处理:
- 使用支付宝 SDK 验证签名;
- 核对
app_id、seller_id等商户身份信息; - 根据
out_trade_no查找本地订单; - 核对
total_amount和本地订单金额; - 确认交易状态符合业务要求;
- 以幂等方式更新订单;
- 按照支付宝协议返回成功响应。
示意代码如下:
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 对应的证书客户端初始化方式,不能混用证书模式和普通公钥模式的配置。
备注:内容仅供参考。
