HarmonyOS开发——uni-app 桥接鸿蒙原生工程调起微信小程序支付:从环境配置到真机跑通完成回调

随着鸿蒙设备越来越多,App 适配 HarmonyOS Next 已成为刚需。本文记录了一次完整的实战经历:在鸿蒙原生工程中集成微信 Open SDK,实现从 App 调起微信小程序完成支付的全链路——包括调起、支付、回调数据回传 JS 层,以及过程中踩过的每一个坑。


背景

我们的商城 App 基于 uni-app 开发,微信支付采用调起微信小程序的方式完成——App 拉起支付小程序,传入订单参数,用户在小程序内完成支付后返回 App 展示结果。

Android 和 iOS 端通过 plus.share.getServices() 获取微信服务再调用 launchMiniProgram() 即可,一切正常。但当我们要适配鸿蒙端时,发现这条路走不通了。


技术选型

鸿蒙端为什么不能用 plus.share?

uni-app 的 plus API 在鸿蒙端的支持是不完整的。plus.share 在鸿蒙运行时中为 undefined,直接调用会抛出 TypeError。更广泛地说,plus.runtime 整体在鸿蒙端都不可用——这意味着 Android/iOS 上的微信支付代码以及回调数据读取逻辑在鸿蒙端完全不可用。

微信 Open SDK for HarmonyOS Next

好消息是,微信官方已经发布了 HarmonyOS Next 版本的 Open SDK(@tencent/wechat_open_sdk),支持 LaunchMiniProgramReq API,可以原生拉起微信小程序。

方案明确了:通过鸿蒙原生代码调用微信 SDK,再由 JS 层桥接调用原生方法。同时,回调数据也需要通过原生桥接回传给 JS 层。


整体架构

调起链路(App → 微信小程序)
JS 层
  └─ uni.launchWxMiniProgram({ path, miniProgramType })
      └─ [Reflect.set 桥接]
          └─ 原生 launchWxMiniProgramExt(options)
              └─ WXApiWrap.launchWxMiniProgram(context, path, type)
                  ├─ isWXAppInstalled() 前置检测
                  ├─ new LaunchMiniProgramReq(userName, path, type)
                  └─ WXApi.sendReq(context, req)
                      └─ 微信 SDK → 拉起微信小程序
回调链路(微信小程序 → App JS 层)
微信跳回 App(通过 wxentity.action.open)
  └─ EntryAbility.onNewWant
      └─ WXApi.handleWant → WXEventHandler.onResp
          └─ LaunchMiniProgramResp → 缓存 extMsg 到模块变量
              └─ 页面 onShow 触发
                  └─ uni.getWxPayCallbackResult() ← Reflect.set 桥接
                      └─ 解析 app-parameter → 执行导航
                          └─ uni.clearWxPayCallbackResult() 清除残留

关键设计:鸿蒙端 plus.runtime.arguments 不可用,因此回调数据必须通过「原生缓存 → JS 读取 → JS 清除」三步完成桥接。


实施步骤

第一步:module.json5 跨应用通信配置

鸿蒙系统对跨应用通信有严格管控,必须声明微信的 URL Scheme 和回调 action:

module: {
    // 允许查询微信的 URL Scheme
    querySchemes: ["weixin", "wxopensdk"],
    abilities: [{
        name: "EntryAbility",
        skills: [{
            entities: ["entity.system.home"],
            // 关键:允许微信通过此 action 跳回我们的 App
            actions: ["action.system.home", "wxentity.action.open"],
        }],
    }],
}

querySchemes 让系统能发现微信的 Ability;wxentity.action.open 是微信 SDK 回调时跳回 App 的通道。

第二步:EntryAbility 接入微信回调

微信小程序支付完成后,微信会通过 Want 跳回我们的 Ability。需要在冷启动(onCreate)和热启动(onNewWant)两个入口都处理:

import { WXApi, WXEventHandler } from '../wechat/WXApiWrap';

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onCreate(want, launchParam);
    this.handleWeChatCallIfNeed(want);
}

// 热启动场景
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    super.onNewWant(want, launchParam);
    this.handleWeChatCallIfNeed(want);
}

