uni-app微信小程序登录与支付全链路实战指南
2026/9/14 9:05:05 网站建设 项目流程

简介:这是一套基于uni-app框架开发的多端商城源码,面向前端开发者与小程序初学者,解决跨平台电商应用快速搭建难题,尤其适用于需集成微信登录与支付功能的轻量级小程序项目。资源共2000个文件,以292个.vue页面组件、322个.js逻辑脚本、226个.json配置及214个.wxml+214个.wxss微信小程序专属文件为主,完整覆盖页面结构、交互逻辑、样式渲染与平台适配;另含176个Java后端接口示例及MyBatis相关映射文件,体现前后端协同设计思路。压缩包仅2.84MB,结构清晰,按“脚本/源码/相关文件”三级目录组织,便于快速定位核心模块。目前已有668人学习下载,购买者可直接运行调试、二次开发定制,掌握uni-app多端编译、微信开放能力接入、小程序支付闭环等关键实践技能。

1. 一个能真正在微信里跑通登录+支付的 uni-app 商城,不是 demo,是上线前最后一关

很多开发者卡在「uni-app 商城能跑,但微信登录点不动、支付一直提示requestPayment:fail invalid sign」——这不是代码写错了,而是没理清微信生态里三套身份体系的边界:小程序用户 openid、unionid、商户号与 appid 的绑定关系、以及支付签名所需的prepay_id生命周期。这个标题里的「含小程序微信登陆、小程序微信支付」,本质是要求你把 uni-app 的跨端能力,精准锚定到微信官方 SDK 的调用链路上:从wx.login()换取 code,到后端用该 code 向微信接口换 openid,再到创建订单时传入openid生成预支付交易,最后前端调起wx.requestPayment()完成唤起。它适合已经用 HBuilderX 搭出商品列表和购物车、正准备对接真实微信环境的中阶开发者;新手容易在appid填错环境(开发版/体验版/正式版)、mch_id未开通 JSAPI 支付权限、或后端返回的timeStamp类型为字符串而非数字这三处直接卡死。


2. 微信登录:从 wx.login() 到后端换取 openid 的完整链路与参数校验

2.1 为什么 uni-app 的 uni.login() 不适用于微信小程序登录?

uni-app 提供的uni.login()是跨平台抽象层,但在微信小程序环境下,它默认调用的是wx.login(),看似等价,实则埋下隐患:uni.login()返回的code在部分 HBuilderX 版本中会因平台判断逻辑问题,返回空值或过期 code;更关键的是,它无法控制scope权限粒度,而微信要求获取用户信息必须显式声明scope.userInfo,且需用户主动授权。真实生产环境必须弃用uni.login(),改用原生wx.login()+wx.getUserInfo()组合调用,确保 code 有效、用户态明确、后续可追溯。

// ✅ 正确做法:在 onLaunch 或需要登录的页面中 export default { methods: { async handleWechatLogin() { try { // 第一步:获取临时登录凭证 code const loginRes = await wx.login(); if (loginRes.errMsg !== 'login:ok') { throw new Error('wx.login 失败:' + loginRes.errMsg); } // 第二步:调用自己后端接口,传 code 换取 openid const res = await uni.request({ url: 'https://your-api.com/api/wechat/login', method: 'POST', data: { code: loginRes.code } }); if (res[1].data.code === 200) { uni.setStorageSync('userToken', res[1].data.token); uni.setStorageSync('openid', res[1].data.openid); } else { uni.showToast({ title: '登录失败', icon: 'none' }); } } catch (e) { console.error('微信登录异常', e); uni.showToast({ title: '网络错误,请重试', icon: 'none' }); } } } }

注意wx.login()获取的code有效期仅 5 分钟,且同一 code 只能使用一次。后端必须在收到 code 后立即向微信接口https://api.weixin.qq.com/sns/jscode2session发起请求,携带appidsecretjs_codegrant_type=authorization_code四个参数。若后端缓存了旧 code 或重复使用,将返回errcode: 40029

2.2 后端换取 openid 的关键参数与微信接口响应解析

