PAYATHON 2026

外部 App 内嵌 H5 如何唤起支付宝支付

支付老李

结论

这两种场景适合采用不同的支付方案:

  • 外部 App 原生页面直接发起支付:使用支付宝的 App 支付产品。服务端调用 alipay.trade.app.pay 生成签名后的订单字符串,再由 App 通过支付宝客户端 SDK 发起支付。
  • 外部 App 内嵌 H5 发起支付:使用手机网站支付产品。服务端调用 alipay.trade.wap.pay 生成支付表单或跳转地址,H5 提交表单后进入支付宝收银台。

使用手机网站支付时,外部 H5 通常不需要集成支付宝 JS-SDK。前端只需提交服务端返回的支付表单,或者跳转到支付地址。支付参数的组装与签名,以及异步通知的验签,都必须由服务端完成。

App 内嵌 WebView 对支付宝客户端唤起、URL Scheme 和支付后返回 App 的支持,与系统浏览器并不完全相同。实际项目中,优先使用系统浏览器打开手机网站支付页面,通常兼容性更好。

为什么不建议两种场景统一使用手机网站支付

手机网站支付面向浏览器和 H5 页面,支付流程通常如下:

H5 请求业务服务端
→ 服务端调用 alipay.trade.wap.pay
→ H5 提交支付表单
→ 浏览器跳转支付宝收银台
→ 用户完成支付
→ 支付宝发送异步通知

外部 App 的原生支付流程则是:

App 请求业务服务端
→ 服务端调用 alipay.trade.app.pay
→ 服务端返回 orderString
→ App 调用支付宝客户端 SDK
→ 支付宝 App 完成支付
→ SDK 返回客户端结果
→ 支付宝发送异步通知

原生 App 也可以先打开 H5 页面,再通过手机网站支付完成付款,但这种做法会带来额外的 WebView 兼容、支付宝客户端唤起、浏览器跳转和支付后返回问题。如果原生场景已经具备 App 支付能力,就没有必要再绕到 H5 支付链路。

App 内嵌 H5 的接入方式

1. H5 向业务服务端创建订单

H5 只负责传递业务订单信息。支付宝应用私钥不能保存在前端,签名也不能由前端生成。

async function createPayment() {
  const response = await fetch('/api/pay/alipay/wap', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      orderId: 'ORDER_202609080001'
    })
  });

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

  const html = await response.text();

  // html 必须来自可信的业务服务端。
  document.open();
  document.write(html);
  document.close();
}

服务端也可以返回一个业务支付页地址,再由前端跳转:

window.location.href = paymentUrl;

使用表单还是 URL,取决于项目所用支付宝服务端 SDK 的返回形式。不要在前端自行拼接签名参数。

2. 服务端调用手机网站支付接口

下面是 Java SDK 的结构示例。类名和请求模型可能随 SDK 版本变化,具体写法应以项目使用的官方 SDK 文档为准:

AlipayClient alipayClient = new DefaultAlipayClient(
    gatewayUrl,
    appId,
    appPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setNotifyUrl("https://example.com/api/pay/alipay/notify");
request.setReturnUrl("https://example.com/pay/result");

AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_202609080001");
model.setTotalAmount("99.00");
model.setSubject("订单支付");
model.setProductCode("QUICK_WAP_WAY");

request.setBizModel(model);

AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);

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

// 返回可提交的 HTML form。
String paymentForm = response.getBody();

服务端可以把 paymentForm 直接返回给 H5,也可以通过一个中间支付页面输出。由于浏览器会执行这部分内容,相关接口必须进行登录校验和订单归属校验,并防止重复下单。

3. 处理支付异步通知

支付是否成功,要以支付宝服务端发送到 notify_url 的异步通知为准。不能只看 H5 的跳转结果,也不能只依赖 App 收到的客户端回调。

处理通知时,至少要完成以下检查和操作:

  1. 使用支付宝公钥或支付宝公钥证书验证签名。
  2. 检查 app_id 是否属于当前应用。
  3. 检查 out_trade_no 是否存在,并确认它对应当前订单。
  4. 检查 total_amount、币种和收款方信息是否与业务订单一致。
  5. 判断 trade_status。
  6. 在幂等事务中更新订单状态。
  7. 处理成功后,按照接口要求返回成功响应。

伪代码如下:

Map<String, String> params = getAlipayNotifyParams(request);

boolean verified = AlipaySignature.rsaCheckV1(
    params,
    alipayPublicKey,
    "UTF-8",
    "RSA2"
);

if (!verified) {
    return "failure";
}

Order order = orderService.findByOrderNo(params.get("out_trade_no"));

if (order == null
        || !appId.equals(params.get("app_id"))
        || order.getAmount().compareTo(
            new BigDecimal(params.get("total_amount"))
        ) != 0) {
    return "failure";
}

