在鸿蒙上跑 uni-app x 支付,真正卡住人的往往不是下单逻辑,而是几个平台专属参数:少一个 dataType、漏配一个模块,代码在 Android/iOS 上跑得好好的,到鸿蒙上就直接报错。本文围绕 uni.requestPayment 在鸿蒙平台的接入流程,把兼容性边界、manifest 配置、orderInfo 格式、错误码处理和常见坑位一次讲清。
一、先确认 requestPayment 在鸿蒙上的适用边界
uni.requestPayment 是 uni-app x 提供的统一支付 API,一套代码同时对接微信支付与支付宝支付,不需要分别集成两家的原生 SDK,框架层抹平了差异。但它的平台支持有明确范围:Web 端不支持,鸿蒙平台从 4.61 版本开始支持。也就是说,低于 4.61 的鸿蒙环境根本调不起来,遇到“支付无反应”时先核对基础版本,再去怀疑业务代码——这个版本问题很容易让人白排查半天。
API 签名为 uni.requestPayment(options: RequestPaymentOptions)。成功回调返回 RequestPaymentSuccess,失败回调返回 RequestPaymentFail,其中包含 errCode、errMsg 等字段,后续的错误分支判断都基于它。
二、把支付链路摆正:前端只负责“调起”
支付不是调用一次 API 就结束的事,完整链路分为七步:
1. 前端向自建服务端发起下单请求;
2. 服务端调用微信/支付宝的统一下单接口,生成预支付订单;
3. 服务端把 orderInfo 返回给前端;
4. 前端调用 uni.requestPayment,传入 orderInfo,调起微信/支付宝完成支付;
5. 微信/支付宝异步通知服务端支付结果;
6. 服务端收到通知后更新订单状态;
7. 前端通过轮询或 WebSocket 获取最终支付结果。
关键认知是:success 回调只代表客户端这一侧调起并完成了支付动作,订单是否真正到账要以服务端收到的异步通知为准。把发货、开通权益的逻辑放在前端回调里,是典型的架构错误。
三、鸿蒙专属配置三件事
第一件是 manifest 模块配置。鸿蒙平台需要在 manifest.json 的 modules 中声明 uni-payment 模块,不配置调用支付会直接报错。HBuilderX 4.71 及以上版本支持可视化界面配置,配置路径为 manifest.json → modules → uni-payment;也可以手动写入:
- {
- "modules": {
- "uni-payment": {}
- }
- }
复制代码
第二件是 dataType 必须传 'text'。这是鸿蒙平台与其他平台差异最大的一处,同样是 requestPayment 调用,Android 和 iOS 不传 dataType 可以正常跑,鸿蒙平台会报错。所以这份代码从其他端迁过来时,最容易在这里翻车。
- uni.requestPayment({
- provider: 'wxpay',
- orderInfo: orderInfo,
- dataType: 'text', // 鸿蒙平台必须传
- })
复制代码
第三件是应用标识取 bundleName。鸿蒙平台获取应用标识用的是 bundleName,既不是 packageName 也不是 bundleId,用条件编译区分平台可以避免取值错误:
- const getAppId = (): string => {
- let appId = ''
- // #ifdef APP-ANDROID
- appId = uni.getAppBaseInfo().packageName ?? ''
- // #endif
- // #ifdef APP-IOS
- appId = uni.getAppBaseInfo().bundleId ?? ''
- // #endif
- // #ifdef APP-HARMONY
- appId = uni.getAppBaseInfo().bundleName ?? ''
- // #endif
- return appId
- }
复制代码
四、orderInfo 的格式随渠道变化
同一个 requestPayment,不同渠道对 orderInfo 的形态要求不一样。微信支付要求 JSON 字符串,前端拿到服务端返回的对象后要 JSON.stringify;支付宝要求以 & 连接的 key=value 字符串。
微信支付的 orderInfo 形如:
- {
- "appid": "wxd569c7238830733b",
- "noncestr": "6N47VnR42bqIm4xq",
- "package": "Sign=WXPay",
- "partnerid": "1230636401",
- "prepayid": "wx26174750316675ac54b89c224eb3250000",
- "timestamp": 1711446470,
- "sign": "EE987459B9CFF6462462147130110D31"
- }
复制代码
支付宝的 orderInfo 形如:
- app_id=2015052600090779&biz_content=...&method=alipay.trade.app.pay&sign_type=RSA2&sign=...
复制代码
对应的调起写法,微信支付如下(注意金额在服务端下单时以“分”为单位):
- const requestWxpay = () => {
- uni.request({
- url: 'https://your-server.com/api/create-order',
- method: 'POST',
- data: {
- productId: 'product_001',
- amount: 99, // 金额,单位分
- },
- success: (res) => {
- const orderInfo = JSON.stringify(res.data)
- uni.requestPayment({
- provider: 'wxpay',
- orderInfo: orderInfo,
- dataType: 'text', // 鸿蒙平台需要传 'text'
- success: (res: RequestPaymentSuccess) => {
- console.log('支付成功:', res.data)
- },
- fail: (err: RequestPaymentFail) => {
- console.log('支付失败:', err.errCode, err.errMsg)
- }
- })
- }
- })
- }
复制代码
支付宝则直接使用服务端返回的字符串,金额在下单时按“元”为单位:
- const requestAlipay = () => {
- uni.request({
- url: 'https://your-server.com/api/create-alipay-order',
- method: 'POST',
- data: {
- productId: 'product_001',
- amount: 0.01, // 金额,单位元
- },
- success: (res) => {
- const orderInfo = res.data as string
- uni.requestPayment({
- provider: 'alipay',
- orderInfo: orderInfo,
- dataType: 'text',
- success: (res: RequestPaymentSuccess) => {
- console.log('支付成功:', res.data)
- },
- fail: (err: RequestPaymentFail) => {
- console.log('支付失败:', err.errCode, err.errMsg)
- }
- })
- }
- })
- }
复制代码
如果同一份代码还要在微信小程序里跑,orderInfo 的传法不同,需要用条件编译区分:小程序端用展开语法把订单字段平铺进参数,App 端仍然传 JSON 字符串并带上 dataType。
- uni.requestPayment({
- provider: 'wxpay',
- // #ifdef MP-WEIXIN
- ...orderData,
- // #endif
- // #ifdef APP
- orderInfo: JSON.stringify(orderData),
- dataType: 'text',
- // #endif
- success: (res: RequestPaymentSuccess) => {},
- fail: (err: RequestPaymentFail) => {}
- })
复制代码
五、错误码分支要提前写好
支付失败时把 errMsg 直接抛给用户没有意义,需要按 errCode 分类处理。实际开发中最常遇到的是这几个:700601 表示用户取消支付,应静默或轻提示,不要当成异常;700604 表示设备上未安装微信,提示语要引导安装;700602 表示网络连接出错;701100 表示订单支付失败。
- fail: (err: RequestPaymentFail) => {
- if (err.errCode == 700601) {
- paymentError.value = '用户取消支付'
- } else if (err.errCode == 700604) {
- paymentError.value = '请先安装微信'
- } else if (err.errCode == 700602) {
- paymentError.value = '网络连接出错'
- } else if (err.errCode == 701100) {
- paymentError.value = '订单支付失败'
- } else {
- paymentError.value = err.errMsg
- }
- }
复制代码
六、调起之前先做两项可用性检查
第一项是检查设备上有哪些支付通道。uni.getProvider 传 service: 'payment' 可以拿到 providers 列表,逐项打印 id 和 name,据此决定支付方式列表是否要置灰。
- uni.getProvider({
- service: 'payment',
- success: (res) => {
- res.providers.forEach((provider) => {
- console.log('支付通道:', provider.id, provider.name)
- })
- },
- fail: (err) => {
- console.log('获取支付通道失败:', err.errMsg)
- }
- })
复制代码
第二项是检查微信客户端是否安装。微信支付依赖微信客户端,鸿蒙设备上没装微信,调用会直接返回 700604。可以在发起前判断 provider 是否为 UniPaymentWxpayProvider 实例,并读取 isWeChatInstalled:
- const provider = res.providers.find((item): boolean => {
- return item.id == 'wxpay'
- })
- if (provider != null && provider instanceof UniPaymentWxpayProvider) {
- if (!provider.isWeChatInstalled) {
- uni.showToast({ title: '请先安装微信', icon: 'none' })
- return
- }
- }
复制代码
七、一个可复用的支付页结构
把上面的点落成一个页面,核心是三个状态:选中的支付渠道、支付中标记 isPaying、支付结果与错误信息。点击支付时先 showLoading 拉取订单信息,按渠道决定 orderInfo 的序列化方式,再调 requestPayment,在 complete 中复位 isPaying,避免用户重复点击导致的重复下单。
- const startPayment = () => {
- if (isPaying.value) {
- return
- }
- isPaying.value = true
- paymentResult.value = ''
- paymentError.value = ''
- uni.showLoading({ title: '获取订单信息...' })
- uni.request({
- url: 'https://your-server.com/api/create-order',
- method: 'POST',
- data: {
- productId: 'product_001',
- amount: 99,
- payMethod: selectedPayMethod.value,
- },
- success: (res) => {
- uni.hideLoading()
- let orderInfo = ''
- if (selectedPayMethod.value == 'wxpay') {
- orderInfo = JSON.stringify(res.data)
- } else {
- orderInfo = res.data as string
- }
- uni.requestPayment({
- provider: selectedPayMethod.value,
- orderInfo: orderInfo,
- dataType: 'text', // 鸿蒙平台必须传
- success: (res: RequestPaymentSuccess) => {
- paymentResult.value = 'success'
- },
- fail: (err: RequestPaymentFail) => {
- paymentResult.value = 'fail'
- paymentError.value = err.errMsg
- },
- complete: () => {
- isPaying.value = false
- }
- })
- },
- fail: () => {
- uni.hideLoading()
- isPaying.value = false
- uni.showToast({ title: '获取订单信息失败', icon: 'none' })
- }
- })
- }
复制代码
模板层只需渲染商品信息、支付方式单选列表和一个在 isPaying 时禁用的支付按钮,支付结果区域根据 paymentResult 决定文案与颜色。
八、鸿蒙接入自检清单
把踩过的坑收敛成一份清单,适配时逐条对照即可:
1. 鸿蒙基础版本是否达到 4.61,低于该版本 requestPayment 不可用;
2. manifest.json 的 modules 是否声明了 uni-payment,HBuilderX 4.71+ 可直接在界面勾选;
3. 每次调用是否都带上 dataType: 'text';
4. 微信支付是否用了 JSON 字符串形式的 orderInfo,支付宝是否用了 & 连接的 key=value 字符串;
5. 取应用标识是否使用 bundleName;
6. 是否在调用前判断了微信客户端安装情况,并对 700601 做了“取消支付”的独立分支;
7. 订单最终状态是否以服务端异步通知为准,而非客户端回调。
九、架构取舍:统一 API 还是原生 IAP Kit
如果服务端使用 uniCloud,可以配合云端统一支付服务 uniPay,把 App、微信小程序、支付宝小程序的服务端支付逻辑统一封装,前端 requestPayment 对接云端 uniPay,链路更短。另一种路线是鸿蒙原生开发,直接接入华为支付 IAP Kit,需要自行处理 iap.createPurchaseIntent 等 IAP Kit 的回调与参数配置,复杂度明显高于统一 API。选择依据很直接:应用只在鸿蒙单端发布、且需要华为支付能力时,原生方案更合适;否则用 uni.requestPayment 一套代码覆盖微信与支付宝,维护成本更低。 |