Revolut Pay SDK 在 WebView 支付重定向后因 return URI 崩溃
结论
崩溃发生在 Revolut Pay SDK 处理 WebView 回调时,不是在 MainActivity.onNewIntent() 中。
myapp://revolut-pay 是合法的 Android hierarchical URI,Manifest 配置也能与它匹配。异常表明,SDK 在 WebViewConfirmationActivity.shouldOverrideUrlLoading() 中对一个实际为 opaque URI 的地址调用了 getQueryParameterNames()。正常情况下,SDK 应先检查 uri.isHierarchical。结合堆栈判断,可能是 SDK 没有正确处理 MAIN 环境返回的地址,也可能是 MAIN 环境实际下发的重定向 URL 与配置不一致。
异常发生在 SDK 自己的 WebView Activity 内部,因此在应用层为 handleDeepLink() 添加 try/catch 或 URI 校验,也无法阻止这次崩溃。
为什么会崩溃
Android 的 Uri.getQueryParameterNames() 只能处理 hierarchical URI,例如:
myapp://revolut-pay?status=success
https://example.com/callback?status=success
没有 // 的 URI 属于 opaque URI,例如:
myapp:revolut-pay?status=success
如果对 opaque URI 调用:
uri.getQueryParameterNames()
系统会直接抛出:
UnsupportedOperationException: This isn't a hierarchical URI.
堆栈中的关键位置是:
WebViewConfirmationActivity$b.shouldOverrideUrlLoading
这说明 WebView 在拦截跳转 URL 时发生了崩溃。此时系统还没有把 deep link 交给 MainActivity,所以即使注释掉 handleDeepLink(intent),崩溃仍会出现。
首先确认生产环境实际返回的 URL
除了检查传给 init() 的 returnUri,还需要确认 MAIN 环境中的 WebView 最终导航到了哪个完整 URL。尤其要检查实际地址是否变成了:
myapp:revolut-pay
而不是:
myapp://revolut-pay
同时检查以下情况:
- URL 是否经过二次编码或错误解码;
- MAIN 商户后台配置的 return URI 是否与 SDK 中的值一致;
- 重定向经过中间页面后,URI 格式是否发生变化;
- WebView 跳转到回调地址前,是否先触发了其他自定义 scheme;
- 生产环境支付页面是否与当前 Android SDK 版本存在兼容问题。
可以使用 Chrome 的 WebView 远程调试、网络代理或 Revolut 提供的诊断日志,确认实际跳转地址。不要在日志或工单中公开支付令牌、订单信息和客户数据。
建议调整 return URI
原地址按照 Android 规则已经是 hierarchical URI。不过,可以尝试改用包含明确路径的形式,减少服务端拼接或解析时的歧义:
private const val RETURN_URI = "myapp://revolut-pay/callback"
初始化时必须使用完全相同的值:
RevolutPayments.revolutPay.init(
environment = RevolutPayEnvironment.MAIN,
returnUri = Uri.parse(RETURN_URI),
merchantPublicKey = "...",
requestShipping = false,
customer = Customer("", "", "", null, null)
)
Manifest 中也要配置对应的 path:
<activity
android:name=".ui.activity.MainActivity"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="revolut-pay"
android:path="/callback" />
</intent-filter>
</activity>
如果还需要在 Revolut 商户后台登记生产环境回调地址,那里也必须填写相同的值:
myapp://revolut-pay/callback
结尾斜杠、大小写和路径是否敏感,应以当前 Revolut Pay 的配置规则为准。不能认为 SANDBOX 中可用的地址会自动同步到 MAIN。
正确处理冷启动和已有实例
SDK 内部的崩溃解决后,应用仍需同时处理冷启动,以及 singleTop 模式下传入的新 Intent:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleDeepLink(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
handleDeepLink(intent)
}
private fun handleDeepLink(intent: Intent?) {
val uri = intent?.data ?: return
if (!uri.isHierarchical) {
Log.e("RevolutDeepLink", "Ignoring opaque URI: $uri")
return
}
if (uri.scheme != "myapp" || uri.host != "revolut-pay") {
return
}
runCatching {
RevolutPayments.revolutPay.handle(uri)
}.onFailure { error ->
Log.e("RevolutDeepLink", "Error handling Revolut Pay URI", error)
}
}
这段校验只能保护 deep link 进入应用后的处理逻辑,无法捕获 Revolut SDK 的 WebViewConfirmationActivity 在 shouldOverrideUrlLoading() 中抛出的异常。
推荐的解决顺序
- 升级到 Revolut Pay Android SDK 当前可用的稳定版本,并查看发布说明中是否提到 WebView、deep link 或 return URI 修复。
- 逐字符核对 MAIN 商户配置和代码中的
returnUri。 - 获取 WebView 最后访问的几个 URL,确认其中是否包含 opaque URI。
- 尝试使用带固定 path 的回调地址,例如
myapp://revolut-pay/callback。 - 如果问题仍然存在,将 SDK 版本、Android 版本、MAIN 环境、脱敏后的实际跳转 URL 和完整堆栈提交给 Revolut 支持团队。
SDK 内部较合理的处理方式类似:
val uri = Uri.parse(url)
if (!uri.isHierarchical) {
// 忽略、交给系统处理或返回明确错误
return false
}
val parameterNames = uri.queryParameterNames
但这段逻辑位于 Revolut SDK 内,应用无法通过自己的 onNewIntent() 修复。
综合这些现象,问题更可能来自 MAIN 环境的 WebView 重定向格式异常,或 SDK 缺少必要的 URI 防御性检查,而不是当前 Activity 的 deep-link handler。使用带 path 的 return URI 可以作为兼容性尝试,但如果实际 URL 仍被生成为 opaque URI,最终还需要 Revolut 修复服务端重定向或发布新版 SDK。
备注:内容仅供参考。