String tradeStatus = params.get("trade_status");

if ("TRADE_SUCCESS".equals(tradeStatus)
        || "TRADE_FINISHED".equals(tradeStatus)) {
    orderService.markPaidIdempotently(
        order.getOrderNo(),
        params.get("trade_no")
    );
}

return "success";

客户端在展示“支付成功”前,还可以主动查询业务服务端的订单状态,以免异步通知尚未到达时误判结果。

前端是否需要支付宝 JS-SDK

在普通外部浏览器或外部 App WebView 中使用手机网站支付,通常不需要支付宝 JS-SDK。

支付宝 JSAPI 或相关 JS-SDK 一般依赖特定的运行环境,例如支付宝客户端内的 H5 容器。在外部 App 自己的 WebView 中引入脚本,并不会自动获得支付宝客户端的原生支付能力。

这个场景可以按以下方式划分职责:

  • 服务端:创建订单、调用支付接口、生成签名、验证签名并接收异步通知。
  • H5 前端:请求创建订单,提交服务端返回的表单,或者执行页面跳转。
  • 外部 App:处理 WebView 导航、外部协议、系统浏览器打开和支付后的页面返回。
  • 支付宝客户端 SDK:仅用于原生 App 支付方案。

外部 App 原生支付示例

原生页面需要直接发起支付时,服务端应调用 alipay.trade.app.pay,再把完整的签名订单字符串返回给 App:

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setNotifyUrl("https://example.com/api/pay/alipay/notify");

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("ORDER_202609080002");
model.setTotalAmount("99.00");
model.setSubject("订单支付");
model.setProductCode("QUICK_MSECURITY_PAY");

request.setBizModel(model);

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

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

String orderString = response.getBody();

App 应将 orderString 原样交给支付宝客户端 SDK。不要拆开参数重新排序或拼接,也不要在客户端使用应用私钥签名。

WebView 接入时的注意事项

支付页面的打开方式

部分 WebView 会拦截非 HTTP/HTTPS 协议、跨应用跳转、新窗口或重定向,导致支付宝客户端无法正常唤起。可以按以下顺序处理:

  1. 优先使用系统浏览器打开 H5 支付页面。
  2. 如果必须在 WebView 内完成流程,App 需要正确处理外部协议和页面跳转。
  3. 如果无法唤起支付宝客户端,应允许用户继续使用网页收银台,不能直接认定支付失败。

Android WebView 可以在导航回调中识别非 HTTP/HTTPS 地址,再交给系统处理。允许哪些地址,需要按照支付宝当前的支付链路和安全策略配置,不宜使用宽泛规则放行所有自定义协议。

override fun shouldOverrideUrlLoading(
    view: WebView,
    request: WebResourceRequest
): Boolean {
    val uri = request.url
    val scheme = uri.scheme?.lowercase()

    if (scheme == "http" || scheme == "https") {
        return false
    }

    return try {
        val intent = Intent(Intent.ACTION_VIEW, uri)
        view.context.startActivity(intent)
        true
    } catch (e: ActivityNotFoundException) {
        true
    }
}

生产环境还要配置 Scheme 白名单、校验目标应用,并处理跳转异常。不能把任意自定义协议直接交给系统执行。

支付后的返回

return_url 只用于同步页面跳转,不能作为可信的支付结果。如果需要从 H5 返回外部 App,可以通过业务中间页配合 Universal Links、App Links 或受控的自定义 Scheme 实现。

支付结果页应先查询服务端订单状态:

async function queryOrder(orderId) {
  const response = await fetch(
    `/api/orders/${encodeURIComponent(orderId)}/payment-status`
  );
  const result = await response.json();

  if (result.status === 'PAID') {
    // 展示支付成功,或通过受控链接返回 App。
  } else {
    // 展示处理中,并允许用户重新查询。
  }
}

不要直接把 return_url 中的查询参数当作支付成功凭证。

安全与兼容性

  • 应用私钥只能保存在服务端。
  • 金额、商品名称和订单号应由服务端根据业务订单生成。
  • 同一个业务订单需要防止重复支付。
  • 异步通知处理必须保证幂等。
  • WebView 不能忽略 HTTPS 证书错误。
  • 不能无条件放行所有 URL Scheme。
  • Android 和 iOS 对外部应用跳转的限制不同,需要分别测试。
  • 测试范围需要包括已安装支付宝、未安装支付宝、取消支付、支付完成后切回 App,以及异步通知延迟等情况。
  • 能否直接从 WebView 唤起支付宝,还取决于系统版本、WebView 配置和支付宝当前的收银台策略,最终应以真实设备的联调结果为准。

原生页面使用 App 支付,内嵌 H5 使用手机网站支付,职责划分会更清楚。H5 前端不需要为手机网站支付额外集成支付宝 JS-SDK。

备注:内容仅供参考。