PAYATHON 2026

安卓手机首次唤起APP支付成功,后续转为H5支付

支付小周

结论

这通常不是”支付宝安装状态失效”,而是第二次支付走了另一条调用链。第一次调用的是支付宝 App SDK,后续请求可能切换到了 H5 支付接口或 WebView URL,也可能触发了业务代码中的 H5 降级逻辑。

卸载并重新安装业务 App 后,第一次支付又能正常唤起支付宝,说明问题更可能出在本地状态、支付对象复用、回调处理或支付方式缓存上。仅凭这一现象还无法确定具体原因,需要对比前两次支付的客户端调用方式、服务端下单参数和返回内容。

优先排查的原因

1. 后续支付没有继续调用 App 支付 SDK

支付宝 App 支付需要先由服务端调用对应接口生成订单信息,再由客户端调用:

PayTask payTask = new PayTask(activity);
Map<String, String> result = payTask.payV2(orderInfo, true);

如果后续支付采用了以下任一方式,就可能进入 H5 页面:

  • 使用 WebView.loadUrl(...) 加载支付地址
  • 使用浏览器打开支付链接
  • 服务端调用手机网站支付接口
  • 聚合支付平台把渠道切换为 WAP/H5
  • App 支付失败后,业务代码自动降级到 H5

需要确认每次点击支付时是否都执行了 PayTask.payV2(),而不是只有第一次执行。

2. 服务端下单接口或产品码发生变化

App 支付和手机网站支付是两种不同的产品。以支付宝开放平台接口为例,通常需要区分:

  • App 支付:alipay.trade.app.pay
  • 手机网站支付:alipay.trade.wap.pay

如果服务端把第二次请求路由到了 alipay.trade.wap.pay,客户端拿到的就会是 H5 页面或跳转地址。

建议在服务端记录并对比两次请求的以下内容:

支付接口名称
商户订单号 out_trade_no
product_code
app_id
签名方式 sign_type
支付渠道 channel
返回内容类型
是否触发降级逻辑

不要只对比订单金额和商品名称。

3. 客户端保存了错误的支付方式状态

卸载重装后第一次恢复正常,明显指向本地状态对支付流程产生了影响。重点检查:

  • SharedPreferences
  • 本地数据库
  • 单例对象和静态变量
  • WebView Cookie
  • 支付次数或首次支付标记
  • “App 支付失败后使用 H5”的缓存
  • 支付回调后被修改的渠道字段

例如,下面的逻辑会让第一次支付使用 App,之后一直改走 H5:

boolean useH5 = preferences.getBoolean("use_h5_pay", false);

if (useH5) {
    openH5Pay(payUrl);
} else {
    startAliAppPay(orderInfo);
}

需要检查是谁在什么条件下写入了 use_h5_pay。不要因为一次取消支付、回调异常或状态未知,就永久切换支付方式。

4. 支付结果判断错误,误触发 H5 降级

判断支付宝支付结果时,不能只看是否收到回调或返回字符串是否为空。客户端同步结果可能是成功、取消、处理中或失败。最终交易状态通常还需要通过服务端异步通知或主动查询来确认。

示例:

ExecutorService executor = Executors.newSingleThreadExecutor();

executor.execute(() -> {
    PayTask payTask = new PayTask(activity);
    Map<String, String> result = payTask.payV2(orderInfo, true);

    activity.runOnUiThread(() -> {
        String resultStatus = result.get("resultStatus");

        if ("9000".equals(resultStatus)) {
            // 客户端显示支付已受理。
            // 最终结果仍建议向业务服务端查询确认。
        } else if ("8000".equals(resultStatus)) {
            // 支付结果处理中,不应立即永久降级为 H5。
        } else if ("6001".equals(resultStatus)) {
            // 用户取消,不应记为“不支持 App 支付”。
        } else {
            // 记录完整诊断信息。
            // 如需降级,应仅对明确且可恢复的异常生效。
        }
    });
});

如果把用户取消、结果处理中和回调解析失败都视为”无法唤起支付宝”,后续支付就可能被错误地转到 H5。

5. 重复使用第一次的订单信息

每次支付都应向服务端重新创建订单,并获取新的、经过完整签名的 orderInfo。不要重复使用第一次的订单字符串,也不要在客户端修改已经签名的参数。

