Android 通过 alipays:// 协议唤起支付宝 App 支付失败
结论
如果 Android 已经成功打开支付宝,但支付宝提示”操作已超时,请重新发起支付”,通常说明 alipays:// 协议已经正常唤起,问题出在支付宝收到的支付信息无效。常见原因有:
- 支付订单、支付 token 或二维码内容已过期;
alipays://platformapi/startApp中的嵌套 URL 或订单参数编码不正确;- 参数被重复编码或截断,或者经过 Android 的
Uri、WebView、重定向后发生了变化; - 客户端重复使用了之前生成的支付链接;
- 服务端生成的订单参数、签名或支付宝应用配置有误。
iOS 能成功唤起,并不代表两端最终传给支付宝的字符串完全一致。排查时要比较 Android 和 iOS 实际打开的完整 URI,不能只比较服务端返回值。
alipays://platformapi/startApp 是支付宝客户端的跳转协议。App 内支付应优先使用支付宝提供的 Android App 支付 SDK,不建议将自行拼接的协议作为主要支付链路。
先判断失败发生在哪一层
需要先区分两种情况。
支付宝 App 没有打开
重点检查:
- 设备是否安装了支付宝;
Intent是否正确;- URI 是否被 WebView 拦截;
- Android 11 及以上系统是否因包可见性配置导致安装检测失败;
- 是否误将
alipays://当作普通 HTTPS 地址处理; - 系统或浏览器是否禁止当前场景启动外部应用。
支付宝已经打开,但提示超时
这说明 Android 侧基本已经成功调起协议,问题更可能出在订单有效期、参数编码、签名或支付链接本身。
不要把这个提示直接归因于 Android 无法识别 alipays://。
排查步骤
1. 每次支付都重新创建订单
不要缓存或重复使用之前生成的支付 URI、二维码内容、订单字符串或支付 token。
用户点击”立即支付”后,客户端应向业务服务端请求一笔新订单,并立即调起支付宝。服务端同时记录:
- 商户订单号;
- 创建时间;
- 支付渠道;
- 返回给客户端的原始支付参数;
- 支付宝接口返回结果;
- 订单当前状态。
如果使用同一条 URI 多次测试,第二次或间隔一段时间后出现”操作已超时”,应先确认 URI 中是否包含一次性或短时有效的支付凭证。
2. 比较 Android 和 iOS 最终使用的 URI
分别记录两端调用系统跳转接口前的完整字符串,然后逐字符比较:
- 参数名称和数量是否一致;
&、=、?、#是否被截断或重新解释;%是否变成%25;+是否被当成空格;- Base64 内容中的
+、/、=是否被修改; - 中文及非 ASCII 字符是否使用 UTF-8;
- 嵌套 URL 是否只编码了一次;
- Android 拼接 URI 时是否遗漏参数。
日志可能包含订单号、签名或 token。线上环境必须脱敏,不要把完整支付凭证上传到公共日志平台。
3. 检查嵌套参数的 URL 编码
如果协议参数中还包含 URL、二维码内容或订单参数,应编码参数值,不要把原始值直接拼入 URI。
例如:
import android.net.Uri
val nestedUrl = serverResponse.payUrl
val schemeUri = Uri.Builder()
.scheme("alipays")
.authority("platformapi")
.appendPath("startApp")
.appendQueryParameter("appId", serverResponse.appId)
.appendQueryParameter("url", nestedUrl)
.build()
Uri.Builder.appendQueryParameter() 会对参数值进行必要的编码。不要先完整编码 nestedUrl,再将它交给 appendQueryParameter(),否则可能造成重复编码。
如果服务端返回的已经是完整 alipays:// URI,客户端原则上应原样使用:
val paymentUri = Uri.parse(serverResponse.alipayScheme)
此时不要再对整个字符串调用 URLEncoder.encode()。整体编码会破坏协议头和查询参数结构。
4. 使用原生 Intent 调起
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
fun openAlipay(context: Context, scheme: String): Boolean {
return try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(scheme)).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
true
} catch (e: ActivityNotFoundException) {
false
} catch (e: SecurityException) {
false
}
}
如果业务要求只能由支付宝处理,可以在充分验证兼容性后指定支付宝包名:
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(scheme)).apply {
setPackage("com.eg.android.AlipayGphone")
}
指定包名可能降低对不同发行渠道、设备环境及未来客户端变化的兼容性。除非支付宝接入文档明确要求,否则更适合通过 URI 调起,并处理没有可用应用的情况。
5. 检查 Android 11 及以上的包可见性
如果代码会先通过 PackageManager 检测支付宝是否安装,Android 11 及以上系统可能需要在 AndroidManifest.xml 中声明查询范围:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<package android:name="com.eg.android.AlipayGphone" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="alipays" />
</intent>
</queries>
</manifest>
包可见性主要影响 resolveActivity()、queryIntentActivities() 等方法的查询结果。即使查询结果为空,也不能直接认定支付宝未安装,还要结合目标 Android 版本和实际 startActivity() 结果判断。
6. 检查 WebView 拦截逻辑
如果协议来自 H5 页面,Android WebView 需要单独处理非 HTTP(S) URI:
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
val uri = request.url
if (uri.scheme.equals("alipays", ignoreCase = true)) {
return try {
view.context.startActivity(
Intent(Intent.ACTION_VIEW, uri)
)
true
} catch (e: ActivityNotFoundException) {
true
}
}
return false
}
还要检查:
- 页面是否经过多次 302 重定向;
- WebView 是否把协议交给了错误的下载器或网络请求组件;
- 是否配置了 URL 白名单;
- 前端路由是否多次调用
decodeURIComponent()或encodeURIComponent(); - 用户手势与实际调起之间的间隔是否过长。
推荐的解决方式
对于标准 App 支付,业务服务端应调用支付宝相关接口,生成签名后的订单信息,再由 Android 客户端通过支付宝 App 支付 SDK 发起支付。客户端不能保存商户私钥,也不应自行生成服务端签名。
典型调用形式如下:
import com.alipay.sdk.app.PayTask
Thread {
val payTask = PayTask(activity)
// orderInfo 必须由可信业务服务端生成并签名
val result = payTask.payV2(orderInfo, true)
activity.runOnUiThread {
val resultStatus = result["resultStatus"]
val memo = result["memo"]
// 客户端结果仅用于界面提示。
// 最终支付状态应由服务端主动查询或异步通知确认。
}
}.start()
具体依赖版本、接口参数和 Android 配置,应以当前支付宝开放平台文档及已申请产品的接入说明为准,不要直接套用来源不明的协议参数。
注意事项
alipays://能打开支付宝,并不代表支付参数有效。- 不要重复使用一次性支付链接或历史订单参数。
- 不要对完整 Scheme URI 进行整体 URL 编码。
- 嵌套 URL 应按参数值编码,而且只能编码一次。
- 支付签名必须在服务端完成,商户私钥不能放入 Android APK。
- 不要仅凭客户端返回结果将订单标记为支付成功。
- 最终结果应以支付宝异步通知或服务端主动查询为准。
- 如果 Android 和 iOS 传入的 URI 字节完全一致,订单仍在有效期内,服务端签名也正确,但只有 Android 失败,应收集脱敏后的订单号、发生时间、支付宝客户端版本、Android 版本和 SDK 日志,再通过支付宝开放平台技术支持进一步核查。
备注:内容仅供参考。