微信jscode2session接口返回 JSON 格式数据,核心字段为openid(用户在当前小程序的唯一标识)和unionid(同一微信开放平台下多应用共用的用户 ID)。商城类应用必须依赖openid,而非unionid,因为微信支付 JSAPI 接口要求下单时必须传入openid,且该openid必须与调起支付的小程序appid绑定一致。

字段名类型是否必填说明
appidstring小程序的 AppID,不是公众号或开放平台的 AppID
secretstring小程序后台「开发管理 → 开发者秘钥」中获取,切勿前端暴露
js_codestring前端wx.login()返回的 code
grant_typestring固定值authorization_code

后端 Node.js 示例(Express):

// POST /api/wechat/login app.post('/api/wechat/login', async (req, res) => { const { code } = req.body; const appId = 'wx1234567890abcdef'; // 替换为你的小程序 AppID const appSecret = 'your_app_secret_here'; // 从微信后台复制,严禁硬编码在前端 try { const wechatRes = await axios.get( 'https://api.weixin.qq.com/sns/jscode2session', { params: { appid: appId, secret: appSecret, js_code: code, grant_type: 'authorization_code' } } ); const { openid, unionid, errcode, errmsg } = wechatRes.data; if (errcode) { return res.status(400).json({ code: 400, msg: `微信接口错误:${errmsg}` }); } // ✅ 关键:此处必须保存 openid,并关联到用户 session 或 token const token = jwt.sign({ openid }, process.env.JWT_SECRET, { expiresIn: '7d' }); res.json({ code: 200, msg: '登录成功', token, openid, unionid: unionid || null // unionid 仅当小程序绑定开放平台时存在 }); } catch (e) { res.status(500).json({ code: 500, msg: '服务器内部错误' }); } });

提示:若返回errcode: 40013,说明appidsecret不匹配,检查是否填错小程序 AppID(不是公众号)、或secret是否被重置过;若返回errcode: 40001,则是secret错误或已过期。


3. 微信支付:从创建预支付订单到前端调起 requestPayment 的全流程落地

3.1 微信支付 JSAPI 接口调用前提:三证合一与权限开通

在代码层面能调通wx.requestPayment()之前,必须完成三项微信侧配置,缺一不可:

  • 商户号(mch_id)已通过微信支付商户平台认证,且开通「JSAPI 支付」能力;
  • 小程序 AppID 已在商户平台「产品中心 → APPID 授权管理」中绑定该商户号
  • 后端服务器 IP 已添加至商户平台「账户中心 → API 安全 → IP 白名单」(否则调用微信统一下单接口会返回invalid ip)。

注意:很多开发者忽略第二项——即使你有商户号、有小程序 AppID,若未在商户平台手动绑定二者,后端调用unifiedorder接口时会返回errcode: 10005appid not bind mchid),此时前端wx.requestPayment()会直接报requestPayment:fail invalid sign,但错误日志里完全不提绑定问题,极易误判为签名错误。

3.2 后端统一下单接口(unifiedorder)的核心参数与签名逻辑

微信支付 JSAPI 下单必须调用https://api.mch.weixin.qq.com/pay/unifiedorder,这是一个 HTTPS POST 接口,必须使用证书(apiclient_cert.pem + apiclient_key.pem)进行双向认证,且所有参数需按微信规则生成sign签名。关键字段如下表:

