微信小程序点餐外卖源码实战:解压配置与微信支付对接

发布时间:2026/9/12 1:25:53
微信小程序点餐外卖源码实战:解压配置与微信支付对接 简介一套完整的微信小程序点餐外卖系统源码面向希望快速上手小程序开发或搭建同类订餐应用的开发者与学习者。资源将前端小程序界面与后端服务逻辑整合在一起涉及菜品浏览、下单支付、订单处理、配送跟踪、评价等常见业务场景既可作为入门学习案例也能作为二次开发基础。资源包共1147个文件压缩后约3.91MB。主要文件类型包括wxml/wxss/js等前端页面逻辑php/html用于后端接口与管理页面png/gif/jpg提供界面与菜品图片素材json/config保存配置数据dat与functions则可能是缓存或功能模块文件。整体目录结构清晰便于按模块学习和调试。目前已有4964人学习下载热度较高。通过学习源码可掌握小程序前后端交互、数据库设计、微信支付集成等关键流程也能参考其代码组织方式快速改造出符合自身需求的外卖点餐小程序。1. 微信小程序点餐外卖完整源码一次解压、三方对接与业务拆解的实战从标题看这个 zip 里装的不是“一个能直接上架的小程序”而是一套“作者自己电脑上能跑的工程快照”。真正落地的点餐外卖小程序至少要同时具备三样东西小程序端 UI 工程、后端下单/支付接口、一份能跑起来的微信支付商户配置。所以下载后第一天要做的并不是打开压缩包找代码而是确认这三样东西分别对应包里的哪个目录、哪份配置文件、哪张数据库表。初次对接的人最常见的卡点有两个一是把“完整源码”理解成“解压即用”导入微信开发者工具后报 AppID 不存在或云环境未创建二是把前后端硬接后端地址还停留在 localhost真机预览直接 request 失败。下面这套路径从 zip 解压开始把项目结构、小程序配置、购物车与下单、微信支付 v3 对接、上线排错的流程完整走一遍重点讲那些作者没写在 README 里的参数。2. 拆开 zip 看结构小程序端、后端、数据库与三方配置拿到微信小程序-点餐外卖小程序完整源码.zip之后先不要双击解压到桌面先在命令行看清单。这样能提前判断包里是原生小程序还是 uniapp 产物以及是否带cloudfunctions目录。zip包本身也是一个文件解压失败往往不在密码而在下载完整性。# 列内容不要先解压 unzip -l ./微信小程序-点餐外卖小程序完整源码.zip | head -40 # 解压到独立目录避免和已有项目混在一起 unzip -O gbk -q ./微信小程序-点餐外卖小程序完整源码.zip -d takeout-appunzip -l先列 zip 内的文件清单可以快速判断包里有没有miniprogram、cloudfunctions、README.md这几类关键目录。-O gbk指定文件名编码解决 Windows 上压缩的中文目录名在 macOS/Linux 下乱码的问题如果命令行版本不支持-O换成图形工具 7-Zip 或 Python 的zipfile处理。-d是解压到目标目录而不是把 zip 复制过去。如果解压时报error read zip archive或End-of-central-directory signature not found说明文件下载不完整或者文件被网页重命名过根本不是有效 zip。先用file看真实类型有些“源码.zip”实际是 rar 或自解压 exe。2.1 小程序目录与 app.json 路由解压后典型目录结构如下takeout-app/ ├── miniprogram/ │ ├── app.js │ ├── app.json │ ├── app.wxss │ ├── pages/ │ │ ├── index/ │ │ ├── menu/ │ │ ├── cart/ │ │ ├── order/ │ │ └── me/ │ ├── components/ │ ├── images/ │ └── utils/ ├── cloudfunctions/ │ ├── login/ │ ├── orderCreate/ │ └── paymentCallback/ ├── project.config.json ├── README.md └── doc/app.json是小程序的“根路由”点餐外卖的核心页面都在这里声明。注意 tabBar 并不是越多越好页面一旦注册为 tabwx.redirectTo就无法跳过去业务跳转只能用switchTab。看源码时先看tabBar.list里放了哪几个页面就能知道作者把“购物车”是做成独立 tab 还是页面内抽屉。{ pages: [ pages/index/index, pages/cart/cart, pages/user/order/list/list, pages/me/me ], tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/user/order/list/list, text: 订单 }, { pagePath: pages/me/me, text: 我的 } ] }, window: { navigationStyle: custom }, style: v2 }这个配置里购物车没有放在 tabBar而是在 menu 页通过弹出层完成减少页面栈切换。navigationStyle改成custom后自定义顶部导航栏需要通过wx.getMenuButtonBoundingClientRect()拿到胶囊位置算出高度。网上很多“微信小程序顶部导航栏高度”脚本在这一步写死44px到刘海屏、灵动岛机型就全乱了。2.2 点餐外卖的数据模型先看表再看代码完整源码往往带着数据库初始化脚本但云开发版没有导出集合结构只能从cloudfunctions里读字段。常见做法是看db.collection(orders).add那段云函数字段表如下集合关键字段说明categories_id, name, sort, status菜品分类前后端用同一排序字段dishes_id, categoryId, name, price, stock, statusprice 用“分”存储避免浮点误差cart一般不入库客户端缓存购物车是会话级状态放库里反而会超时orders_id, userId, orderNo, status, items, totalFeetotalFee 必须服务端计算addresses_id, userId, receiver, phone, detail外卖需要堂食点位不需要点餐外卖的价格字段八成源码用Number保存用19.90这种字符串展示但计算时会遇到浮点问题。靠谱的做法是定义price * 100整数渲染时再除 100便于和微信支付 v3 的“分”对齐。订单号orderNo要生成唯一业务号不能直接用Date.now()因为同一毫秒并发时会撞号。可以自己拼接前缀加时间加随机数或者使用后端分布式 ID。3. 用微信开发者工具把完整源码跑通AppID、基准路径与自定义导航3.1 导入前必须改的三个参数project.config.json里的appid是最常见的坑。作者留下的touristappid或wx123456无法使用云开发登录态也会失败。打开项目前先做三件事去 mp 后台申请一个自己的小程序 AppID找到utils/config.js或app.js里的wx.cloud.init检查cloudfunctions每个函数目录下是否有package.json云函数要单独npm install。// utils/config.js 示例 const env takeout-1a2b3c; // 云开发环境 ID不是 AppID module.exports { baseUrl: https://api.example.com/takeout, // 自建后端时使用 cloudEnv: env, status: { success: 0 } };cloudEnv填的是微信云开发控制台里的“环境 ID”形如takeout-xxxxAppID 是wx开头两个一旦填反登录云函数会返回env not found。baseUrl这一项在云开发版本里用不到但源码如果是“自建后端版”必须改成你自己备案域名下的 HTTPS 地址真机预览不允许 http。3.2 小程序的请求封装与登录态先看登录态。用wx.login换取 code传给云函数 login自建后端版则是拿 code 换openid后签发token。两个版本的区别是云开发版每个云函数通过cloud.getWXContext()直接拿OPENID不需要传 token自建后端版需要每次请求 header 里带 Authorization。// utils/request.js const { baseUrl, status } require(./config); function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${baseUrl}${path}, method, data, header: { Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code status.success) { resolve(res.data.data); } else { // 401 时统一重新登录 if (res.data.code 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/me/me }); } reject(res.data); } }, fail: reject }); }); }这里把 HTTP 状态码和业务码分开处理后端即使返回 200业务逻辑也可能失败。token 过期后用 401 触发重新登录不要每个页面都写一遍判断。调试模式下在工具面板勾选“不校验合法域名”能跳过域名限制但真机预览时这个开关不会被带到手机上仍然需要合法域名。3.3 修改刚进入的加载页顶部导航栏高度与启动跳转很多完整源码会把启动逻辑放在pages/index/index.js但“完整”不代表逻辑正确。进入小程序后先显示哪个页面取决于app.json的pages数组第一项而不是文件目录顺序。如果想让用户先看到一个居中的 logo 加载页再决定去登录还是去首页要单独加一个pages/launch/launch页面并把它放在pages数组第一位。// pages/launch/launch.js Page({ async onLoad() { const token wx.getStorageSync(token); // 先读取本地缓存再决定跳转方式 if (token) { wx.switchTab({ url: /pages/index/index }); } else { wx.redirectTo({ url: /pages/me/me }); } } });注意wx.switchTab只对 tabBar 页面生效redirectTo不适用于 tab 页面所以用switchTab走首页、redirectTo走登录页。启动页千万别在onLoad里做耗时网络请求后再跳转用户会盯着白屏正确做法是请求放在跳转后的页面里做加载页只做路由判断。自定义导航时使用wx.getMenuButtonBoundingClientRect()获取胶囊按钮位置const rect wx.getMenuButtonBoundingClientRect(); const navBarHeight (rect.top - statusBarHeight) * 2 rect.height;这里量出来的是一个具体数值不是所有机型都是 44px。rect.top是胶囊顶边到屏幕顶边的距离statusBarHeight是状态栏高度两者差值就是导航栏上下边距。拿到后把它缓存到全局后续页面直接使用不要再重复计算。4. 点餐外卖业务代码怎么改购物车、下单与订单状态机4.1 购物车页面缓存、规格拆分与 UI 刷新点餐外卖最容易被改坏的代码是 cart。购物车是典型的“跨页面状态”同时涉及app.globalData和wx.setStorageSync。加购时先查是否已存在同样dishId和规格spec的行存在就加数量不存在就新增一行。规格不同不能合并口味也会影响价格。// utils/cart.js function addToCart(dish, options) { const globalData getApp().globalData; const cart globalData.cart || []; const found cart.find(item item.dishId dish._id JSON.stringify(item.spec) JSON.stringify(options.spec) ); if (found) { found.count 1; } else { cart.push({ dishId: dish._id, title: dish.name, price: dish.price, spec: options.spec, count: 1 }); } globalData.cart cart; wx.setStorageSync(cart, cart); // 跨页面保持 }found的判断必须比较JSON.stringify(item.spec) JSON.stringify(options.spec)否则后加的“少冰”会合并到“正常冰”里。setStorageSync写整个 cart 数组数据量不超过 1MB不需要做增量同步。注意globalData变化不会自动触发页面渲染每次加购后要手动setDataPage({ addDish(e) { // ... 调用 addToCart this.setData({ cartNum: getApp().globalData.cart.length }); } });购物车在 menu 页通常只是显示角标到 cart 页才逐条列出角标数量用cart.length而不是 sum(count)用户点了 3 份同一个菜角标应该是 1 还是 3产品定义各不同但代码里要用明确的字段区分。4.2 下单云函数金额重算、库存扣减与订单号生成下单是不能放在客户端的。客户端传来的items只是参考服务端要根据菜品表最新价格重新计算总金额。订单号和支付单号要唯一用前缀 时间戳 随机数。库存扣减最好用事务简单场景用_.inc(-n)但要防止库存变成负数。// cloudfunctions/orderCreate/index.js const cloud require(wx-server-sdk); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); const db cloud.database(); const _ db.command; exports.main async (event) { const { OPENID } cloud.getWXContext(); const { items, address, remark } event; const orderNo TO${Date.now()}${Math.floor(Math.random() * 1000)}; let totalFee 0; for (const item of items) { const res await db.collection(dishes).doc(item.dishId).get(); const dish res.data; if (!dish || dish.stock item.count) { throw new Error(stock-not-enough: ${item.dishId}); } totalFee dish.price * item.count; } const order { orderNo, userId: OPENID, items, totalFee, address, remark, status: PENDING_PAYMENT, createdAt: db.serverDate() }; await db.collection(orders).add({ data: order }); for (const item of items) { await db.collection(dishes) .doc(item.dishId) .update({ data: { stock: _.inc(-item.count) } }); } return { orderNo, totalFee }; };_.inc(-item.count)是原子操作但仍然需要先查一遍库存否则同时下单会超卖更严格的场景要使用数据库事务db.startTransaction()把“查库存、扣库存、创建订单”放在一个事务里。PENDING_PAYMENT表示待支付支付回调后改状态。这里的totalFee单位是分返回给前端用(totalFee / 100).toFixed(2)展示。4.3 订单状态字段与流转规则用整数状态0/1/2/3的写法在点餐外卖里维护成本极高。推荐直接用语义化字符串枚举对接支付回调时一眼能看出状态。状态表状态含义操作方下一状态PENDING_PAYMENT待支付用户支付/15分钟超时PAID/CANCELLEDPAID已支付商家接单ACCEPTEDACCEPTED已接单骑手取餐DELIVERINGDELIVERING配送中用户确认送达COMPLETEDCANCELLED已取消系统/用户终态REFUNDING退款中商户后台REFUNDED状态流转必须只由后端订单状态机驱动小程序端不能直接把PAID改成DELIVERING否则“先送达后接单”会出现在真机上。超过 15 分钟未支付要主动关单调用微信支付关单接口/v3/pay/transactions/out-trade-no/{out_trade_no}/close同时把数据库订单置为CANCELLED否则微信会一直保留待支付订单。5. 微信支付 v3 对接签名头、回调幂等与退款5.1 构造 v3 请求签名很多早期源码还在用 v2 的 MD5 加签方式现在微信支付新商户基本都要走 v3。v3 的签名头是WECHATPAY2-SHA256-RSA2048签名内容由请求方法、请求路径、时间戳、随机串和请求体拼接而成。const crypto require(crypto); function wechatSign({ method, urlPath, body, mchId, serialNo, privateKey }) { const timestamp Math.floor(Date.now() / 1000).toString(); const nonceStr crypto.randomBytes(16).toString(hex); const message ${method}\n${urlPath}\n${timestamp}\n${nonceStr}\n${body}\n; const signature crypto .createSign(RSA-SHA256) .update(message) .sign(privateKey, base64); return { authorization: WECHATPAY2-SHA256-RSA2048 mchid${mchId}, nonce_str${nonceStr},signature${signature}, timestamp${timestamp},serial_no${serialNo}, nonceStr, timestamp }; }urlPath不带 querybody是 JSON 字符串privateKey是商户 API 私钥文件内容而非证书文件。serial_no是上传 API 证书后在商户平台看到的“证书序列号”不是证书内容里的serialNumber。这个签名方法只能放在云函数或自建后端放前端会直接把私钥暴露给用户。5.2 统一下单与支付回调调用 JSAPI 下单接口curl -X POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi \ -H Authorization: $AUTH \ -H Content-Type: application/json \ -d { appid: wx1234567890, mchid: 1620000000, description: 外卖订单, out_trade_no: TO1720000000000123, notify_url: https://api.example.com/wechat/pay/notify, amount: { total: 1990, currency: CNY }, payer: { openid: oOpenIdXXX } }total单位是分不是元out_trade_no要和小程序下单云函数返回的orderNo保持一致。notify_url需要是 HTTPS 公网地址且路径不能带 query。下单成功后会返回prepay_id前端再用wx.requestPayment发起支付支付参数需要后端用同样签名方式生成。5.3 回调幂等与查单支付回调可能被微信重试多次收到回调第一件事不是改订单状态而是先判断当前订单状态。用out_trade_no查数据库如果已经是PAID直接返回成功否则再把状态从PENDING_PAYMENT改成PAID。同时记录transaction_id和回调原始报文。// cloudfunctions/paymentCallback/index.js exports.main async (event) { const { out_trade_no, transaction_id, trade_state } event; const order await db.collection(orders) .where({ orderNo: out_trade_no }).get(); if (order.data.length 0 || order.data[0].status PAID) { return { code: SUCCESS }; } await db.collection(orders).doc(order.data[0]._id).update({ data: { status: PAID, transactionId: transaction_id, paidAt: db.serverDate() } }); return { code: SUCCESS }; };先查重再更新避免并发回调把状态覆盖。不要用trade_state作为唯一判断依据后端还要通过查单接口确认金额一致。如果商户平台显示“由于小程序违规支付功能暂时无法使用”那不是签名问题是账号资格问题需要到 mp 后台处理后再继续。5.4 退款参数与对账退款接口路径是/v3/refund/domestic/refunds请求体里amount对象包含refund和total两个字段。关键参数如下参数取值注意transaction_id微信支付订单号与 out_trade_no 二选一out_trade_no商户订单号优先用这个查库out_refund_no商户退款单号自己生成前后一致amount.refund退款金额分不能超过原订单金额amount.total原订单金额分用于校验reason退款原因会展示给用户注意措辞退款状态是靠回调通知的同一个商户退款单号重复提交会返回原单或错误码开发时要先查单再退款。对账时下载交易账单把微信支付返回的transaction_id和数据库订单逐笔核对发现不一致的订单要触发人工处理。6. 上线前检查与排错zip 源码包的完整性与真机预览完整源码从 zip 落地到真机需要经历导入、编译、预览、上传四步。常见报错和排查顺序是白屏先看 console接口失败先看域名白名单云函数报错先看日志。但有一个容易被忽略的点你拿到的 zip 本身可能不完整。6.1 用命令行验证 zip 完整性zip 不是普通文件夹解压报错很常见。用zip -T测试完整性zip -T ./微信小程序-点餐外卖小程序完整源码.zipzip -T会遍历每个压缩条目并重新计算 CRC如果输出OK但某个文件解压不出来可以用unzip -t指定文件。常见错误error read zip archive是下载不完整需要重新下载invalid zip archive: could not find EOCD是文件被修改过把 rar 后缀改成 zip 也会出现。先file检查类型再解压。6.2 伪加密与密码有一些源码 zip 会在文件头加一个伪加密标志0x09此时命令行解压会提示输入密码但用十六进制编辑器把标志改为0x00后可以直接解出。这不是“破解”只是 zip 压缩包的伪加密开关。遇到真正的密码加密先看压缩包注释和 README很多作者会把密码直接写在 release 说明里。“zip 压缩包密码破解工具”消耗时间且容易误报源码包本身不跑起来没有价值花两小时破解不如自己搭一套模板。6.3 验证完整可编译的小技巧不要直接上传体验版。先解压后执行一次构建再检查project.config.json里是否设置了miniprogramRoot以及cloudfunctions下每个函数是否有package.json。打开微信开发者工具后在详情面板确认基础库版本点餐外卖这种项目太老的基础库会报wx.requestPayment不存在。换新手机调试时一定要把开发工具的“下次编译时清缓存”打开这样可以确定你跑的不是上一份旧代码。本文还有配套的精品资源点击获取