private handleWeChatCallIfNeed(want: Want): void {
    try {
        WXApi.handleWant(want, WXEventHandler);
    } catch (err) {
        // want 不含微信数据时静默跳过
    }
}
第三步:原生层实现拉起小程序 + 回调缓存

在 WXApiWrap.ets 中封装核心逻辑。重点:不仅要拉起小程序,还要缓存回调结果供 JS 层后续读取:

import * as wxopensdk from "@tencent/wechat_open_sdk";

/** 回调结果缓存(鸿蒙端 plus.runtime.arguments 不可用) */
let _wxPayCallbackResult: string | null = null;

export const WXApi = wxopensdk.WXAPIFactory.createWXAPI(APP_ID);

export async function launchWxMiniProgram(
    context: common.UIAbilityContext,
    path: string,
    miniProgramType: number = 0,
): Promise<boolean> {
    _wxPayCallbackResult = null; // 调起前清除上次残留

    if (!WXApi.isWXAppInstalled()) return false;

    const req = new wxopensdk.LaunchMiniProgramReq();
    req.userName = "gh_xxxxxxxxxxxx"; // 小程序原始 ID
    req.path = path;
    req.miniprogramType = miniProgramType;
    return await WXApi.sendReq(context, req);
}

// 在 onResp 中缓存回调结果
if (resp instanceof wxopensdk.LaunchMiniProgramResp) {
    const extMsg = resp.extMsg;
    _wxPayCallbackResult = extMsg ?? ""; // 缓存,等 JS 层 onShow 时读取
}

// 暴露 getter/clearer 供 JS 层通过 uni 对象调用
export function getWxPayCallbackResult(): string | null {
    return _wxPayCallbackResult;
}
export function clearWxPayCallbackResult(): void {
    _wxPayCallbackResult = null;
}

为什么需要缓存? Android/iOS 端,小程序返回后 JS 层通过 plus.runtime.arguments 直接读取回调数据。但鸿蒙端 plus.runtime 整体不可用,如果不缓存,回调数据就永远无法传递到 JS 层——支付成功了但 App 不知道。

第四步:JS ↔ 原生桥接(最大的坑)

这是整个适配过程中最曲折的部分。我们需要让 JS 层能调用原生的函数。

尝试一:条件编译(失败)

最初以为只要用 #ifdef APP-HARMONY 把鸿蒙端代码隔离就行。结果发现 #ifdef APP-PLUS 在鸿蒙构建时也为 true,导致鸿蒙端编译进了 plus.share.getServices() 的代码,运行时直接崩溃。

尝试二:运行时检测 + 直接赋值 uni 对象(失败)

改为运行时检测 typeof plus !== 'undefined' && plus.share,鸿蒙端走另一条路:

(uni as any).launchWxMiniProgram = myFunction; // ❌ ArkTS 编译报错

ArkTS 严格模式禁止 any 类型,编译直接报错。

尝试三:Reflect.set(成功)

最终发现可以用 Reflect.set 绕过 ArkTS 的类型限制:

export function registerLaunchWxMiniProgram(uniObj: object): void {
    Reflect.set(uniObj, "launchWxMiniProgram", launchWxMiniProgramExt);
}

然后在 index.generated.ets(uni-app 自动生成的模块注册文件)中调用注册——包括调起函数和回调读取函数:

import {
    registerLaunchWxMiniProgram,
    getWxPayCallbackResult,
    clearWxPayCallbackResult,
} from "../uni-launch-wxmini/Index";

function initUniExtApi() {
    // ... 其他 ext API ...
    registerLaunchWxMiniProgram(uni);
    // 回调桥接:鸿蒙端 plus.runtime.arguments 不可用
    Reflect.set(uni, "getWxPayCallbackResult", getWxPayCallbackResult);
    Reflect.set(uni, "clearWxPayCallbackResult", clearWxPayCallbackResult);
}

这样 JS 层就可以通过 uni.launchWxMiniProgram({ path, miniProgramType }) 调起小程序,并通过 uni.getWxPayCallbackResult() 读取支付回调结果了。

坑中之坑:ArkTS 的 import 语句顺序

