
简介外卖CPS优惠券小程序平台v3.0源码包内含完整项目代码与搭建说明面向计划快速搭建外卖返利小程序的开发者、创业者也适合想学习微信小程序实际商业项目的进阶学习者。平台基于微信小程序实现CPS分成模式用户领券后跳转外卖平台下单商家按实际成交支付佣金源码覆盖用户端、订单处理、优惠券管理、佣金结算等模块业务链路完整。资源共453个文件、约19.92MB其中JS实现接口逻辑Vue与WXML/WXSS构建界面JSON保存配置参数另有字体和PNG/JPG图片素材并附配置订阅信息文档方便部署参考。目前已有581人学习下载。价值上源码包含完整的前端页面与后端逻辑并区分贡献版和云码之家版两个版本可直接部署测试也可结合两者优势进行二次开发扩展分销、会员或营销功能搭建说明对服务器环境、数据库配置及小程序授权等环节的梳理也能明显降低部署门槛。1. 外卖CPS优惠券小程序的赚钱逻辑与v3.0源码定位做外卖CPS优惠券项目的人很多但真正把“领券跳转-订单回流-佣金结算”这条链路跑通的人不多。这套v3.0源码的价值在于它把微信小程序的用户端、服务端的佣金计算、后台的订单管理打包成了一整套可部署的系统而不是网上那些只给前端页面、后端全靠自己补的残缺demo。拿到源码后配合搭建说明里的环境要求理论上一个熟悉PHP和微信开发者工具的工程师可以在半天内把它跑起来。它适合两类人一类是想快速上线一个外卖返利小程序做流量变现的运营者另一类是想研究CPS模式在微信生态内如何落地、二次开发成自己产品的开发者。2. 微信小程序CPS平台的技术栈与目录结构拆解2.1 小程序前端WXML/WXSS/JS三层架构先看小程序端。v3.0这套源码的前端部分遵循微信小程序的标准三层结构WXML负责页面骨架WXSS负责样式JavaScript负责交互逻辑和网络请求。拿到源码后第一件事不是急着改界面而是先梳理pages目录下每个页面的职责否则后期改需求时会在页面跳转关系上绕晕。以优惠券列表页为例典型的目录结构是pages/ ├── index/ # 首页展示外卖优惠券列表 │ ├── index.wxml │ ├── index.wxss │ ├── index.js │ └── index.json ├── order/ # 订单列表页展示用户通过小程序下单的记录 ├── user/ # 个人中心展示用户收益、提现入口 └── webview/ # 用于跳转到外卖平台H5领券页WXML里用block wx:for{{couponList}}循环渲染券列表每个券位绑定>// utils/request.js 简化版 const request (url, method GET, data {}) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url: https://your-domain.com${url}, method, data, header: { Authorization: Bearer ${token} }, success: (res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); reject(res.data); return; } resolve(res.data); }, fail: reject }); }); };这段代码的核心在于把鉴权逻辑收敛到单点。wx.getStorageSync(token)从本地缓存取登录凭证如果接口返回401就统一跳转登录页而不是在每个业务接口里分散处理。实际部署时这个your-domain.com要替换成你自己的HTTPS域名微信小程序要求所有请求域名必须在小程序后台配置为合法域名否则开发工具里直接报request:fail url not in domain list。2.2 后端接口与数据库表的对应关系后端是典型的PHP接口层加MySQL存储。v3.0源码的服务端目录里application/api/controller下按模块划分控制器Coupon.php、Order.php、User.php、Withdraw.php。每个控制器对应一张或几张数据库表。核心表结构如下表名用途关键字段cps_coupon优惠券活动配置id,title,discount,jump_url,statuscps_order用户下单回传的订单记录id,user_id,order_sn,commission,statuscps_user小程序用户id,openid,nickname,balancecps_withdraw提现申请id,user_id,amount,status订单表里的commission字段是CPS模式的核心。它的值不是小程序端计算出来的而是由外卖平台的联盟接口异步回调写入。源码里Order.php控制器有一个notify()方法专门接收平台回调验签后更新订单状态和用户余额。这里的关键是验签逻辑——如果没做签名校验任何人都可以伪造回调请求往自己账户里加钱。v3.0源码用的是MD5加盐方式盐值配置在application/extra/config.php里。// application/api/controller/Order.php public function notify() { $data input(post.); $sign $data[sign]; // 按参数名ASCII码排序后拼接盐值 unset($data[sign]); ksort($data); $str urldecode(http_build_query($data)) . key . config(cps_key); if (md5($str) ! $sign) { return json([code 0, msg sign error]); } // 验签通过后处理订单 $order OrderModel::where(order_sn, $data[order_sn])-find(); if ($order $order[status] 0) { $order-status 1; $order-commission $data[commission]; $order-save(); // 给用户加余额 UserModel::where(id, $order[user_id])-setInc(balance, $data[commission]); } return json([code 1]); }这个notify方法做了两件关键事第一步验签用参数拼接加盐后的MD5值比对第二步幂等处理通过status 0的条件确保同一订单回调多次不重复加钱。实际对接时外卖联盟平台的回调格式可能不是http_build_query格式需要按平台文档调整参数解析方式但验签和幂等的思路是通用的。2.3 佣金结算模块的核心流程CPS佣金结算不是用户下单立刻到账的v3.0源码里设计了一个延迟结算机制。订单状态有三个值0待确认、1已确认、2已结算。当用户通过小程序领券下单后平台回调先写入订单为0等订单过了退货周期通常是7天定时任务再把状态改为2并给用户加余额。这个定时任务在Linux crontab里配置# 每天凌晨2点执行佣金结算 0 2 * * * php /var/www/cps/think cron:settlethink命令是ThinkPHP框架的CLI入口cron:settle是在application/command里自定义的指令。结算脚本的逻辑很简单查出所有status1且update_time超过7天的订单更新状态为2同时给对应用户余额加上佣金。之所以不在回调时直接加钱是为了防止用户下单后退款导致平台垫付佣金的风险。理解了这套状态机后面做二次开发时改结算周期或者加提现门槛就顺理成章了。3. 从零搭建服务器环境配置与部署流程3.1 LinuxNginxPHPMySQL环境准备搭建说明里明确写了推荐环境Linux Nginx PHP 7.x MySQL 5.7。这里不推荐用Apache原因是Nginx的伪静态配置在处理ThinkPHP的PATH_INFO模式时更干净。环境准备可以直接用宝塔面板也可以手动装。手动装的话CentOS 7上的命令序列大致是# 安装EPEL和Remi仓库 yum install -y epel-release rpm -Uvh https://rpms.remirepo.net/enterprise/remi-release-7.rpm # 启用PHP 7.4仓库并安装 yum --enablereporemi-php74 install -y php php-fpm php-mysql php-mbstring php-gd # 安装Nginx和MySQL yum install -y nginx mariadb-server systemctl start nginx mariadb装完之后要确认PHP的pdo_mysql扩展和curl扩展已经启用因为小程序后端不仅要连数据库还要用curl去调用美团或饿了么的联盟接口。PHP版本和扩展可以用php -m | grep pdo_mysql检查缺什么装什么。Nginx的站点配置里关键是把所有非静态文件请求转发给index.php也就是ThinkPHP的入口文件。配置片段如下server { listen 80; server_name your-domain.com; root /var/www/cps/public; index index.php; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }这里root指向public目录而不是项目根目录是出于安全考虑——只有public目录暴露在Web根下应用配置文件和源码文件不会直接被URL访问到。rewrite规则把所有不存在的路径交给index.php处理这样/api/coupon/list这类路由才能正确解析到对应的控制器方法。数据库创建和用户授权也按常规操作CREATE DATABASE cps_db DEFAULT CHARACTER SET utf8mb4; GRANT ALL PRIVILEGES ON cps_db.* TO cps_userlocalhost IDENTIFIED BY your_password; FLUSH PRIVILEGES;使用utf8mb4而非utf8是必须的因为小程序的用户昵称可能包含emojiutf8存不下四字节的字符。如果MySQL版本低于5.7.7utf8mb4的索引长度会超出默认限制请升级MySQL或修改配置。3.2 数据库导入与配置文件修改源码压缩包里通常会带一个.sql文件或者是安装向导。v3.0采用的是导入SQL文件的方式。导入前先确认数据库版本是5.7以上否则utf8mb4索引长度会报错。mysql -ucps_user -p cps_db cps_v3.0.sql导入完成后重点修改三个配置文件。第一个是application/database.php里的数据库连接信息return [ type mysql, hostname 127.0.0.1, database cps_db, username cps_user, password your_password, prefix cps_, charset utf8mb4, ];第二个是application/extra/config.php里的cps_key盐值这个值务必改成随机字符串否则验签形同虚设。第三个是config/site.php或类似文件里的小程序AppID和AppSecret这两个值在微信公众平台的小程序管理后台获取。# 生成随机盐值 openssl rand -hex 16改完配置文件后先在后端执行一次自检curl -X POST http://your-domain.com/api/coupon/list如果在浏览器里能看到JSON数据返回说明后端环境通了。这一步跑通之后再去动小程序端避免前后端同时出问题时无法定位。3.3 微信开发者工具上传与审核后端就绪后打开微信开发者工具导入小程序源码目录。导入时AppID选择已注册的小程序AppID不要用测试号因为CPS涉及支付和跳转外部页面测试号的功能受限。导入后第一件事是检查app.js里的API基础地址有没有改成自己的HTTPS域名// app.js globalData: { apiBaseUrl: https://your-domain.com, appid: wx1234567890abcdef }改完之后在开发者工具右侧的详情-本地设置里勾选“不校验合法域名”先本地预览。如果页面能正常拉到优惠券列表再点工具栏的上传按钮把代码传到微信后台然后在小程序后台配置服务器域名request合法域名填https://your-domain.comdownloadFile合法域名如果有文件下载需求也要配。提示微信小程序后台配置服务器域名时域名必须备案且支持HTTPS/1.1否则真机预览会直接报错。审核时需要注意的坑是微信审核会拒绝诱导分享类的页面。v3.0源码里如果带有“邀请好友得佣金”这类文案审核可能被打回。常见的做法是把这个入口藏在个人中心里不在首屏露出等审核通过后再视情况调整。4. 源码二次开发实战优惠券领取与订单回调改造4.1 优惠券列表页面的渲染逻辑很多人在拿到源码后第一个想改的需求是优惠券排序。默认列表按create_time倒序想改成按折扣力度排序直接改Coupon.php里的lists方法public function lists() { $page input(get.page/d, 1); $limit input(get.limit/d, 20); $list CouponModel::where(status, 1) -order(discount, desc) // 按折扣力度降序 -page($page, $limit) -select(); return json([code 1, data $list]); }input(get.page/d, 1)里的/d是ThinkPHP的强制类型转换表示把参数转成整型防止SQL注入。这里用了链式查询where先过滤状态为启用中的券order指定排序字段page做分页。前端拿到数据后用wx:for渲染每条券的数据结构里jump_url就是跳转到外卖平台领券页的地址。如果要做置顶功能可以在cps_coupon表加一个sort字段排序条件改成-order(sort desc, discount desc)。4.2 订单回调与CPS佣金计算的改造订单回调是CPS项目的生命线。默认实现里当外卖平台通知订单状态时Order.php的notify方法只做了加余额操作。如果想增加一个“邀请人分成”即用户A邀请用户BB下单后A也能拿到一定比例的佣金就需要在notify里加一段逻辑// 给上级用户加邀请奖励比例为佣金10% $inviter_id UserModel::where(id, $order[user_id])-value(inviter_id); if ($inviter_id) { $bonus round($data[commission] * 0.1, 2); UserModel::where(id, $inviter_id)-setInc(balance, $bonus); // 记录邀请奖励流水 db(balance_log)-insert([ user_id $inviter_id, amount $bonus, type invite_bonus, create_time time() ]); }这里的关键是inviter_id字段需要提前在cps_user表里存在。如果v3.0源码的表结构里没有这个字段需要用SQL添加ALTER TABLE cps_user ADD COLUMN inviter_id INT DEFAULT 0 COMMENT 邀请人ID;在用户注册接口里通过share_code参数把邀请人的ID写入inviter_id。这个改造的核心思路是不修改主订单流程只在确认结算的节点追加一个奖励记录降低了对原有逻辑的侵入性。佣金比例建议做成常量或数据库配置方便运营时调整。4.3 常见报错与排查思路部署和改动过程中最常遇到三个报错。第一个是接口返回code:0但没有明确提示这时候打开application/config.php里的app_debug true,开启调试模式后再请求接口页面上会直接打出SQL语句和错误文件行号。排查完务必关掉调试模式否则数据库连接信息和文件路径会暴露出去这一点在生产环境尤其重要。第二个是前端能打开页面但列表为空。先用浏览器直接访问/api/coupon/list看看返回是否是合法的JSON。如果浏览器正常但小程序里空检查app.js里的apiBaseUrl是否少写了https://前缀。微信开发者工具里请求的URL必须带协议头否则会直接报url not valid。第三个是回调请求报sign error。排查思路是从平台侧原始请求报文入手比对签名参数。常见原因是平台回调的编码格式不是UTF-8导致http_build_query拼接出来的字符串和签名时的字节序不一致。解决办法是统一用原始POST body做验签而不是用解析后的数组$raw_body file_get_contents(php://input);如果平台侧回调格式是JSON而非表单格式上述http_build_query方案会失效需要改成json_decode(file_get_contents(php://input), true)解析后再排序验证两边的排序规则必须一致才能通过验签。5. 上线后的数据埋点与佣金对账校验5.1 埋点方案与关键事件设计CPS项目上线后最重要的数据不是新增用户数而是“领券-下单”的转化漏斗。v3.0源码没有内置统计系统需要自己埋点。小程序端的埋点建议跟现有业务代码解耦单独建一个utils/track.js里面封装上报函数// utils/track.js const report (event, params {}) { wx.request({ url: https://your-domain.com/api/track/report, method: POST, data: { event, params, ts: Date.now(), openid: wx.getStorageSync(openid) } }); }; module.exports { report };使用时在关键页面调用// index.js Page({ onShow() { report(page_view, { page: index }); }, onCouponClick(e) { report(coupon_click, { coupon_id: e.currentTarget.dataset.id }); } });后端对应建一张cps_track_log表字段包括id,openid,event,params,create_time。有了这张表就能用简单的SQL查询看到漏斗数据coupon_click的UV除以page_view的UV就是列表页的领券转化率再对比平台回调的数据看下单转化率。如果转化率突然掉了一半优先排查平台券的库存和优惠力度是否有变更这类波动通常跟代码改动无关。5.2 佣金对账的批处理脚本佣金对账是运营者最容易忽略但最要命的问题。平台回调的佣金明细、数据库里的订单记录、用户账户的余额变动三者的金额必须一致。写一个每晚跑的对账脚本把不一致的数据输出到日志里人工核查。# 对账脚本 cron:recon每天凌晨3点执行 0 3 * * * php /var/www/cps/think cron:recon脚本逻辑分三步第一步统计当天回调的总佣金和数据库记录总佣金是否相等第二步统计数据库订单佣金总和与用户余额变动总和是否相等第三步输出差异记录。如果发现差异重点检查是否有订单status没有从0更新到1通常是回调处理中数据库事务没提交导致的。在这个环节补一个经验不要把对账的差异直接用SQL改掉先定位是回调延迟还是逻辑缺陷。回调延迟的差异第二天会自动消失逻辑缺陷的差异会每天累加这两类问题的处理方式完全不同。改动订单回调前先执行git stash保存Order.php的原始版本验签和幂等处理不要动出了问题直接git stash pop回滚比手动删改日志快得多。本文还有配套的精品资源点击获取