参数名类型是否必填说明
appidstring小程序 AppID
mch_idstring商户号
nonce_strstring随机字符串,32位以内,建议用时间戳+随机数
bodystring商品描述,如uni-app商城-订单#10001
out_trade_nostring商户系统内部订单号,必须全局唯一,建议用时间戳+6位随机数
total_feeint订单金额,单位为分(如 100 元 →10000
spbill_create_ipstring调用统一下单的机器 IP,不能填 localhost 或 127.0.0.1
notify_urlstring支付结果异步通知地址,必须为 HTTPS,且能被微信外网访问
trade_typestring固定值JSAPI
openidstring用户在该小程序下的 openid,必须与 appid 匹配

Node.js 使用wechat-pay库示例(简化版):

const WechatPay = require('wechat-pay').default; const pay = new WechatPay({ appid: 'wx1234567890abcdef', mch_id: '1234567890', partner_key: 'your_mch_key_here', // 商户平台「API安全 → 密钥」设置的32位KEY pfx: fs.readFileSync('./cert/apiclient_cert.pem'), // 证书路径 passphrase: 'your_mch_key_here' // 与 partner_key 相同 }); // 创建预支付订单 app.post('/api/pay/create', async (req, res) => { const { openid, orderNo, amount } = req.body; // amount 单位:分 try { const result = await pay.unifiedOrder({ body: `uni-app商城-订单${orderNo}`, out_trade_no: orderNo, total_fee: amount, spbill_create_ip: '119.123.45.67', // 服务器公网IP,非本地IP notify_url: 'https://your-domain.com/api/pay/notify', trade_type: 'JSAPI', openid }); // ✅ 微信返回的 prepay_id 是调起支付的关键 const { prepay_id, timestamp, nonceStr, package: pkg, signType, paySign } = result; // 构造前端调用 wx.requestPayment 所需参数 res.json({ code: 200, data: { appId: 'wx1234567890abcdef', timeStamp: String(Date.now()), // 注意:必须是字符串类型,微信要求 nonceStr, package: `prepay_id=${prepay_id}`, signType: 'MD5', paySign } }); } catch (e) { console.error('统一下单失败', e.response?.data || e.message); res.status(500).json({ code: 500, msg: '支付创建失败' }); } });

关键细节timeStamp字段在微信文档中要求为「当前时间戳,单位秒」,但实际wx.requestPayment()接口校验时,接受毫秒级时间戳字符串(如"1712345678901"),若传入整数类型会报invalid time stamppackage字段必须严格为prepay_id=xxx格式,前后不能有空格。

3.3 前端调起支付:wx.requestPayment 的最小可行调用与错误捕获

uni-app 中调用wx.requestPayment()必须使用uni.getProvider()显式指定平台,避免 HBuilderX 自动降级到支付宝或其它支付方式:

// ✅ 在支付确认页的 methods 中 async doPayment(orderNo) { try { // 1. 先请求后端获取支付参数 const res = await uni.request({ url: 'https://your-api.com/api/pay/create', method: 'POST', data: { openid: uni.getStorageSync('openid'), orderNo, amount: this.orderAmount * 100 // 转为分 } }); const payParams = res[1].data.data; // 2. 调起微信支付 await wx.requestPayment({ ...payParams, success: (res) => { console.log('支付成功', res); uni.showToast({ title: '支付成功', icon: 'success' }); setTimeout(() => { uni.navigateTo({ url: '/pages/order/success?orderNo=' + orderNo }); }, 1500); }, fail: (err) => { console.error('支付失败', err); // ❗重点:微信支付失败 err.errMsg 可能为以下几种 // 'requestPayment:fail cancel' —— 用户取消 // 'requestPayment:fail system error' —— 系统错误(如签名失效) // 'requestPayment:fail invalid sign' —— 签名错误(最常见,检查后端 sign 生成逻辑) if (err.errMsg.includes('cancel')) { uni.showToast({ title: '已取消支付', icon: 'none' }); } else { uni.showToast({ title: '支付失败,请重试', icon: 'none' }); } } }); } catch (e) { console.error('支付流程异常', e); uni.showToast({ title: '网络错误,请重试', icon: 'none' }); } }

提示:若fail回调中err.errMsginvalid sign,请立即检查后端paySign生成逻辑——是否漏加&key=xxx、是否参数排序错误、是否package字段格式不对(必须是prepay_id=xxx)、是否timeStamp传成了数字而非字符串。


4. 微信支付回调与订单状态同步:防止用户关闭页面导致状态丢失

4.1 异步通知(notify_url)的幂等性设计与验签逻辑

微信支付成功后,会以 POST 方式向你配置的notify_url发送 XML 格式通知。该通知可能重复发送(网络超时重试),且无任何身份验证头,必须靠微信签名sign字段验真。核心步骤:解析 XML → 提取所有非空字段 → 按字典序拼接key=value&字符串 → 末尾追加&key=商户KEY→ MD5 小写 → 与sign字段比对。

Node.js Express 示例:

// POST /api/pay/notify (注意:此路由不能有 bodyParser.json() 中间件,需原始 body) app.post('/api/pay/notify', async (req, res) => { // 1. 获取原始 XML body const xml = await getRawBody(req); // 2. 解析 XML(使用 xml2js) const parsed = await parseStringPromise(xml); const { xml: data } = parsed; // 3. 验签:提取所有非空字段,按 key 字典序排序拼接 const sign = data.sign[0]; delete data.sign; const signStr = Object.keys(data) .filter(key => data[key][0] && typeof data[key][0] === 'string') .sort() .map(key => `${key}=${data[key][0]}`) .join('&') + '&key=your_mch_key_here'; // 注意:key 是商户平台设置的 API KEY const mySign = md5(signStr).toLowerCase(); if (mySign !== sign.toLowerCase()) { return res.send('<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[SIGN ERROR]]></return_msg></xml>'); } // 4. ✅ 验签通过,处理业务逻辑 if (data.result_code[0] === 'SUCCESS' && data.return_code[0] === 'SUCCESS') { const outTradeNo = data.out_trade_no[0]; const transactionId = data.transaction_id[0]; // 更新订单状态为「已支付」,并记录 transaction_id await updateOrderStatus(outTradeNo, 'paid', transactionId); // 发货、扣库存、发短信等后续操作... } // 5. 必须返回 SUCCESS XML,否则微信会持续重发 res.send('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'); });

注意getRawBody需使用raw-body库获取原始流,不能依赖body-parserupdateOrderStatus必须是原子操作(如数据库UPDATE ... WHERE status = 'unpaid'),防止重复通知导致状态被多次更新。

4.2 主动查询补救机制:当 notify_url 不可达时的兜底方案

若服务器宕机、防火墙拦截或域名 DNS 故障,导致notify_url无法接收微信通知,订单将长期处于「待支付」状态。此时需启用「订单支付状态主动查询」作为兜底:

  • 在用户点击「去支付」后,启动一个setInterval,每 3 秒调用后端/api/pay/query?out_trade_no=xxx接口;
  • 后端调用微信https://api.mch.weixin.qq.com/pay/orderquery接口,传入out_trade_no查询订单状态;
  • 一旦查到trade_state=SUCCESS,立即更新本地订单状态,并清除定时器;
  • 若连续查询 10 次(即 30 秒)仍未成功,提示用户「支付结果未知,请在「我的订单」中查看」。

uni-app 前端主动轮询示例:

startPolling(orderNo) { this.pollingTimer = setInterval(async () => { try { const res = await uni.request({ url: `https://your-api.com/api/pay/query?out_trade_no=${orderNo}` }); const { trade_state } = res[1].data; if (trade_state === 'SUCCESS') { clearInterval(this.pollingTimer); uni.showToast({ title: '支付成功', icon: 'success' }); uni.navigateTo({ url: '/pages/order/success?orderNo=' + orderNo }); } else if (trade_state === 'CLOSED' || trade_state === 'REVOKED') { clearInterval(this.pollingTimer); uni.showToast({ title: '支付已关闭', icon: 'none' }); } } catch (e) { console.warn('查询支付状态失败,继续轮询'); } }, 3000); }

关键点:轮询必须设上限(如 10 次),避免无限请求;查询接口返回trade_state字段含义需严格对照微信文档:SUCCESS(支付成功)、REFUND(转入退款)、NOTPAY(未支付)、CLOSED(已关闭)、REVOKED(已撤销)、USERPAYING(用户支付中)。


5. 实战排错:5 类高频报错的定位路径与修复指令

5.1requestPayment:fail invalid sign—— 签名错误的三层排查法

这是微信支付最常遇到的报错,表面是签名错,实则可能源于三个层级:

层级检查点验证命令/方法
前端层timeStamp是否为字符串?package是否为prepay_id=xxx格式?signType是否为MD5console.log(typeof payParams.timeStamp, payParams.package)
后端层paySign生成时,是否漏掉&key=xxx?参数是否包含空值字段?nonceStr是否与下单时一致?unifiedOrder返回后,打印result.sign与本地计算的mySign对比
微信侧商户号是否开通 JSAPI 支付?小程序 AppID 是否已在商户平台绑定?商户平台「API安全」中 KEY 是否与代码一致?登录 pay.weixin.qq.com ,进入「产品中心 → 开发配置」逐项核对

快速验证指令:在后端unifiedOrder成功返回后,立即用 Postman 模拟wx.requestPayment参数,调用https://api.mch.weixin.qq.com/pay/orderquery查询该out_trade_no,若能查到订单,则证明签名逻辑正确,问题一定出在前端传参或微信侧配置。

5.2login:fail scope is not authorized—— 用户拒绝授权后的优雅降级

当用户首次进入小程序,点击登录按钮却弹出「拒绝授权」提示,wx.getUserInfo()会直接失败。此时不能简单报错,而应引导用户手动开启:

// 在 wx.getUserInfo 失败回调中 fail: (err) => { if (err.errMsg.includes('scope not authorized')) { // ✅ 弹出引导框,说明需要授权才能使用完整功能 uni.showModal({ title: '授权提示', content: '为了提供完整的购物体验,需要获取您的公开信息(昵称、头像)。请前往「设置」→「隐私」→「小程序授权管理」中开启。', showCancel: true, confirmText: '去设置', success: (res) => { if (res.confirm) { // 跳转小程序设置页 wx.openSetting({ success: (settingRes) => { if (settingRes.authSetting['scope.userInfo']) { // 用户已开启,重新尝试获取 this.handleWechatLogin(); } } }); } } }); } }

5.3Error: Request failed with status code 400—— 后端统一下单 400 错误的字段级定位

微信unifiedorder接口返回 400 时,响应体是 XML 格式,需解析<err_code><err_code_des>字段。常见组合及修复:

err_codeerr_code_des修复动作
INVALID_REQUESTparameter format error检查total_fee是否为整数、spbill_create_ip是否为合法公网 IP、out_trade_no是否含特殊字符
ORDERPAIDorder already paidout_trade_no重复提交,确保订单号全局唯一(建议Date.now() + Math.random().toString(36).substr(2, 6)
NOAUTHno permission商户号未开通 JSAPI 支付,或小程序 AppID 未在商户平台绑定

调试技巧:在后端unifiedOrder调用前,打印所有参数对象,用 JSON.stringify 格式化输出,人工比对微信文档字段要求;特别注意total_fee必须是整数,body不能含 emoji 或控制字符。

5.4wx.requestPayment:fail system error—— 真机调试必备的证书与域名白名单检查

此错误只在真机出现,模拟器无法复现。原因几乎全是环境配置问题:

  • 小程序后台「开发管理 → 开发者工具」中,是否开启了「不校验合法域名」?(仅开发阶段可用,上线必须关闭);
  • requestPayment所需的appIdtimeStampnonceStrpackagesignTypepaySign六个字段,是否全部由后端返回,且前端未做任何修改?(尤其注意package字段,前端拼接会导致签名失效);
  • HBuilderX 运行时,是否勾选了「微信开发者工具」而非「浏览器」?(浏览器环境无法调起微信支付)。

5.5 支付成功但 notify_url 未触发 —— 网络连通性四步诊断

当用户看到「支付成功」弹窗,但订单状态仍为「待支付」,大概率是notify_url未收到微信回调:

  1. 检查域名是否备案且 HTTPS 有效:使用 SSL Labs 测试证书链完整性;
  2. 检查服务器防火墙:执行curl -v https://your-domain.com/api/pay/notify,看是否返回 404 或连接超时;
  3. 检查微信商户平台「API安全 → IP白名单」:是否已添加服务器公网 IP(不是内网 IP);
  4. 检查 Nginx/Apache 日志:是否有POST /api/pay/notify的 4xx/5xx 记录,确认请求是否到达 Web 服务器。

终极验证:在notify_url路由开头加入fs.writeFileSync('/tmp/wechat_notify.log', JSON.stringify(req.body), 'utf8'),部署后让同事扫码支付,立即查看/tmp/wechat_notify.log是否有内容写入——这是判断请求是否抵达服务端的黄金标准。

本文还有配套的精品资源,点击获取

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

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

立即咨询