微信小程序点餐系统源码拆解:从WXML到PHP支付回调的完整实现 📅 发布时间:2026/9/16 16:06:08 👁 浏览次数: 简介这是一份基于微信小程序的奶茶咖啡点餐系统完整源码主要面向小程序开发学习者、前端/全栈开发者及有搭建点餐系统需求的技术人员。系统涵盖在线点餐外卖/自取、多门店管理、微信扫码支付、管理后台、物流配送、优惠券/积分/促销等功能前端采用JavaScript与TypeScript结合WXML/WXSS描述页面结构与样式后端基于PHP具备较好的商用级代码组织与扩展性。资源包共2021个文件主要包括1037个JavaScript脚本、362个TypeScript源码文件、349个JSON配置、201个CSS样式、17个WXML模板及16个WXSS样式等整体约22.55MB目录结构清晰配置与依赖文件齐全便于直接阅读、运行调试和二次学习。目前已吸引405人学习下载适合想从项目级源码入手、理解小程序前后端协作与点餐系统设计思路的读者。项目仅限个人学习研究不得用于商业用途。1. 一套带微信支付的奶茶咖啡点餐小程序源码先看它值不值得拆拿到的源码有 4402 个文件前端的 TypeScript 文件超过 1300 个WXML 页面模板和 wxss 样式各有 300 多个服务端还是 PHP。这个体量不像普通课程设计的玩具项目而是一套能把“选择门店 - 加购奶茶/咖啡 - 微信支付 - 外卖配送或到店自取 - 订单状态更新”跑通的小程序点餐系统。它还带了管理后台、优惠券、积分、促销和物流配送适合想看清楚真实餐饮小程序怎么组织代码的人。对做毕业设计或想入门微信小程序开发的同学来说最值得拆的不是界面而是订单状态和支付回调这一条链路。2. 原生小程序前端的结构拆解WXML、WXSS 与 TypeScript 的分工2.1 4402 个文件里先认清楚哪些页面、哪些公共代码拿到源码不要急着导入开发者工具先看目录。前端部分基本是原生微信小程序结构页面由 WXML 描述结构wxss 控制样式TypeScript/JavaScript 写逻辑。WXML 相当于小程序里的 HTML但它不是 HTML没有 div、span组件是 view、text、button、scroll-view 这些。wxss 则负责 px、rpx 的换算和不同机型适配小程序里 750rpx 等于屏幕宽度设计稿按这个换算可以减少适配时间。从项目文件组成来看各类型文件的数量大概是这样文件类型数量在点餐系统里的职责TypeScript1324页面逻辑、API 封装、类型定义、工具函数JavaScript1037配置文件、依赖脚本或未迁移到 TS 的模块WXML328门店列表、商品卡片、购物车、订单页等页面结构wxss321卡片布局、按钮状态、弹层样式、底部 tab 样式PHP 相关文件若干服务端接口、订单处理、支付回调、后台管理这里有一点要注意TypeScript 数量很多是因为小程序原生支持 TS 后类型定义文件也被统计进来你看到的 xxx.d.ts 也占了一部分这不代表页面真的多到 1300 个。项目里还有 package-lock.json 和 yarn.lock说明前端依赖做了锁定复制到其他机器安装时不容易出现依赖版本不一致的问题。2.2 WXML 模板门店卡片与加购按钮怎么渲染点餐首页第一个核心模块是门店列表。WXML 里一般用 wx:for 循环渲染所有门店再通过 wx:if 控制“营业中/休息中”的状态展示。下面是一段典型结构view classstore-list view wx:for{{storeList}} wx:keyid classstore-card {{item.status open ? store-open : store-closed}} bindtaponSelectStore >// pages/index/index.ts import { fetchStores } from ../../services/store interface LatLng { lat: number lng: number } interface StoreItem { id: string name: string distance: number address: string status: open | closed } Page({ data: { storeList: [] as StoreItem[], currentStoreId: , cartCount: 0 }, async onLoad() { const location: LatLng { lat: 31.2304, lng: 121.4737 } const res await fetchStores(location.lat, location.lng) this.setData({ storeList: res.data }) }, onSelectStore(e: WechatMiniprogram.TouchEvent) { const id e.currentTarget.dataset.id as string this.setData({ currentStoreId: id }) }, addToCart(e: WechatMiniprogram.TouchEvent) { const { id } e.currentTarget.dataset const current this.data.cartCount this.setData({ cartCount: current 1 }) // 真正项目里还会把 skuId 和数量发给服务端预校验 } })代码里有两个容易忽略的细节。fetchStores 返回的是 Promise在 onLoad 里用 await 很直观但小程序老版本基础库对 async/await 的支持有兼容问题源码里的 tsconfig 一般会做 target 转换。另一个是 setData 不要把整个 storeList 重新传一遍只传变化的字段例如 cartCount否则列表量大时会有明显掉帧。门店距离通常不是前端算的而是后端根据经纬度算出 distance再按距离排序。前端传输经纬度时微信小程序定位接口会返回精度、纬度注意转成 number 类型避免字符串拼接导致 bug。2.4 页面栈设计进入点餐页、商品页、结算页的常见衔接小程序一个很重要的概念是页面栈。用户打开小程序先进的是 app.json 里 pages 数组第一个页面如果这个项目把启动页设置为“门店选择页”那么后续跳转会有两条路线一是 navigateTo 压栈进入商品列表页二是通过 switchTab 切到订单页。navigateTo 可以保留上一页状态适合从门店进入菜单而支付完成后通常用 redirectTo 替换结算页避免用户点返回又回到未支付状态。这里也和许多新手误解有关微信小程序顶部导航栏高度不是固定值不同机型胶囊按钮位置不同源码 wxss 里经常能看到通过 padding-top 适配。如果你要修改刚进入的加载页面直接改 app.json 的 pages 数组第一项而不是去改某个组件。加载页可以放一个临时 logo再通过 wx.redirectTo 跳到真正的门店选择页。3. PHP 服务端与多门店、微信扫码支付、物流状态如何联动3.1 为什么选择 PHP 做订单服务端项目摘要里写得很清楚这是基于 PHP 开发的点餐系统。很多人看到 PHP 的第一反应是“老”但在餐饮订单这类场景里PHP 的开发和部署效率比大多数后端语言更直接。Nginx PHP-FPM 几乎在所有云服务器上一键就能装好MySQL 读写用 PDO 就能完成相比 Java 的 Spring Boot 或 Node 的 NestJS学习成本和服务器资源占用都低很多。本项目的管理后台和接口大概率是用 PHP 写的前端小程序通过 request 调这些接口完成数据交换。不是所有项目都需要微服务。单店或几十个门店的奶茶咖啡点餐订单写入量没那么高PHP 做同步请求、MySQL 做事务足够支撑小规模连锁。源码里大量 JS/TS 是前端编译产物和依赖后端 PHP 文件可能集中在 api、admin 这类目录。注意无论怎么改微信支付商户号必须是小程序对应主体申请的否则真机拉起支付会直接报错这一点和 PHP 代码本身无关。3.2 门店、菜品、订单的数据库模型怎么组织多门店多门店不是把门店表里加个字段就行关键在于菜品和门店是多对多关系。一个连锁品牌可以存在总部菜品库但每个门店的“是否售卖”“库存”“价格”可以不同。所以常见设计是四张基础表门店表store、菜品表product、门店菜品关联表store_product、订单表orders。同时订单明细表order_item用于记录下单那一刻的快照避免后来菜品改价影响历史订单。以下是一段核心表的简化结构CREATE TABLE store_product ( id INT PRIMARY KEY AUTO_INCREMENT, store_id INT NOT NULL, product_id INT NOT NULL, price_cents INT NOT NULL, stock_count INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, UNIQUE KEY uk_store_product (store_id, product_id) ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, store_id INT NOT NULL, user_openid VARCHAR(64) NOT NULL, take_type TINYINT NOT NULL COMMENT 1外卖 2到店自取, total_fee_cents INT NOT NULL, discount_cents INT NOT NULL DEFAULT 0, coupon_id INT DEFAULT NULL, points_deduct_cents INT NOT NULL DEFAULT 0, pay_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已退款, status TINYINT NOT NULL DEFAULT 0 COMMENT 0已下单 1制作中 2待取/配送中 3已完成, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );price_cents 用整数“分”存储而不是 float这是订单系统最基本的一条原则能避免浮点精度导致金额出错。order_no 由 PHP 生成一般带日期时间再加随机数确保并发下唯一。可以看到订单表里同时有 take_type、pay_status、status分别表示配送方式、支付状态和业务状态三者不能混在一起。3.3 小程序微信支付 v3下单接口到 wx.requestPayment微信生态里的“微信扫码支付”体现在两个地方一是在电脑上扫码拉起小程序二是小程序内支付成功后展示支付凭证。本项目点餐流程更常见的是后端生成收款订单前端拿到支付参数后调用微信支付。小程序端支付整体流程是前端先调自己后端的下单接口把商品明细、门店、自取/外卖、优惠计算都提交到服务端服务端生成订单后调用微信支付 v3 的 JSAPI 下单接口拿到预支付单再通过支付签名算法生成 paySign 等参数返回给小程序小程序端用 wx.requestPayment 拉起收银台。一定要把金额计算放在服务端不能信任前端传的 total_fee。下面是用 PHP 调用微信支付 v3 创建 JSAPI 订单的简化代码这里的 SDK 命名是示意具体类名以项目自动加载为准// services/PayService.php $payService new WechatPayV3([ merchant_id $config[mchid], serial_no $config[serial_no], private_key $config[apiclient_key], app_id $config[appid], ]); $resp $payService-createJsapiOrder([ description 奶茶咖啡点餐- . $storeName, out_trade_no $orderNo, notify_url $config[base_url] . /api/pay/notify, amount [total $totalFen, currency CNY], payer [openid $userOpenid], ]); // 返回给小程序前端的参数 $result [ timeStamp (string) time(), nonceStr uniqid(), package prepay_id . $resp-prepay_id, signType RSA, ]; $result[paySign] $payService-sign($result);这里的 total 单位是分不是元。createJsapiOrder 返回的 prepay_id 有效期一般是两个小时以内所以支付页面停留太久要重新拉起。paySign 使用 RSA 加密签名千万不要自己拼接字符串时漏掉字段。支付回调是另一个关键点。用户付完款微信服务器会异步 POST 通知到 notify_url你的 PHP 必须在回调里校验签名、核对订单号与金额然后更新订单状态为已支付。很多同学调试支付时遇到“小程序微信支付 v3 对接失败”通常不是算法问题而是回调 URL 没有外网可达或者商户密钥没配对。3.4 优惠券、积分、促销的计算顺序优惠叠加的顺序直接决定实付金额。以奶茶点餐为例最稳妥的做法是原价合计 - 活动满减如满30减5- 优惠券抵扣 - 积分抵扣 - 计算配送费 - 实付金额。每一步都必须记录到订单表不能只在展示层算。计算阶段数据来源存储字段商品原价合计store_product.price_centstotal_fee_cents满减促销promotion_rulediscount_cents优惠券抵扣user_coupondiscount_cents积分抵扣user_pointspoints_deduct_cents配送费门店配送配置delivery_fee_cents实付金额前面结果累加pay_fee_cents优惠券在用户下单时应该先锁定支付失败后再释放否则用户反复发起支付会消耗多张券。积分抵扣一般是精确到分并且需要校验当前积分余额不能用前端传入的积分数量。促销规则尽量放到 PHP 或后台可配置不要写死在小程序里。4. 把整套源码跑起来微信开发者工具、PHP 环境与配置修改顺序4.1 导入前先做依赖检查和目录识别拿到源码压缩包先解压看根目录有没有 package.json、project.config.json、yarn.lock。有这些文件说明前端可能有构建流程或 npm 依赖。建议进入目录先根据锁文件安装依赖yarn install # 或者 npm install然后在微信开发者工具里选择“导入项目”把目录指向 project.config.json 所在的根目录。project.config.json 里的 miniprogramRoot 指定了小程序前端目录如果不是根目录工具会按这个字段进入正确目录。导入后先编译一次看模拟器是否到达启动页。依赖装完不等于能跑通业务。还要确认 PHP 服务端所在目录通常是 api/、server/ 或根目录放 index.php。查看是否有 .env.example 之类文件复制成 .env填入数据库账户密码。项目里的 SQL 导入顺序也要注意先建库再导表部分源码会带 seed 测试数据用于门店和菜品初始化。4.2 必须修改的核心配置项配置位置字段作用常见问题project.config.jsonappid小程序签名标识不换 AppID预览和真机调试受限miniprogram/app.jsglobalData.apiBaseUrl请求的 PHP 接口地址本地联调要改成 http://127.0.0.1:8080服务端 .envdatabase/mysql 配置数据库连接连不上时报 PDOException服务端支付配置mchid、apiclient_key微信商户号密钥回调验签失败微信公众平台request/uploadFile 合法域名小程序线上访问域名非 https 域名直接拦掉微信开发者工具默认开启 urlCheck会校验请求域名是否为合法 https 域名。本地调试阶段可以在项目设置里勾掉“校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”或者把 urlCheck 改为 false这样请求 http://127.0.0.1 才会放行。真机上也要在开发工具里打开“不校验合法域名”。4.3 本地 PHP 接口环境Nginx PHP-FPM在 Ubuntu 上部署本地接口环境的步骤基本是固定的sudo apt update sudo apt install -y nginx php-fpm php-mysql php-curl unzip sudo systemctl start nginx php-fpm然后修改 Nginx 站点配置把 root 指向 PHP 项目入口目录并配置 PHP-FPM 转发server { listen 8080; server_name localhost; root /var/www/milk-tea-order/api; index index.php index.html; location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; } }配置完成后在项目目录里创建 phpinfo 测试文件浏览器访问http://127.0.0.1:8080能看到页面就说明 PHP 环境没问题。然后导入数据库修改 .env 里的数据库地址和账号再测试一个门店列表接口确认能返回 JSON。这样后面小程序端联调时请求链路才是通的。4.4 常见启动报错和排查顺序如果你导入后模拟器白屏或一直在 loading先打开调试器的 Console 面板。最常见的三类问题第一类是 appid 缺失或格式不对微信开发者工具会直接报 project.config.json 错误。第二类是接口请求 failed本地联调时先看 network 面板请求是否发出再用浏览器单独访问一次接口确认 PHP 是否返回。第三类是“支付功能暂时无法使用”或者点击支付无反应说明商户号没有正确配置、小程序没有开通微信支付或者当前真机微信号不是该小程序的体验成员。修改进入时看到的加载页面并不需要改代码逻辑而是直接看 app.json 的 pages 数组。数组第一位就是小程序冷启动加载的第一个页面把门店选择页或首页放第一位即可。如果页面里用了 wx.login要注意开发者工具里测试时返回的 code 不一定能在服务端正常换取 openid真机调试更接近正式结果。5. 改成“可检查的毕设 demo”支付回调 mock、多门店数据与验收清单5.1 先加一个演示模式开关很多拿到源码的人卡在支付环节因为没有商户号。验收时也不一定需要真的扣款可以加演示模式让小程序端在支付按钮触发后直接进入已支付状态同时服务端也返回 mock 回调。// miniprogram/config/env.js module.exports { demoMode: true, mockPay: true, apiBaseUrl: http://127.0.0.1:8080/api }在支付函数里判断 mockPay为 true 时不调用 wx.requestPayment而是直接调用后端/mock/pay/success接口把订单号、支付时间写入订单表。这样演示时流程完整又不依赖真实微信支付权限。5.2 造多门店测试数据多门店演示需要每个门店都有独立菜品最小数据集可以造三家店一家总店营业中一家分店休息中一家分店营业中但部分菜品没有库存。这样评审时能展示门店切换、库存不足、售罄置灰几个状态。插入语句参考INSERT INTO store (name, address, lng, lat, status) VALUES (人民广场店, 黄浦区南京东路100号, 121.477, 31.235, 1), (陆家嘴店, 浦东新区陆家嘴环路500号, 121.505, 31.239, 0), (静安寺店, 静安区南京西路1600号, 121.445, 31.223, 1);5.3 验收时沿着订单主链路过一遍验收清单用表格写清楚比临场演示更可信步骤操作预期结果检查重点冷启动打开小程序进入门店选择页启动页顺序切换门店点击门店卡片商品列表随门店变化库存与状态联动加购选择奶茶加购购物车角标更新setData 性能优惠券下单页勾选优惠券金额重新计算叠加顺序提交订单外卖/自取提交生成订单号数据库入库支付真实支付或 mock 支付回调后订单状态变为已支付回调验签订单进度查看订单详情状态流转到制作/配送中状态机边界如果支付回调走真机验证建议同时在服务端打开日志输出回调原文和验签结果。这样改完后沿着表里链路走一遍订单能在回调后自动变成已支付整套源码就算真正接手成功了。本文还有配套的精品资源点击获取