uni-app实战:登录支付分享全流程详解与避坑指南
2026/9/15 5:15:31 网站建设 项目流程

1. 内容整体设计与思路拆解

做 uni-app 开发的人,迟早都会撞上“登录、支付、分享”这老三样。很多新手朋友接到这类需求时,第一反应是“这不简单吗,官方都有插件,直接调 API 不就行了”,但真到了联调阶段,各种隐藏问题就全冒出来了——iOS 上拉起不了微信、支付回调收不到、分享出去卡片没标题、安卓和 iOS 表现还不一样,每个都能让你折腾到深夜。

我最初接触 uni-app 的登录支付分享,是在给一个跨端电商 App 做基础能力的时候。当时项目要求覆盖微信小程序、支付宝小程序、App 三端,如果三个端分别用原生 SDK 去开发,等于要维护三套代码,成本直接翻三倍。后来从一个老同事那里了解到 uni-app 的统一封装思路,才算把这套逻辑给理顺。这篇博文我就把从零到一的实现过程、踩过的坑、调通的细节全部整理出来,尽量让还没入门的同学能少走弯路,让已经接了一半的同学能对照排查。

先聊点底层的认知。登录、支付、分享这三个功能,看上去是三个独立模块,但从工程架构的角度看,它们其实是同一类东西:一次与外部平台安全交换信息和权限的过程

不管是微信登录还是支付宝支付,本质上都是:

  • 你的应用将用户引导到第三方平台(微信、支付宝等);
  • 第三方平台确认用户身份后,返回一个授权凭证(code、token 或签名信息);
  • 你的应用拿这个凭证,去对应平台换取用户身份或完成支付确认。

uni-app 之所以能成为主流跨端框架,一个重要原因就是它对这三类能力做了统一的 JS API 封装,把微信小程序、支付宝小程序、App 三端的差异尽量抹平。开发者只需要调用同一套uni.loginuni.requestPaymentuni.share,底层会根据当前运行的平台,自动适配对应的原生能力。但这里必须说明:抹平的是“调用入口”,不是“业务逻辑”,更不是“平台审核规则”。这也是很多人忽略的点——以为一个 API 通吃,结果在微信小程序里,支付只能用微信支付,苹果的虚拟支付又完全不能用微信支付,这些平台规则根本不是框架能解决的。

所以在动手之前,我建议你把这三个模块先当成三个独立的“分包任务”来看待,每个任务的第一件事不是写代码,而是去对应平台的管理后台查清楚规则。微信开放平台管 App 的登录与分享,微信公众平台管小程序的登录与支付,支付宝开放平台管支付宝端的支付与授权。哪个平台账号没开通对应权限,代码写得再漂亮也是白搭。

2. 前置准备:不是“准备好 AppID”就完事

我很反感网上很多教程一上来就说“先去申请 AppID”,好像申请完就万事大吉一样。实际上,账号权限、密钥管理、回调域名这些配置,才是社会化功能落地时最耗时间、最容易踩坑的部分。

2.1 三平台账号与权限清单

登录、支付、分享三项功能,涉及的平台账号并不完全重合。拉一张清单:

目标端登录支付分享
微信小程序微信公众平台微信支付商户平台不支持(小程序内没有分享到朋友圈能力,只能分享给好友)
App(iOS/Android)微信开放平台微信支付商户平台 / 支付宝开放平台微信开放平台
支付宝小程序不支持(基于支付宝授权登录)支付宝开放平台支付宝开放平台
H5微信JSAPI支付 / 支付宝电脑网站支付需要依赖第三方分享SDK(原生 App 才支持系统分享)

这张表里最容易搞混的是微信的两个后台。微信开放平台(open.weixin.qq.com)负责 App 的登录、分享、支付应用 ID,需要创建“移动应用”;微信公众平台(mp.weixin.qq.com)负责小程序的 AppID 和登录能力,需要创建“小程序”。很多新手把这两个后台搞混,结果在小程序项目里填了移动应用的 AppID,导致uni.login一直报错。

提示:App 端若要用微信登录或分享,必须在微信开放平台完成“移动应用”创建,并通过开发者资质认证。认证一般需要 1-3 个工作日,千万别在提测前一天才开始申请。

