PAYATHON 2026

鸿蒙系统 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;
}

应用恢复到前台后,如果仍有待处理订单:

  1. 保持或恢复 sourceUrl;
  2. 调用业务服务端查询订单状态;
  3. 服务端确认支付成功后显示成功页;
  4. 如果状态仍是未支付,显示”支付未完成”,或允许用户重新支付。

不能只根据应用重新进入前台就认定支付成功或已经取消,因为用户可能只是暂时切换到其他应用。

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 拦截只负责唤起支付应用,不负责判断支付结果。

备注:内容仅供参考。