private void startAliAppPay(String orderInfo) {
    if (orderInfo == null || orderInfo.trim().isEmpty()) {
        throw new IllegalArgumentException("orderInfo is empty");
    }

    Executors.newSingleThreadExecutor().execute(() -> {
        PayTask payTask = new PayTask(this);
        Map<String, String> result = payTask.payV2(orderInfo, true);
        runOnUiThread(() -> handlePayResult(result));
    });
}

服务端应为每笔交易生成唯一的 out_trade_no。如果订单号重复、订单已经关闭或订单状态异常,应直接排查对应错误,不能用 H5 支付来掩盖问题。

6. WebView 或路由拦截导致支付链接在网页中打开

如果支付流程经过 WebView,需要检查:

  • shouldOverrideUrlLoading() 是否拦截了支付宝相关 Scheme
  • 是否强制让所有外部链接留在 WebView 中打开
  • 是否在异常处理时把 Scheme 替换成 H5 URL
  • Android 包可见性配置是否影响支付宝安装检测

在 Android 11 及以上版本中,如果代码通过 PackageManager 判断支付宝是否安装,可能需要声明包可见性。具体包名应以当前使用的支付宝 SDK 和官方接入文档为准,不要凭经验填写。

示意配置:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <queries>
        <package android:name="com.eg.android.AlipayGphone" />
    </queries>

    <application
        android:theme="@style/AppTheme">
        <!-- application components -->
    </application>
</manifest>

不过,包可见性配置错误通常会持续影响检测,不一定只在第二次支付时出现,所以它不是当前现象的首要排查对象。

建议的定位步骤

第一步:记录每次支付入口

点击支付按钮后立即记录:

Log.i("PayTrace", "channel=ALIPAY_APP");
Log.i("PayTrace", "invoke=PayTask.payV2");
Log.i("PayTrace", "orderInfoLength=" + orderInfo.length());

同时为每次支付生成独立的链路编号,用它关联客户端日志和服务端日志。不要在日志中输出完整订单字符串、签名、Token 或用户隐私信息。

第二步:对比第一次和第二次的服务端下单记录

确认两次请求是否都满足以下条件:

使用相同的 App 支付产品
调用相同的支付接口
使用预期的 app_id
每次生成新的 out_trade_no
返回给客户端的是 SDK 订单字符串
没有返回 form、HTML 或 H5 URL
没有命中渠道降级规则

如果第二次返回的内容以 <form、http:// 或 https:// 开头,基本可以确定服务端返回的是网页支付内容,而不是 App SDK 所需的订单信息。

第三步:检查支付完成后的状态修改

全局搜索支付回调中对以下内容的写入:

payType
channel
useH5
fallback
isFirstPay
payFailed
alipayAvailable
SharedPreferences
Cookie

重点检查用户取消支付、返回 App、Activity 重建和回调解析失败时,代码是否修改了默认支付渠道。

第四步:暂时关闭自动降级

调试期间可以先禁止自动跳转 H5,只记录 App 支付失败的信息。这样可以看到真正的错误,避免它被后续打开的 H5 页面掩盖。

if (appPayFailed) {
    Log.e("PayTrace", "App pay failed: " + sanitizedResult);
    showPayError();
    // 调试期间不要立即 openH5Pay(...)
}

第五步:使用最小化调用验证

暂时绕过业务中的支付方式缓存、聚合路由和 WebView,只保留以下链路:

点击支付
→ 服务端创建新的 App 支付订单
→ 客户端取得 orderInfo
→ 调用 PayTask.payV2()
→ 记录同步结果
→ 服务端确认最终订单状态

如果这条最小化链路可以连续唤起支付宝 App,问题就在业务侧的渠道选择或降级逻辑中。如果仍然只有第一次能唤起,则需要继续核对 SDK 集成、订单参数和支付宝客户端环境。

注意事项

  • 不要在客户端生成支付签名,签名应由服务端完成。
  • 不要重复使用 out_trade_no 或旧的 orderInfo。
  • 不要把用户取消支付视为”不支持 App 支付”。
  • 不要只依赖客户端同步结果确认到账,应以服务端异步通知或主动查询结果为准。
  • 不要在日志中打印完整签名、私钥、Token、用户账号或完整支付参数。
  • 如果使用第三方聚合支付 SDK,还要检查它的渠道优先级、失败重试和 H5 兜底配置。实际切换规则应以该平台的日志和文档为准。

结合目前的现象,应该先确认第二次支付是否仍然调用 PayTask.payV2(),服务端是否仍在生成 App 支付订单,以及首次支付结束后是否把 H5 降级状态写入了本地缓存。

备注:内容仅供参考。