支付宝这边相对简单,统一走支付宝开放平台,创建应用后开通“支付宝登录”和“电脑网站支付/手机网站支付/App 支付”权限即可。但要注意,支付宝的密钥有 RSA2 加密和公钥模式之分,后续在服务端签名时要用到,建议用支付宝官方提供的密钥生成工具生成一对 RSA2 密钥,并把公钥粘贴到平台,自己保留私钥。

2.2 服务端域名、HTTPS 和回调地址

登录、支付都涉及服务端交互,尤其是支付结果的通知,第三方平台会向你的服务器发起 HTTP 请求。微信支付要求回调地址必须是公网可访问的 HTTPS 地址(测试环境可以用 HTTP,但正式环境强制 HTTPS),支付宝同样要求 HTTPS 并且对域名有备案要求。

回调地址配置时的几个常见坑:

  • 回调地址中不能包含查询参数,比如https://yourdomain.com/pay/callback?type=wechat这种写法,微信会直接忽略?后面的内容。建议通过路径区分支付渠道,比如/pay/wechat/callback/pay/alipay/callback
  • 回调地址对应的接口必须是 POST 方法,并且返回内容要按规定格式写,微信要求返回字符串SUCCESS,支付宝要求返回字符串success,大小写和格式都不能错,否则平台会认为回调失败并持续重试。
  • 回调接口要做好验签,第三方平台发来的请求虽然是 HTTPS,但仍有伪造风险,必须用平台公钥(或 API 密钥)验证签名后再处理业务逻辑。

另外,uni-app 的 App 端在调试微信登录、分享时,需要在微信开放平台配置 App 的 bundle ID(iOS)和包名、签名(Android)。iOS 的 bundle ID 必须和 Xcode 工程中的完全一致,Android 的“应用签名”则是用官方签名工具生成的 MD5 值(去掉冒号的 32 位大写字符串)。这一部分配置错了,微信 SDK 拉起时会直接返回“未验证应用”的报错,而且这种报错在 uni-app 的日志里还不明显,经常需要看微信的日志才能定位。

2.3 manifest.json 模块配置

在 uni-app 项目的manifest.json中,需要为不同的平台配置对应的 SDK 参数和模块权限。这里我以 HBuilderX 可视化操作为例:

  • App 模块配置:在App模块权限配置中,勾选Geolocation(如果支付、分享需要定位则勾选,不需要可不勾)、OAuth(登录授权)、Payment(支付)、Share(分享)。
  • 微信登录:需要在OAuth配置里填入微信开放平台申请的 AppID 和 AppSecret。AppSecret实际上不需要写在前端,建议只在服务端持有。但 HBuilderX 的配置项要求必填,我的做法是填一个占位值,真正校验时以服务端为准。
  • 微信支付:在Payment配置中,选择微信支付,填入微信开放平台的 AppID,以及微信支付商户平台的商户号 mch_id。注意这里填的是开放平台的 AppID(和登录、分享共用同一个)。
  • 支付宝支付:在Payment配置中,添加支付宝,填入支付宝开放平台的应用 AppID 和支付宝公钥。
  • 分享:在Share配置中,选择微信好友/朋友圈,填入微信开放平台 AppID,苹果还需要配置 Universal Links。

注意:这里说的“填写的 AppID”,在实际 App 打包后,会和你在微信开放平台后台配置的 bundle ID/包名进行校验,任何一个对不上,拉起微信 SDK 时会失败。

3. 核心逻辑拆解:登录为什么不能只靠前端

登录是整个社会化功能里最基础但也最容易被低估的一环。很多初学者拿到uni.login一个成功的回调后,以为登录就完成了,实际上你只拿到了一个 code(临时凭证),并不是真正的用户身份。要拿到用户的 openid、unionid、昵称头像,必须用这个 code 去微信的接口换取。而这一步有一个重要的安全要求:必须放到服务端完成

为什么必须走服务端?很简单,uni.login返回的 code 有效期只有几分钟,且是一次性的。但换取到的openidsession_key是敏感数据,尤其是session_key,它可以直接解密用户手机号等敏感信息。如果写在纯前端里,相当于把这把钥匙扔到了用户手里,安全上等于裸奔。在实际项目中,微信官方也明确要求 code2session 的请求必须在开发者服务器完成。

3.1 微信登录完整链路

用户点击“微信登录”后,前端做的事:

