支付宝扫码打开 H5 获取 openId 并唤起支付是否可行?
结论
可以实现。
用户通过支付宝扫码进入 H5 后,可先通过支付宝网页授权获取用户标识,再由服务端创建支付订单,最后从 H5 唤起支付宝收银台完成付款。
需要注意两点:
- 支付宝的用户标识可能是
user_id,也可能是open_id。具体返回哪个字段,取决于应用类型、已开通能力和当前开放平台接口规则,不能直接套用微信的openid概念。 - 获取用户标识和发起支付是两个独立流程。支付本身不要求预先获取用户标识。只有需要识别、绑定或记录当前支付宝用户时,才需要先授权。
推荐实现流程
完整流程如下:
支付宝扫码
→ 打开商户 H5
→ 跳转支付宝静默授权
→ 回调 H5 并携带 auth_code
→ 服务端用 auth_code 换取用户标识
→ 服务端创建支付订单
→ H5 跳转或提交表单进入支付宝收银台
→ 支付宝异步通知服务端
→ 服务端验签并更新订单状态
授权换取用户标识、创建订单、签名和异步通知验签都必须由服务端完成。应用私钥不能放在 H5 JavaScript 中。
第一步:申请并配置相关能力
先在支付宝开放平台创建应用,再根据实际场景开通以下能力:
- 支付宝网页授权;
- 手机网站支付,即
alipay.trade.wap.pay; - 如果要在支付宝客户端内使用 JSAPI 支付,还需开通对应的支付产品和权限。
还需要配置:
- 授权回调地址;
- 支付完成后的同步跳转地址;
- 支付结果异步通知地址;
- 应用公钥证书或公钥;
- 支付宝公钥证书或支付宝公钥;
- H5 域名及平台要求的其他安全配置。
应用能否直接取得 open_id,应以应用后台实际开通的产品和 alipay.system.oauth.token 的返回内容为准。如果接口只返回 user_id,就保存 user_id,不要自行生成或转换成所谓的 openId。
第二步:发起静默授权
如果只需识别用户,不需要昵称、头像等信息,可以使用基础授权范围。常见的授权地址如下:
https://openauth.alipay.com/oauth2/publicAppAuthorize.htm
?app_id=YOUR_APP_ID
&scope=auth_base
&redirect_uri=ENCODED_CALLBACK_URL
&state=RANDOM_STATE
授权地址可以由服务端生成。前端确认当前请求尚未完成授权后,也可以直接跳转:
const callbackUrl = encodeURIComponent(
"https://pay.example.com/alipay/oauth/callback"
);
const state = "由服务端生成的一次性随机值";
location.href =
"https://openauth.alipay.com/oauth2/publicAppAuthorize.htm" +
"?app_id=YOUR_APP_ID" +
"&scope=auth_base" +
"&redirect_uri=" + callbackUrl +
"&state=" + encodeURIComponent(state);
授权完成后,支付宝会将用户带回回调地址,并附上 auth_code 和原来的 state:
https://pay.example.com/alipay/oauth/callback
?auth_code=AUTH_CODE
&state=RANDOM_STATE
服务端必须校验 state,以防止伪造回调和请求串联。
第三步:服务端换取用户标识
服务端收到 auth_code 后,调用 alipay.system.oauth.token 获取授权结果。下面是 Java 示例,具体类名可能因支付宝 SDK 版本不同而略有差异:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipaySystemOauthTokenRequest request =
new AlipaySystemOauthTokenRequest();
request.setGrantType("authorization_code");
request.setCode(authCode);
AlipaySystemOauthTokenResponse response =
alipayClient.execute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"支付宝授权失败:" + response.getSubMsg()
);
}
String userId = response.getUserId();
// 部分应用或新版接口模型可能提供 open_id。
// 是否存在对应 getter,应以所使用 SDK 和实际响应为准。
String openId = null;
不能只根据 SDK 中是否存在某个 getter,判断平台是否会返回对应字段。联调时应记录原始业务响应,并对照开放平台当前接口文档和应用权限进行核对。
授权码通常只能使用一次,而且有效期较短。H5 不应保存授权码并反复使用。服务端取得用户标识后,可以建立自己的登录会话,例如通过安全的 HttpOnly Cookie 保存会话编号。
第四步:创建手机网站支付订单
取得用户标识后,服务端调用 alipay.trade.wap.pay 创建手机网站支付订单。该接口通常不要求传入买家的 open_id 或 user_id,买家会在支付宝收银台完成身份确认。
Java 示例:
AlipayTradeWapPayRequest request =
new AlipayTradeWapPayRequest();
request.setReturnUrl(
"https://pay.example.com/payment/return"
);
request.setNotifyUrl(
"https://pay.example.com/payment/notify"
);
AlipayTradeWapPayModel model =
new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_202609090001");
model.setTotalAmount("99.00");
model.setSubject("订单 ORDER_202609090001");
model.setProductCode("QUICK_WAP_WAY");
request.setBizModel(model);
AlipayTradeWapPayResponse response =
alipayClient.pageExecute(request);
if (!response.isSuccess()) {
throw new IllegalStateException(
"创建支付宝支付失败:" + response.getSubMsg()
);
}
String paymentForm = response.getBody();
paymentForm 通常是一段需要提交至支付宝网关的 HTML 表单。服务端可以直接将其作为 HTML 返回给浏览器:
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(paymentForm);
浏览器加载表单后会进入支付宝收银台。在支付宝客户端的内置浏览器中,通常会直接进入客户端支付流程。如果页面在其他浏览器中打开,实际体验取决于手机系统、支付宝的安装情况以及平台当时的跳转策略。
另一种方式:支付宝客户端内使用 JSAPI
如果业务明确要求在支付宝客户端内直接调起支付,服务端也可以先通过 alipay.trade.create 创建交易,取得支付宝交易号 trade_no,再由 H5 调用支付宝桥接能力:
function pay(tradeNo) {
AlipayJSBridge.call(
"tradePay",
{
tradeNO: tradeNo
},
function (result) {
console.log("tradePay result:", result);
}
);
}
if (window.AlipayJSBridge) {
pay("服务端返回的 trade_no");
} else {
document.addEventListener(
"AlipayJSBridgeReady",
function () {
pay("服务端返回的 trade_no");
},
false
);
}
这种方式对运行环境的要求更严格:
- 页面必须运行在支持
AlipayJSBridge的支付宝客户端环境中; - 应用必须拥有对应的 JSAPI 和支付权限;
- 交易仍然必须由服务端创建;
- JSAPI 回调不能作为最终到账依据。
如果不能保证页面始终在支付宝客户端内打开,建议优先使用 alipay.trade.wap.pay,其兼容性通常更容易控制。
支付结果必须以后端异步通知为准
同步跳转地址 return_url 只用于展示支付结果页面。用户可能中途关闭页面,跳转参数也可能被伪造,因此订单是否支付成功,必须以 notify_url 收到的支付宝异步通知为准。
服务端处理通知时,至少需要完成以下检查和操作:
- 使用支付宝公钥验签;
- 检查
app_id是否属于当前应用; - 检查
out_trade_no是否存在; - 检查通知金额是否与本地订单金额一致;
- 检查商户身份相关字段;
- 判断交易状态是否符合业务要求;
- 幂等更新订单,防止重复发货;
- 处理成功后,按支付宝要求返回成功响应。
示意代码:
Map<String, String> params = extractRequestParameters(request);
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
response.getWriter().write("failure");
return;
}
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
// 继续校验 app_id、商户身份、订单金额和订单状态。
// 更新操作必须保证幂等。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
markOrderPaidIdempotently(outTradeNo, totalAmount);
response.getWriter().write("success");
} else {
response.getWriter().write("failure");
}
注意事项
- 扫码打开支付宝 H5,不代表系统已经取得用户身份。二维码中的普通 H5 地址仍需经过网页授权流程。
- 如果二维码可能被微信、系统相机或普通浏览器扫描,需要增加环境判断和相应提示,不能默认一定存在
AlipayJSBridge。 auth_code应由服务端兑换。应用私钥、支付宝密钥和签名逻辑不得下发到前端。- 商品名称、金额和商户订单号必须由服务端根据本地订单生成,不能直接采用前端传入的金额。
- 每次支付都应使用唯一的
out_trade_no。同一业务订单需要重复支付时,应先查询原交易状态,避免产生多笔有效交易。 - 用户标识应以支付宝接口实际返回的字段为准。如果返回的是
user_id,不要将其称为open_id。 - 获取用户标识并非支付的必要前置条件。如果只需让用户付款,直接创建 WAP 支付订单即可。如果还需进行会员绑定、付款人识别或后续业务关联,再加入静默授权流程。
- 网页授权、WAP 支付和 JSAPI 支付的准入条件可能随应用类型和平台规则调整。上线前,应在对应应用后台确认当前可用能力,并完成沙箱或正式环境联调。
备注:内容仅供参考。
