鸿蒙系统 WebView 拦截 scheme 后如何处理支付返回
结论
不要根据是否拦截过 scheme 或 WebView 的历史记录来判断支付是否返回。支付跳转 URL 和支付返回 URL 应使用两个能够明确区分的地址,并采用不同的处理方式:
- 支付跳转 URL:由 WebView 拦截,再交给支付应用或系统能力打开。
- 支付返回 URL:由应用接收回调,然后主动让 WebView 加载原页面或支付结果页。
- 用户取消且支付应用没有回调:应用恢复到前台后,查询支付结果,或返回支付前保存的页面。
如果支付平台既不支持配置返回 URL,也不会在取消时回调,仅靠 WebView 无法可靠区分支付成功返回和取消返回。此时必须查询订单状态。
为什么拦截后不能直接返回
WebView 中的普通 HTTP 页面跳转会写入导航历史,因此可以使用 backward() 返回上一页。
支付 scheme 的处理方式不同。例如:
payment-app://pay?orderId=123
WebView 拦截这个地址后,原生代码会接管后续导航。WebView 没有加载支付页面,也就不会产生相应的历史记录。支付应用中的返回或取消发生在另一个应用里,无法依靠 WebView 的返回栈恢复原页面。
支付平台提供的返回方式也不统一,可能是:
- 自定义 scheme;
- App Link;
- 支付 SDK 回调;
- 应用重新进入前台,但没有携带回调地址。
应用需要针对这些情况分别处理。
推荐的 URL 设计
支付跳转地址和返回地址应使用不同的 scheme、host 或 path。例如:
支付跳转:
payment-app://pay?orderId=123
支付返回:
myapp://payment/result?orderId=123&status=cancel
即使两者必须使用相同的 scheme,也应通过 host 或 path 区分:
myapp://payment/start
myapp://payment/result
不要采用下面这种模糊匹配:
url.includes('pay')
正确的做法是解析 URL,分别匹配协议、主机和路径,同时校验订单号等参数。
WebView 中只拦截支付跳转
下面的代码用于说明整体处理结构。具体回调参数以及启动外部应用的配置方式,应以项目使用的 HarmonyOS SDK 版本为准。
import { common, Want } from '@kit.AbilityKit';
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct PayPage {
private controller: webview.WebviewController =
new webview.WebviewController();
private context = getContext(this) as common.UIAbilityContext;
private isPaymentUrl(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === 'payment-app:' &&
parsed.hostname === 'pay';
} catch (_) {
return false;
}
}
private async openPayment(url: string): Promise<void> {
const want: Want = {
uri: url
};
try {
await this.context.startAbility(want);
} catch (error) {
console.error(`Open payment failed: ${JSON.stringify(error)}`);
// 此处应提示用户未安装支付应用或无法打开支付页面
}
}
build() {
Web({
src: 'https://example.com/order/123',
controller: this.controller
})
.onLoadIntercept((event) => {
const url = event.request.getRequestUrl();
if (this.isPaymentUrl(url)) {
this.openPayment(url);
return true; // 已由原生侧处理,阻止 WebView 加载
}
return false; // 普通页面继续由 WebView 加载
});
}
}
拦截回调只应处理支付应用需要的外部 scheme。业务站点的 https 页面、支付结果页和其他正常导航都应返回 false,否则可能破坏 WebView 原有的返回流程。
接收支付返回地址
应用需要在 module.json5 中为相应的 Ability 配置可接收的 URI。具体字段应按照当前 HarmonyOS SDK 和工程模板填写,目标是让下面的地址能够重新唤起应用:
myapp://payment/result
Ability 需要同时处理首次启动和已有实例被重新唤起的情况,因为支付返回不一定会创建新的 Ability。
import { UIAbility, Want } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want): void {
this.handlePaymentCallback(want);
}
onNewWant(want: Want): void {
this.handlePaymentCallback(want);
}
private handlePaymentCallback(want: Want): void {
const callbackUrl = want.uri;
if (!callbackUrl) {
return;
}
try {
const parsed = new URL(callbackUrl);
if (
parsed.protocol !== 'myapp:' ||
parsed.hostname !== 'payment' ||
parsed.pathname !== '/result'
) {
return;
}
const orderId = parsed.searchParams.get('orderId');
// 不要直接信任 URL 中的 status。
// 通知页面查询服务端订单状态,再决定显示成功、失败或取消。
this.notifyPaymentReturned(orderId);
} catch (error) {
console.error(`Invalid payment callback: ${JSON.stringify(error)}`);
}
}
private notifyPaymentReturned(orderId: string | null): void {
// 可通过应用状态管理或事件机制通知承载 WebView 的页面
}
}
页面收到通知后,可以加载支付结果页:
const resultUrl =
`https://example.com/payment/result?orderId=${encodeURIComponent(orderId)}`;
this.controller.loadUrl(resultUrl);
另一种做法是保留原订单页面,通过 JavaScript 或重新加载页面来触发订单状态查询。
处理没有回调的取消操作
部分支付应用在用户直接关闭、手势返回或取消时,不保证回调自定义 URL。遇到这种情况,可以在跳转前保存支付上下文:
interface PendingPayment {
orderId: string;
sourceUrl: string;
startedAt: number;
}
应用恢复到前台后,如果仍有待处理订单:
- 保持或恢复
sourceUrl; - 调用业务服务端查询订单状态;
- 服务端确认支付成功后显示成功页;
- 如果状态仍是未支付,显示”支付未完成”,或允许用户重新支付。
不能只根据应用重新进入前台就认定支付成功或已经取消,因为用户可能只是暂时切换到其他应用。
WebView 自身的返回处理
用户已经回到承载 WebView 的页面时,系统返回键可以优先处理 WebView 的历史记录:
onBackPress(): boolean {
if (this.controller.accessBackward()) {
this.controller.backward();
return true;
}
return false;
}
这段逻辑只能返回 WebView 内部已经访问过的页面,不能替代支付应用的回调机制。
注意事项
- 支付结果应以服务端查询或支付平台的服务端通知为准,不能直接信任回调 URL 中的
status=success。 - 返回 URL 应携带可校验的订单标识或随机
state,防止其他应用伪造回调。 - 应为允许打开的 scheme、host 和 path 设置白名单,避免任意 URL 唤起外部应用。
- Ability 首次启动和已有实例接收新 Want 的场景都要处理。
- 支付前应保存 WebView 当前的业务地址,不要把支付页面视为 WebView 历史记录的一部分。
- 如果支付平台提供专用的 HarmonyOS SDK,应优先使用 SDK 的结果回调。scheme 拦截只负责唤起支付应用,不负责判断支付结果。
备注:内容仅供参考。