uni.login({ provider: 'weixin', success: async (loginRes) => { // loginRes.code 为临时凭证,5分钟内有效 const code = loginRes.code; // 把 code 发送到自己的后端服务器 const res = await request.post('/api/login/wechat', { code }); // 后端返回自定义登录态 token const { token, userInfo } = res.data; uni.setStorageSync('token', token); uni.setStorageSync('userInfo', userInfo); }, fail: (err) => { // 用户取消授权或拉起失败 console.error('微信登录失败', err); } });

后端拿到 code 后,调用微信的jscode2session(小程序)或oauth2/access_token(App 移动应用)接口,换回 openid、unionid、session_key 等信息。这里有一个非常关键的概念要讲清楚。

openid 是用户在某个应用下的唯一 ID。同一个用户在“你的小程序”和“你的 App”里,openid 是不同的,但 unionid 相同(前提是两者绑定在同一微信开放平台账号下)。如果你的项目需要打通小程序端和 App 端用户体系,必须使用 unionid 作为用户唯一标识,而不能用 openid。

后端换到 openid 之后,还需要做这几件隐藏工作:

  1. 查数据库,若该 openid / unionid 不存在,自动创建用户记录;
  2. 生成一个你自己的token(推荐 JWT 或随机字符串),关联用户 ID,并设置过期时间;
  3. 将 token 返回给前端,前端存到storage中,后续请求头里带上 token。

很多团队在第一步就踩坑:直接用 openid 当用户 ID。从技术上没错,但如果未来接入了支付宝登录、苹果登录,或者同一用户从小程序迁移到 App,用户 ID 就会乱套。我的建议是自建一个user_id作为主键,openidunionid只作为第三方绑定的字段,这样用户体系才能真正“社会化”。

3.2 手机号登录与验证码登录的差异化

除了微信登录,很多应用也会提供手机号验证码登录。在 uni-app 里,短信登录的实现和其他 App 没有本质区别:前端输入手机号,请求发送验证码;后端生成验证码并接短信服务商(如阿里云短信、腾讯云短信)下发;用户输入验证码后,后端校验通过并创建/关联用户。

这里我给两个容易出问题的细节:

验证码必须有过期时间与失败次数限制。验证码有效期设为 5 分钟是常规操作,但除了这个,我建议限制同一手机号每天最多发送 5 次,同一 IP 每小时最多发送 10 次,验证码校验接口连续错误 5 次就作废当前验证码。这些限制能挡住绝大多数恶意刷短信的请求,别等被刷爆了才追悔莫及。

手机号登录成功后不要直接返回用户信息。如果用户是第一次用这个手机号登录,需要拉起“完善资料”的流程。如果已有账号,则正常进入首页。一个常见的用户体验问题是:新用户登录后首页显示为空头像、空昵称,非常突兀。我的做法是后端在登录接口返回一个is_new_user字段,前端据此决定跳转到资料完善页还是直接进首页。

3.3 登录态的存储与过期处理

登录态设计看似简单,实际牵一发动全身。uni-app 的 App 端有uni.setStorageSync,小程序端有同样的 API,数据都存储在本地。但如果用户清掉缓存,或者卸载重装,token 就没了。因此服务端的 token 必须设计成可校验、可续期

推荐方案是:

  • token 过期时间设 7 天(根据业务调整);
  • 每次请求时校验 token 是否有效;
  • 如果 token 距过期时间少于 2 天,后端在响应头中返回一个新的 token(刷新 token),前端收到后自动覆盖本地旧 token;
  • 如果 token 已过期,后端返回 401,前端判断后跳转登录页。

这套方案的实现成本不高,但用户体验会好很多,不用频繁重新登录。

4. 支付模块:微信支付与支付宝支付的差异化实现

支付是整个社会化功能里,安全要求最高、最容易出问题的一个模块。先解释一个很多新人困惑的问题:uni-app 的uni.requestPayment到底做了什么?

uni.requestPayment并不是真的“发起支付”,它只是“唤起支付控件”。真实的支付流程是:

  1. 用户在前端点击“支付”按钮;
  2. 前端将订单信息(商品 ID、金额、用户 token 等)发送到你的后端
  3. 你的后端调用微信支付/支付宝的“统一下单”接口,生成一笔预支付订单;
  4. 第三方支付平台返回一个用于支付调起的参数(如微信的timeStampnonceStrpackagesignTypepaySign;支付宝的orderStr);
  5. 后端把这个参数返回给前端;
  6. 前端调用uni.requestPayment拉起支付界面;
  7. 用户在支付界面输密码/指纹确认;
  8. 支付平台异步通知你的后端支付结果(回调 notify_url);
  9. 后端验证回调签名、更新订单状态,并给前端返回结果。

可以看到,前端在整个流程里只承担了“展示”和“确认”的工作,真正的资金流、订单状态都在服务端做。这一点必须给做前端的同事说清楚,别想着在前端直接调用微信支付接口实现支付。

4.1 微信支付的统一下单与参数生成

以 App 端微信支付为例。后端用商户号调用微信支付的v3/transactions/app接口,传参包括appid(开放平台移动应用的 AppID)、mchid(商户号)、description(商品描述)、out_trade_no(商户内部订单号)、notify_url(回调地址)、amount(金额,单位是分)。

这里有两个细节:

金额单位是分。如果你从数据库里取到的订单金额是元(比如 9.9 元),传给微信时不能直接用,要乘以 100 转成 990 分。这个坑我亲眼见过有人踩——前端给后端传了9.9,后端没做处理直接传给微信,微信那边直接报“金额无效”。反过来也是,微信回调里金额也是分,处理时要再转回元。

订单号 out_trade_no 必须唯一且只含数字、字母。建议规则:日期 + 随机数 + 用户 ID,例如2025012015301200012345。同一个订单号支付一次后,再次支付会直接失败,这也是防止重复支付的一种手段。

生成支付参数这部分,微信支付 v3 需要用到商户 API 证书和私钥进行签名。如果后端是 Java,可以用官方 SDKwechatpay-java;如果是 Node.js,可以用wechatpay-node-v3;但我不建议直接引入完整的 SDK,更推荐用封装的 HTTP 请求库,加上签名工具自己实现一遍。原因很简单:SDK 更新慢,遇到问题你连报错在哪都找不到。

用 Node.js 实现时,几个核心步骤示意:

const crypto = require('crypto'); // 构造请求 const url = 'https://api.mch.weixin.qq.com/v3/transactions/app'; const body = { appid: config.wx.appid, mchid: config.wx.mchid, description: '测试商品', out_trade_no: orderNo, notify_url: config.wx.notifyUrl, amount: { total: 990, currency: 'CNY' } }; // 需要把 body 序列化字符串和请求信息一起加入签名 const message = `POST\n/v3/transactions/app\n${timestamp}\n${nonce}\n${JSON.stringify(body)}\n`; const signature = crypto.sign('RSA-SHA256', Buffer.from(message), config.wx.privateKey); // 请求头中需要带上 Authorization,格式为 WECHATPAY2-SHA256-RSA2048 ... const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="...",nonce_str="...",timestamp="...",serial_no="...",signature="..."`; // 给前端的最终结果 const payParams = { timeStamp: timestamp + '', nonceStr: nonce, package: `prepay_id=${prepayId}`, signType: 'RSA', paySign: '' }; // paySign 是对上述信息再签名一次的结果

提示:客户端调用uni.requestPayment时,需要仔细核对传入参数名。uni-app 官方示例要求timeStampnonceStrpackagesignTypepaySign这些参数和水微信 App SDK 一致,不能传错名。有一个常见问题:timeStamp是字符串类型,如果是数字类型,在某些 iOS 版本会拉起失败。

4.2 支付宝支付的差异点

支付宝支付的接入思路类似,但有几个明显差异:

服务端统一下单接口不同。支付宝用的是alipay.trade.app.pay(App 支付),返回的不是一个支付 ID,而是直接返回一个orderStr字符串(一段经过签名的订单描述字符串)。前端拿到orderStr后,直接传给uni.requestPayment即可。

签名方式不同。支付宝使用 RSA2 签名(SHA256withRSA),签名对象是“业务参数 + 公共参数”拼成的字符串。签名内容的具体拼法,官方文档写得很详细,但新手还是容易漏掉编码问题——所有参数值在拼接前必须做 URL 解码或编码处理,特别是商品名称中有中文、特殊符号时。

回调验签方式不同。支付宝回调返回的是POST表单格式,验签主要是在所有参数中剔除signsign_type两个字段后,将剩余参数按字典序排序、拼接、再用支付宝公钥验签。这一步很多后端同学容易搞混:验签用的是支付宝公钥,不是应用私钥;生成订单签名用的是应用私钥,不是支付宝公钥。一对密钥对,一个用来签名,一个用来验签,不能混。

// 后端接收支付宝回调(Node.js 风格,伪代码) const query = event.queryStringParameters; const sign = query.sign; const signType = query.sign_type; delete query.sign; delete query.sign_type; const sortedKeys = Object.keys(query).sort(); let content = sortedKeys.map(key => `${key}=${query[key]}`).join('&'); const result = crypto.createVerify('RSA-SHA256').update(content); // 用支付宝公钥验证 const isValid = result.verify(alipayPublicKey, sign, 'base64');

4.3 支付回调处理的原则

支付回调是业务准确性的生命线,处理不当会导致“用户已付款,但订单未更新”的客诉,甚至资金对不上账。我自己总结的三个原则是:

原则一:先验签,再查单。任何回调请求到达后,第一件事是验签。验签失败直接返回失败,不进入业务逻辑。验签通过后,再根据out_trade_no查自己的订单状态,防止平台重复通知时重复处理。

原则二:幂等处理。微信和支付宝都会对支付结果进行多次通知,直到你返回成功为止。因此,回调处理逻辑必须天然幂等:同一笔订单无论被通知多少次,最终效果只能有一次成功。常见做法是在订单表加一个“支付状态”字段,逻辑判断如果已经是“已支付”,直接返回成功,不做重复更新。

原则三:回调成功不代表前端页面立即更新。支付平台回调到你服务器一般是秒级延迟,但用户可能更快返回 App。所以前端在“支付成功”页面的最佳实践是:支付完成后询问后端订单状态,如果 3 秒内未确认“已支付”,进入轮询(每 2 秒查询一次,最多查 10 次),这样能覆盖大多数回调延迟场景。

5. 分享模块:uni.share 的通用场景与特定限制

分享功能在 uni-app 里被封装成uni.share,调用方式很简单,但它背后的平台限制才是重点。

5.1 基础分享调用

在 App 端(微信好友/朋友圈)调起分享,代码大致如下:

uni.share({ provider: 'weixin', scene: 'WXSceneSession', // 会话,即聊天 type: 0, // 0 网页类型 title: '分享标题', summary: '分享摘要', href: 'https://yourdomain.com/share-page?id=123', imageUrl: 'https://yourdomain.com/static/share-icon.png', success: () => { // 分享成功(指成功拉起微信并返回) }, fail: (err) => { // 用户取消或分享失败 } });

分享到朋友圈时,把scene换成WXSceneTimeline即可。这里有个细节:微信对分享到朋友圈的href页面有限制吗?并没有限制,但朋友圈的分享卡片文案,很多时候只显示标题和图片,摘要大多不展示。

5.2 小程序端分享的“另类实现”

小程序端的分享能力,uni-app 同样有封装。如果你在小程序里需要“分享给好友”,可以直接:

// 小程序内右上角菜单分享 onShareAppMessage() { return { title: '邀请你一起使用', path: '/pages/index/index?inviter=123', imageUrl: 'https://yourdomain.com/share-wx.png' }; }

但小程序分享到朋友圈,入口比较深,需要用户在右上角菜单里主动选择,不能通过代码直接拉起。如果你的业务强依赖“分享到朋友圈”来做增长,这个小程序规则会是一个天然的瓶颈,需要提前让产品经理知道。

还有一个容易忽略的点:小程序分享的path中带的参数(比如邀请人 ID),接收方点开时会自动拼接在页面路径上,前端可以在onLoad中获取。这是很多社交裂变活动的数据追踪基础,但一定要对参数做签名校验,否则恶意用户可以伪造邀请人 ID,造成数据混乱。

5.3 App 端分享卡的“隐藏问题”

App 端分享,我遇到的两个高频坑是:

图片 URL 不能是本地路径。imageUrl必须是一个可访问的网络图片地址,如果你的图片是本地的static/share.png,部分 Android 机型分享时会白屏或者分享失败。解决办法是:把分享图片传到 CDN 或 OSS,确保 URL 可公网访问,并且图片格式尽量用 PNG,大小控制在 500KB 以内。

Android 微信分享需要包名和签名匹配。这一点上面提到过,但这里要再强调一次:分享和登录用的 AppID 是同一个,开放平台后台也会校验签名。如果是调试阶段使用 HBuilderX 的“自定义基座”,你必须在微信开放平台配置“调试基座”的包名和签名;等正式打包时,又要重新配置正式包的包名和签名。很多开发者忘记切换导致正式环境下分享失败,排查半天才发现是开放平台配置错了。

6. 实操过程与核心环节实现

前面把原理讲了一下,这一节我们直接落地,以“微信小程序登录 + App 微信支付 + App 微信分享”的完整项目为例,把每个环节的操作步骤和注意点写清楚。这个组合是现实中最常见的搭配。

6.1 工程初始化与 manifest 配置

新建 uni-app 项目后,第一件事是打开manifest.json,在“微信小程序”配置项中填写小程序的 AppID;在“App 模块配置”中按需勾选OAuth(登录鉴权)Payment(支付)Share(分享)

如果是 HBuilderX 项目,可以用可视化界面勾选;如果是 CLI 项目,直接修改manifest.json源码。注意:这里的配置项必须与你真实申请到的 AppID、密钥一致,快捷键保存后 HBuilderX 会自动重新编译。

随后在pages.json中确认loginpayshare这几个页面的路由是正常的。我这里习惯把登录页、支付结果页、分享引导页都做进分包,避免主包体积过大。小程序主包超过 2MB 会导致发布不通过,App 端虽然没有这么严格的限制,但分包对后续维护有好处。

6.2 登录流程完整落地

流程分成三段:前端拉起授权 → 后端换 session → 前端存 token。

前端代码示意(uni-app 微信小程序端):

在登录页放一个“微信登录”按钮,点击后执行:

handleWechatLogin() { uni.login({ provider: 'weixin', success: (loginRes) => { if (loginRes.code) { this.sendCodeToServer(loginRes.code); } else { uni.showToast({ title: '未获取到 code', icon: 'none' }); } }, fail: () => { uni.showToast({ title: '用户取消登录', icon: 'none' }); } }); }

需要说明的是,小程序的uni.login相比 App 端,拉起的是微信内部的授权弹窗,一般情况下用户没有特别明显的感知,但如果你在小程序里调uni.getUserProfile来获取头像昵称,那会在登录前就弹出“申请获取你的昵称头像”的授权弹窗。从微信官方规则来说,头像昵称的获取必须由用户主动触发,且每次进入小程序都需要用户点击同意。很多开发者的误区是:认为授权一次以后就永久生效,其实微信早已调整策略,不再提供“长期授权”的能力。所以,如果后端已经能通过 unionid 识别用户,就不要强制依赖前端拿头像昵称,等用户后续主动完善资料时再采集。

后端代码示意(Node.js Koa 版,仅展示换 session 部分):

const axios = require('axios'); router.post('/api/login/wechat', async (ctx) => { const { code } = ctx.request.body; // 用 code 换取 openid 和 session_key const url = 'https://api.weixin.qq.com/sns/jscode2session'; const params = { appid: config.wx.appid, secret: config.wx.secret, js_code: code, grant_type: 'authorization_code' }; const res = await axios.get(url, { params }); const { openid, session_key, unionid } = res.data; if (!openid) { ctx.status = 400; ctx.body = { message: '微信登录失败', detail: res.data }; return; } // 查数据库或新建用户 let user = await findUserByUnionId(unionid) || await createUser({ unionid, openid }); // 生成自定义 token const token = generateToken(user.id); ctx.body = { token, userInfo: sanitizeUser(user) }; });

登录态存储与请求携带:

前端把token存储在uni.setStorageSync('token', token)后,全局请求封装里每次带上:

const request = (url, data, method = 'POST') => { return new Promise((resolve, reject) => { const token = uni.getStorageSync('token'); uni.request({ url: BASE_URL + url, method, data, header: { 'Authorization': `Bearer ${token}` }, success: (res) => { if (res.statusCode === 401) { // token 失效,跳转登录页 uni.navigateTo({ url: '/pages/login/login' }); reject(res); } else { resolve(res.data); } }, fail: reject }); }); };

6.3 微信支付流程完整落地

以 App 端微信支付为例,我们要分两层来实现。

后端预支付接口:

前端发起支付前,向后端发送订单信息,后端生成微信预支付订单并返回前端需要的参数。

router.post('/api/pay/wechat/prepay', async (ctx) => { const { orderId } = ctx.request.body; // 业务逻辑:从库里读取订单,校验用户权限、金额 const order = await findOrderById(orderId); if (!order || order.status !== 'UNPAID') { ctx.body = { code: 1, message: '订单不存在或已支付' }; return; } // 生成商户订单号 const outTradeNo = `${Date.now()}${Math.random().toString(36).slice(2, 8)}`; // 调用微信支付接口(此处简化代码,实际请引入请求封装) const prepayId = await createWechatPrepayOrder({ outTradeNo, appid: config.wx.appid, mchid: config.wx.mchid, description: order.title, notifyUrl: config.wx.notifyUrl, amount: order.amount // 单位:分 }); // 生成前端拉起支付需要的参数 const payParams = generatePayParams(prepayId); ctx.body = { code: 0, payParams }; });

前端拉起支付:

const res = await request('/api/pay/wechat/prepay', { orderId }); if (res.code === 0) { const { timeStamp, nonceStr, package: pkg, signType, paySign } = res.data.payParams; uni.requestPayment({ provider: 'wxpay', timeStamp, nonceStr, package: pkg, signType, paySign, success: () => { // 注意:这里只代表用户完成了微信端的支付动作,最好轮询确认订单状态 uni.redirectTo({ url: '/pages/pay/pay-result?orderId=' + orderId }); }, fail: (err) => { // 用户取消或支付失败 uni.showToast({ title: '支付未完成', icon: 'none' }); } }); }

这里有一个很重要的地方:uni.requestPaymentsuccess回调并不代表支付已经成功,它只代表微信 SDK 成功调起了支付界面。如果用户付完钱后,支付平台回调还没到,前端就显示“支付成功”,而后端数据库里订单状态还是未支付,就会出现对账问题。所以我强烈建议:success回调里不要立刻更新本地业务状态,而是进入“查询订单状态”的轮询流程。等后端确认订单已支付,再引导用户跳转到“支付成功”页面。

6.4 分享流程完整落地

分享在 App 端的调用,我已经在代码示例里给过。这里补充两个实际项目中几乎必做的扩展:

分享数据动态化。分享的标题、图片、链接不能写死。常见玩法是:用户在列表页点击“分享”按钮,前端先请求后端拿到分享模板数据,再调uni.share。比如:

const shareRes = await request('/api/share/config', { scene: 'product', productId: 123 }); if (shareRes.code === 0) { uni.share({ provider: 'weixin', scene: shareRes.data.scene, // 'WXSceneSession' 或 'WXSceneTimeline' type: 0, title: shareRes.data.title, summary: shareRes.data.summary, href: shareRes.data.url, imageUrl: shareRes.data.image }); }

分享回调的埋点与数据追踪。uni.sharesuccess回调只能告诉你“微信 SDK 成功唤起”,无法告诉你用户是不是真的发了出去。如果要精确追踪分享数据,最可靠的方式是:在分享链接里带上分享人 ID,接收方打开分享页时,后端在 URL 参数中记录一次“曝光”或“点击”,并把来源 ID 和自己的 ID 绑定。这样即使微信不提供分享回传数据,你也能通过自己的服务器拿到比较准确的裂变效果数据。

7. 常见问题与排查技巧实录

这部分全部来自我实际开发中遇到或帮朋友排查过的问题,按模块整理成速查表,方便你按图索骥。

7.1 登录类高频问题

现象可能原因排查思路与解法
uni.login返回失败,errMsg提示invalid codecode 有效期过短或被重复使用前端拿到 code 后立即传给后端;检查是否有接口重试机制,导致同一 code 被调用两次
App 端点击微信登录无反应,日志提示not support微信开放平台未创建应用,或 AppID 未配置确认开放平台移动应用已创建,manifest.json中 OAuth 配置已填 AppID
微信登录后用 openid 查不到用户小程序和 App 的 openid 不同改用 unionid 关联用户,且确保两个应用绑定同一开放平台账号
登录后接口偶发 401token 过期时间太短,或服务器时间不准确认 JWT 的过期判断是相对时间还是绝对时间,检查服务器 NTP 同步

独家经验:微信登录在 iOS 上偶发“未验证应用”的问题,90% 是 bundle ID 和开放平台不一致,剩下 10% 是 Universal Links 没配好。Universal Links 的配置需要在 iOS 工程里关联域名,并在服务器根目录放apple-app-site-association文件。如果你的项目前后端分离,且没有运维协助,建议在开发初期就把这个文件准备好,否则分享、登录、支付都会受影响。

7.2 支付类高频问题

现象可能原因排查思路与解法
拉起支付提示package参数错误prepay_id=前缀缺失或自行拼写错误确认传给uni.requestPaymentpackage字段是prepay_id=xxx
支付成功后订单状态未更新回调验签失败,或回调地址配置错误检查后端日志,确认回调地址能收到请求;验签逻辑中排除sign字段
Android 支付正常,iOS 拉起失败常见是timeStamp类型错误确保timeStamp是字符串,且为秒级时间戳(10位),不是毫秒级(13位)
支付结果通知重复推送,订单量对不上回调处理未幂等增加“已支付”状态判断,已处理完的订单直接返回成功
支付宝支付回调验签总失败验签字母拼接包含漏项,或公钥配置错误用支付宝官方验签工具在线测试,对比参数拼接结果

独家经验:微信支付 v2 转 v3 之后,很多老代码还在用 v2 的MD5签名,而 HBuilderX 内置的微信支付模块默认走 App SDK 拉起。如果你的后端是 v3 接口,而前端uni.requestPayment里的signType写的MD5,iOS 端会直接拉不起微信。正确做法是signType字段写RSA,这是微信支付 v3 和 App SDK 之间公认的签名类型。我在项目里吃过这个亏,排查了一整天。

7.3 分享类高频问题

现象可能原因排查思路与解法
分享出去没有缩略图imageUrl无法访问,或图片格式不正确确认图片地址必须是网络图片,支持 HTTPS,后缀名正确
分享到朋友圈无内容scene配置错误,或标题太长朋友圈分享建议scene: 'WXSceneTimeline',标题控制在 30 字以内
分享回调显示成功,但微信里没有记录微信 SDK 的“分享成功”只代表拉起成功通过分享链接的落地页统计真实分享数据
H5 端无法调起分享H5 不支持uni.shareH5 的分享需要接入第三方社交分享组件,或引导用户复制链接

独家经验:在 Android 手机上使用自定义调试基座测试分享时,经常出现分享成功后微信自动退出或闪一下,这大概率是微信开放平台后台的“应用签名”填错了。HBuilderX 的官方文档提供了一个获取签名的小工具(dcloud.getUniAppSign),用它生成的 MD5 再去微信后台填,比自己在命令行里算要稳得多。

7.4 打包与权限引发的“隐藏炸弹”

除了上面这些直接问题,还有一个容易忽略的“隐藏炸弹”:App 打包时的权限声明。如果manifest.json里勾选了短信、通讯录等敏感权限,但业务中根本没用到,应用市场上架审核时很容易被拒。而在登录支付分享这个组合里,需要留意的是:

  • 使用微信登录/分享/支付时,需要在权限配置里勾选INTERNET,这个是基础权限,默认已有;
  • 如果你的分享功能需要读取本地图片(比如分享本地相册图片给好友),则需要相册权限,在manifest.json里配置好对应的 iOSNSPhotoLibraryUsageDescription和 Android 相应权限;
  • 支付宝支付在 Android 上偶尔会因为缺READ_PHONE_STATE权限导致拉起失败,但这个权限属于敏感权限,建议只在支付模块初始化时动态申请。

注意:uni-app 的 App 端在本地调试时,用“自定义调试基座”和“标准基座”的包名、签名可能不一样。如果你用自定义基座调试微信登录,一定要确认基座里的 AppID 和开放平台的配置一致,否则调试时一切正常,打正式包后却全部失效,这类问题最难排查。

8. 最后再补一句我的体感

登录、支付、分享这三个功能,业务都不复杂,复杂的永远在细节里。三端适配、微信支付宝双渠道、回调幂等、发布审核规则,每一个环节都能单独写一篇长文。做了这么多年跨端开发,我最大的体会是:在动手写代码之前,把账号权限、回调地址、包名校验这些“趟路”的脏活累活先做完,后面代码实现反而是水到渠成的事。

还有一个小建议:不要把所有逻辑都写在 App 前端。登录态的校验、支付回调的处理、分享裂变的埋点,这些统统放到服务端。前端只做 UI 展示和用户交互,能少背很多锅。

这套方案在我参与运营的多个项目里落地过,从日活几千到十几万都扛住了。如果你在接入过程中遇到别的问题,或者发现某些平台规则有了新变化,欢迎在评论区补充交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询