在桥接模块 Index.ets 中 re-export 函数时,如果把 export 语句插在 import 之间:

import {
    launchWxMiniProgram,
    getWxPayCallbackResult,
} from "../wechat/WXApiWrap";
export { getWxPayCallbackResult }; // ❌ 这行导致后续 import 全部报错
import { common } from "@kit.AbilityKit";

ArkTS 会报 10605150 arkts-no-misplaced-imports,但报错指向的是被挤到后面的 import 行号,完全不提示真正的元凶是那行 export。正确做法:所有 import 放最前,export 放其后。

第五步:JS 层适配

JS 层的适配分两部分:调起小程序和读取回调结果。

调起小程序:运行时检测替代条件编译
function launchWxMiniProgram(query: string, launchedRef: Ref<boolean>) {
    if (typeof plus !== "undefined" && plus.share) {
        // Android/iOS:走 plus.share 通道
        plus.share.getServices(
            (services) => {
                const sweixin = services.find((s) => String(s.id) === "weixin");
                sweixin.launchMiniProgram({
                    id: WX_MINI_PROGRAM_ID,
                    path: `?${query}`,
                    type: WX_MINI_PROGRAM_TYPE,
                });
            },
            (err) => {
                /* ... */
            },
        );
    } else {
        // 鸿蒙端:走 uni ext API 桥接
        launchWxMiniProgramHarmony(query, launchedRef);
    }
}

async function launchWxMiniProgramHarmony(
    query: string,
    launchedRef: Ref<boolean>,
) {
    const uniAny = uni as any;
    if (typeof uniAny.launchWxMiniProgram !== "function") {
        uni.showToast({ title: "鸿蒙端微信桥接未就绪", icon: "none" });
        return;
    }
    const success = await uniAny.launchWxMiniProgram({
        path: `?${query}`,
        miniProgramType: WX_MINI_PROGRAM_TYPE,
    });
    if (!success) {
        uni.showToast({ title: "请先安装微信", icon: "none" });
    }
}
读取回调结果:鸿蒙端专属分支

支付完成后微信跳回 App,页面 onShow 触发。Android/iOS 通过 plus.runtime.arguments 读取回调参数,鸿蒙端则通过桥接函数读取原生缓存:

function handleMiniProgramReturn(onNavigate, onComplete) {
    const payOrderId = uni.getStorageSync(WX_PAY_ORDER_ID_KEY) || "";
    setTimeout(() => {
        // #ifdef APP-HARMONY
        // 鸿蒙端:plus.runtime.arguments 不可用,通过桥接函数读取
        const uniAny = uni as any;
        let args = null;
        if (typeof uniAny.getWxPayCallbackResult === "function") {
            args = uniAny.getWxPayCallbackResult();
            uniAny.clearWxPayCallbackResult(); // 读后即清
        }
        // #endif
        // #ifndef APP-HARMONY
        const args = plus.runtime.arguments;
        try {
            plus.runtime.arguments = "";
        } catch {}
        // #endif

        const paramValue = parseAppParameter(args);
        onNavigate(paramValue || "order", payOrderId);
        onComplete?.();
    }, 300);
}

时序保证:原生 onResp 在 EntryAbility.onNewWant 中同步执行,早于 JS 层的 onShow 触发,因此 onShow 读取时回调结果已就绪。


微信开放平台配置

代码改完后,还需要在 微信开放平台 注册鸿蒙端的应用标识:

  1. 管理中心 → 移动应用 → 找到你的 App
  2. 添加 HarmonyOS 平台
  3. 填写 Bundle Name(如 com.yourcompany.yourapp.hm)
  4. 保存提交

不配置这一步,调起微信时会报:"第三方应用信息校验失败,identifier错误"。


踩坑清单

