ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

微信扫码点餐小程序开发实战:从数据库到支付全流程

微信扫码点餐小程序开发实战:从数据库到支付全流程 简介基于微信平台的的点餐系统小程序完整源码适合小程序开发者、餐饮行业技术人员以及需要课程设计或毕业设计项目的学生参考。资源围绕前端界面、菜品展示、购物车、订单管理、后端服务、数据库管理、微信API集成、订单处理、支付功能、用户管理、菜品管理、数据统计等模块展开较为完整地覆盖了点餐系统从开发到上线的关键环节。包体共1343个文件约14.45MB主要包括png图片素材、js与vue前端逻辑、java后端接口、wxml与wxss小程序页面、json配置文件以及sql数据库脚本等目录结构清晰便于按模块查阅。已有1282人学习下载适合用来快速理解点餐小程序的技术架构也可基于此进行二次开发或改造学习。资源包含前后端完整源码、数据库初始化脚本和项目构建相关文件能帮助读者系统掌握微信小程序点餐系统的整体设计思路与实现方法。 第一次给朋友的餐饮店做点餐小程序时我还在“能用”和“好用”之间来回折腾。后来去店里蹲了一下午看着服务员拿着点菜宝来回跑我才明白点餐系统的核心不是把菜单塞进手机里而是把“选菜、加购、下单、支付、接单”这条链路走顺。微信小程序刚好把这个闭环串起来了——顾客扫桌码进店点餐不用装App、不用关注公众号商家后台看单接单支付环节由微信支付原声能力承接。这个项目类型也特别适合小程序开发者练手和接私活前端有交互后端有数据建模中间还夹着支付对接这种硬骨头。下面把我开发一套可用的扫码点餐小程序的完整思路和踩坑记录写出来源码结构和关键代码都会贴出来这套东西拿去改门店信息就能部署。1. 为什么点餐系统是微信小程序里最适合练手和变现的项目1.1 微信生态天然解决了点餐的三个痛点我之前想过用网页H5做点餐但实际操作下来问题不少顾客要在浏览器里打开链接还要经过登录、授权、支付一通操作每一步都可能流失用户。做成iOS和Android双端App更不现实餐饮店老板不可能为了点餐单独给店里配几个App。微信小程序的扫码即用刚好命中场景顾客到店扫一下桌码小程序直接拉起首页就是菜单没有任何安装成本。第二个痛点是支付。微信小程序里接入微信支付的流程比H5干净很多用户点击支付直接调起原生支付界面体验顺滑而且不用像公众号H5那样折腾JSAPI支付授权回调。第三个痛点是消息触达。顾客下单后商家需要第一时间知道小程序订阅消息可以做到“下单通知到店长微信”虽然订阅消息有一次性订阅的限制但比短信通知便宜太多。1.2 技术选型云开发还是自建后端刚开始做的时候我直接在微信云开发上跑了一套原型体验是真的快前端调用云函数数据库直接用云数据库一个下午就能跑通“点菜、下单、支付”的流程。但如果要做成能商用、能接住实际门店需求的东西我还是建议自建轻量后端。原因有三点第一餐饮店大概率要接后厨小票打印机云开发的云函数做打印机协议对接很别扭而自建后端可以常驻进程轮询打印机状态也更顺手第二商家后台通常需要在PC浏览器上操作云开发的数据权限模型对这类后台需求不够灵活第三自建后端意味着数据库、接口设计都掌握在自己手里后续要多门店、多端扩展都不受限制。我实际用的是Node.js MySQL这套组合小程序端原生开发。你不用纠结语言接口设计逻辑是一样的后端换Spring Boot、Go也完全没问题。这套系统我拆成了三个部分小程序端顾客扫码后进入完成选餐、购物车、下单、支付、查订单商家管理端跑在浏览器里的H5完成菜品上下架、分类管理、订单接单、桌台管理后端API所有数据交互和微信支付调用的统一出口。2. 从数据表到页面点餐系统的功能边界与数据结构2.1 功能边界第一版只碰“点单上菜”给餐饮店做系统最怕功能越堆越多。第一版我只保留了核心链路顾客端是扫码进店、浏览分类菜单、加购、购物车、提交订单、微信支付、查看订单状态商家端是菜品分类管理、菜品上架下架、订单列表、订单接单操作、桌台二维码管理。会员储值、外卖配送、优惠券、后厨打印机这些我建议放到第二版。原因很简单MVP阶段的重点是跑通“点单-支付-接单”如果一上来就接打印机协议、对接外卖平台项目周期会拖得很长老板很容易失去耐心。打印机可以在第二版通过后端轮询的方式补上不影响核心点餐业务。2.2 六张核心数据表的设计与为什么这么设计点餐系统的数据模型不复杂但每一张表都有自己的讲究-- 门店表 CREATE TABLE shop ( id BIGINT PRIMARY KEY, name VARCHAR(64) NOT NULL, address VARCHAR(255), notice VARCHAR(255) COMMENT 门店公告, status TINYINT DEFAULT 1 ); -- 菜品分类表 CREATE TABLE dish_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, shop_id BIGINT NOT NULL, name VARCHAR(32) NOT NULL, sort INT DEFAULT 0, UNIQUE KEY uk_shop_name(shop_id, name) ); -- 菜品表 CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, shop_id BIGINT NOT NULL, name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(255), stock INT DEFAULT 999 COMMENT 库存-1表示不限量, status TINYINT DEFAULT 1 COMMENT 1上架 0下架 ); -- 订单表 CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, shop_id BIGINT NOT NULL, table_no VARCHAR(16) COMMENT 桌号, openid VARCHAR(64) NOT NULL COMMENT 顾客openid, total_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 10 COMMENT 0已取消 10待支付 20已支付 30已接单 40已完成, remark VARCHAR(255), pay_time DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, dish_id BIGINT NOT NULL, dish_name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, count INT NOT NULL );用户表我只保留了一个最小字段集openid、昵称、头像。对于点餐系统来说用户身份以openid为唯一关联键就够了没必要做完整的账号体系。订单明细表里冗余了dish_name和price字段这是不少新手容易忽略的点。菜品名称和价格随时可能在后台被修改如果订单明细里去查菜品表历史订单显示的菜名和价格就会跟着变对账和顾客展示都会出问题。把采买时候的快照存进明细表订单永远显示下单那一刻的真实数据。3. 小程序端开发实录菜单交互、购物车状态和导航栏适配3.1 分类菜单与商品列表的联动实现小程序端最核心的页面是点餐页由一个左分类右菜品的结构组成。分类列表用scroll-view做纵向滚动点击分类时右侧菜品列表滚动到对应分组右侧滚动时自动高亮当前分类。这个交互实现起来不复杂关键是数据组织方式小程序的setData性能有限不要把分类和菜品分开传最好一次传一棵树状数据。// 数据结构示例 data: { categories: [ { id: 1, name: 招牌菜, dishes: [{ id: 101, name: 酸菜鱼, price: 58.00 }] }, { id: 2, name: 凉菜, dishes: [{ id: 201, name: 拍黄瓜, price: 12.00 }] } ], cartList: [], totalCount: 0, totalPrice: 0.00 } addToCart(e) { const dish e.currentTarget.dataset.dish const cartList this.data.cartList.slice() const existed cartList.find(item item.dishId dish.id) if (existed) { existed.count 1 } else { cartList.push({ dishId: dish.id, name: dish.name, price: dish.price, count: 1 }) } const total cartList.reduce((acc, cur) acc cur.price * cur.count, 0) this.setData({ cartList, totalCount: cartList.reduce((acc, cur) acc cur.count, 0), totalPrice: total.toFixed(2) }) }这里有个关键点购物车的商品信息需要把name和price一起存进来不能只存dishId。购物车在顾客加购之后、下单之前还要展示给顾客看如果每次展示都去请求菜品详情既慢又会增加接口压力而且菜品如果正好被商家下架购物车数据就拉不到了。购物车里的快照只影响本次点餐最终下单仍然以后端校验为准。3.2 页面动态标题一个常被忽略但很实用的小细节点餐系统经常是多门店复用同一套代码顾客扫码进入不同门店导航栏上显示的门店名也应该不同。这个需求用wx.setNavigationBarTitle就能处理不需要单独开发页面。wx.setNavigationBarTitle({ title: shopInfo.name })看起来很基础但实际业务里有个注意点如果顾客从A门店的小程序码进入再切换到B门店页面标题要跟着变。我习惯把门店信息放在全局store里每次点餐页onShow的时候重新读取一次而不是只在onLoad里设置这样能避免扫码进入时参数还没解析完、标题已经过早设置的问题。3.3 自定义导航栏高度适配点餐页为了视觉效果通常要自定义顶部导航栏把门店背景图延伸进状态栏。这时候就涉及热词里提到的“顶部导航栏高度”问题。const systemInfo wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync() const menuButton wx.getMenuButtonBoundingClientRect() const statusBarHeight systemInfo.statusBarHeight const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height这个公式里的关键是menuButton右上角胶囊按钮的位置。胶囊按钮垂直居中于导航栏所以用胶囊按钮到状态栏的距离乘以2再加上胶囊自身高度就是导航栏的总高度。这个适配在iPhone的刘海屏和安卓水滴屏上表现是不一样的写死数值必翻车。把statusBarHeight和navBarHeight算出来之后给自定义导航栏设置相同高度再往页面内容区加对应的padding-top就能避免内容被刘海遮挡。3.4 请求封装token失效自动静默登录小程序的登录状态有一个特点用户在微信里打开小程序后可能长时间挂在后台不关闭token很容易过期。点餐下单的时候请求返回401用户此时已经用完餐准备结账突然报错体验非常差。我在utils/request.js里封装了一层对所有返回401的请求做统一处理先用wx.login拿到code传给后端换新token然后重发原请求。这样用户无感知地重新完成了登录。const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Authorization: Bearer ${getToken()} }, success: async (res) { if (res.statusCode 401) { await silentLogin() resolve(await request(url, method, data)) return } resolve(res.data) } }) }) }4. 微信支付对接与订单状态流转最容易翻车的环节4.1 支付流程前端绝不碰签名逻辑点餐系统的支付链路是这样走的小程序前端点“提交订单”后把门店、桌号、菜品列表发给后端后端校验库存和价格生成待支付订单然后调用微信支付统一下单接口后端拿返回的prepay_id按微信支付协议生成前端需要的5个参数timeStamp、nonceStr、package、signType、paySign返回给小程序端前端拿到参数后调用wx.requestPayment拉起微信支付。签名逻辑必须放在后端这是铁律。如果把商户号和APIv3密钥放在前端小程序打出的包反编译一下密钥就全泄露了。我见过有人图方便在云函数里调微信支付SDK时不设think结果商户密钥被捞走的案例不要心存侥幸。4.2 支付V3对接找不到可用平台证书的解决办法热词里那个“小程序微信支付v3对接 无可用的平台证书”的报错我头一回对接V3时也撞上了。这个报错不是真的没有证书而是程序找不到或者加载不了商户平台证书文件。常见原因有两个第一在商户平台下载的API证书和配置的商户私钥不匹配证书文件路径写错了导致SDK加载证书时直接抛异常第二许多新版官方SDK默认使用“微信支付公钥”模式验签但配置里还保留着旧式平台证书的路径两者冲突。解决办法是按新的公钥模式配置在商户平台申请微信支付公钥拿到公钥ID和公钥内容再把商户私钥配置到位。这样既不用操心平台证书的定期轮换也不容易再报“无可用的平台证书”。另外提醒一句商户号和小程序AppID一定要先在商户平台完成绑定否则接口会直接拒绝调用。4.3 支付回调处理验签、解密、幂等支付结果以后端收到的回调为准不能只信wx.requestPayment的success回调。用户支付成功后微信支付会异步POST一条通知到你在商户平台配置的回调URL后端要做三件事验证签名、解密支付信息、修改订单状态。解密后拿到商户订单号order_no先查订单当前状态如果已经是“已支付”直接返回成功给微信支付不再重复处理。支付回调的重复投递是正常现象不做幂等处理的话很容易出现一个订单被重复修改状态、甚至重复发订阅消息的问题。async function handlePayNotify(rawBody) { const decrypted await decryptNotify(rawBody) const order await Order.findByOrderNo(decrypted.out_trade_no) if (order.status 20) return { code: SUCCESS } await Order.updateStatusByOrderNo(decrypted.out_trade_no, 20) return { code: SUCCESS } }订单状态流转我是这么设计的10待支付20已支付30已接单40已完成0已取消。顾客支付成功后状态变20商家在后台点“接单”状态变30顾客确认吃完或商家点“完成”状态变40。超时未支付的订单由后端定时任务统一关单状态置0并调用微信支付的关单接口。这个状态机看着简单但能撑住绝大多数门店的业务。5. 真机运行后踩过的坑与源码跑通指南5.1 扫码进入场景scene参数是最容易出问题的环节扫码点餐的核心是顾客扫桌上的码直接进到对应桌台。我建议用小程序码类似二维码但带scene参数的码而不是普通微信二维码。通过wx.scanCode或者直接扫小程序码进入后场景值在onLoad的options.scene里。这里有一个坑scene参数必须经过encodeURIComponent编码长度也有限制直接放明文中文比如桌号“A区03桌”到了小程序端会解码失败。我的做法是scene只放tableId这样的数字编号门店信息由tableId在后端关联出来这样既稳定又安全。另一个坑是安卓和iOS对扫码进入的时序处理不同有的机型在onLoad时options.scene还没完全解析出来需要在onShow里再兜底读一次用标志位防止重复初始化购物车。5.2 重复下单和购物车跨页同步顾客在点餐页加购后如果又进入菜品详情页返回时购物车角标必须还是对的。这是页面栈的问题点餐页必须在onShow里重新拉一次购物车状态而不是依赖页面保留的data。购物车状态我放在全局store里页面onShow同步一次这样不管从哪个页面返回角标都是准的。重复下单的坑更现实。支付收银台还在转圈顾客手快又点了一次“提交订单”结果后台生成了两笔待支付订单。解决方案有两层前端在提交按钮点击后加loading状态和disabeld禁用后端在接收到下单请求时用tableId 桌号 菜品hash比较最近一分钟内是否有相同订单有就直接返回已有订单防止重复生成。5.3 抓包调试用开发者工具和vConsole就够有段时间我排查支付回调参数问题看到网上教程都是用burp suite或者Charles抓包微信小程序。这类方案涉及安装证书和代理设置真机调试时比较麻烦而且容易误伤其他请求。我自己的经验是小程序端的网络请求先用微信开发者工具自带的Network面板信息已经足够详细真机上需要看请求细节时用vConsole这个轻量方案在移动端页面里直接看log和网络请求不用把手机代理到电脑上抓包安全也省事。5.4 源码跑通指南拿到项目源码后按下面的步骤操作一小时左右能在真机上跑通在微信公众平台注册一个小程序账号拿到AppID下载微信开发者工具导入项目根目录填入自己的AppID初始化MySQL数据库执行项目里sql目录的建表脚本改好连接配置启动后端服务确认接口能通修改小程序端config.js里的baseURL本地开发时可以在开发者工具里勾选“不校验合法域名”编译运行手机扫码预览如果要用支付功能必须把配置里的合法request域名换成已备案且带HTTPS证书的域名同时商户平台的开发配置里绑定小程序AppID。下面是几个我实测最常见的启动问题报错现象可能原因解决办法调用接口报URL域名不合法request合法域名未配置小程序后台配置合法域名必须是HTTPS登录接口返回401token过期或openid为空检查后端日志确认wx.login的code2Session调用成功提交订单报菜品不存在菜品表没有初始化数据执行菜品种子数据SQL确认shop_id正确支付时提示商户号与AppID不匹配商户平台未绑定小程序商户平台-产品中心-AppID账号管理里绑定回调收不到回调URL不能是内网或本地地址部署到公网服务器回调URL换成线上地址我建议第一遍跑通时先把支付动态放到开发者工具模拟器上验证确认整个下单链路没问题再部署到公网真机测支付。支付环节一旦出问题后端日志一定要打开特别是回调通知处理部分很多问题靠看日志比靠一遍遍点支付更高效。最后再分享一个我在部署时候的习惯商品图片一定压缩到80KB以内再上传第一次我没注意菜单加载时图片一个接一个转圈顾客体验很差。后来在商家端统一做了一遍图片压缩菜单秒开这个细节对点餐系统来说比什么花哨功能都更重要。总的来说这套系统做到现在稳定撑住了几家门店的日常运营核心还是因为数据结构和订单状态的边界划得清楚。你拿到源码后建议先跑通再改改的时候优先扩展菜品属性和打印机对接这两个方向是餐饮店老板最愿意付钱的功能。本文还有配套的精品资源点击获取
返回列表