问题 根因 解决方案
鸿蒙端提示"仅支持APP端" #ifdef APP-PLUS 在鸿蒙构建时也为 true 改用运行时检测 typeof plus !== 'undefined'
plus.share.getServices() 崩溃 plus.share 在鸿蒙端为 undefined 运行时检测后再调用
(uni as any).xxx 编译报错 ArkTS 严格模式禁止 any 使用 Reflect.set(uni, key, fn)
uni 对象赋值后 JS 调不到 uni 是 Proxy,普通赋值不桥接 Reflect.set 可以穿透 Proxy
The specified ability does not exist 设备未安装鸿蒙版微信 安装微信 + isWXAppInstalled() 前置检测
identifier错误 微信开放平台未登记鸿蒙 Bundle Name 在平台添加 HarmonyOS 并填写 Bundle Name
index.generated.ets 改动丢失 该文件由 uni-app 构建系统自动生成 每次构建后检查并重新添加注册代码
支付成功返回 App 后回调逻辑未执行 鸿蒙端 plus.runtime.arguments 不可用,原生回调数据未桥接到 JS 原生缓存 extMsg + Reflect.set 注册 getter,JS 通过 uni.getWxPayCallbackResult() 读取
ArkTS 报 misplaced-imports 编译失败 export {} 插在 import 之间,报错指向后续 import 而非元凶 所有 import 放最前,export 放其后

完整调用链路

用户点击支付
  → JS: useOrderPay.payByWechat(orderId)
    → JS: 运行时检测 plus.share 不可用
      → JS: uni.launchWxMiniProgram({ path, miniProgramType })
        → [Reflect.set 桥接]
          → 原生: launchWxMiniProgramExt(options)
            → 原生: WXApiWrap.launchWxMiniProgram(context, path, type)
              → 原生: isWXAppInstalled() 前置检测
              → 原生: 清除上次回调残留
              → 原生: new LaunchMiniProgramReq(...)
              → 原生: WXApi.sendReq(context, req)
                → 微信 SDK → 拉起微信小程序
                  → 用户完成支付
                    → 微信跳回 App (wxentity.action.open)
                      → EntryAbility.onNewWant
                        → WXApi.handleWant → WXEventHandler.onResp
                          → LaunchMiniProgramResp → 缓存 extMsg 到模块变量
                            → 页面 onShow 触发
                              → uni.getWxPayCallbackResult() 读取回调结果
                                → 解析 app-parameter → 导航到订单详情
                                  → uni.clearWxPayCallbackResult() 清除残留

总结

鸿蒙端调起微信小程序支付,核心挑战不在微信 SDK 本身,而在于 JS 层如何桥接到原生层以及回调数据如何从原生层回传到 JS 层。uni-app 鸿蒙端的 uni 对象是一个有白名单的 Proxy,自定义方法无法通过普通赋值注册;plus.runtime 整体不可用,回调数据必须通过额外的桥接机制传递。

关键经验
  1. 不要信任条件编译:APP-PLUS 在鸿蒙构建中也为 true,平台差异必须用运行时检测
  2. ArkTS 严格模式是硬约束:any、unknown、对象字面量类型全部禁止,需要找替代方案
  3. uni 对象不是普通对象:它是 Proxy,自定义属性注册需要用 Reflect.set
  4. 微信开放平台配置不能忘:Bundle Name 必须在平台登记,否则校验失败
  5. 自动生成的文件要小心:index.generated.ets 会被构建系统覆盖,改动需持久化
  6. 回调链路是独立链路:「调起哪个版本」和「返回后数据如何回传」是完全独立的两条链路,排查时不要混为一谈
  7. plus.* API 在鸿蒙端整体不可用:不仅是 plus.share,plus.runtime 也不行,所有依赖它读取回调数据的方案都需要替代
  8. ArkTS 报错信息会误导:misplaced-imports 报错指向的是被挤的 import 行号,而非真正的元凶(插在中间的 export)。遇到此类错误先检查 import 块中是否混入了非 import 语句

希望这篇记录能帮到正在做鸿蒙适配的开发者。如果微信后续在鸿蒙端提供了更完善的 JS Bridge 方案,这些 workaround 就可以退役了。


本文基于 @tencent/wechat_open_sdk v1.0.20 + HarmonyOS Next + uni-app 3.x alpha 环境,实际效果可能因 SDK 版本而异。

posted on 2026-09-21 18:34  码间留白  阅读(12)  评论(0)    收藏  举报

导航