简介:外卖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,status |
cps_order | 用户下单回传的订单记录 | id,user_id,order_sn,commission,status |
cps_user | 小程序用户 | id,openid,nickname,balance |
cps_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里自定义的指令。结算脚本的逻辑很简单:查出所有status=1且update_time超过7天的订单,更新状态为2,同时给对应用户余额加上佣金。之所以不在回调时直接加钱,是为了防止用户下单后退款导致平台垫付佣金的风险。理解了这套状态机,后面做二次开发时改结算周期或者加提现门槛就顺理成章了。
3. 从零搭建:服务器环境配置与部署流程
3.1 Linux+Nginx+PHP+MySQL环境准备
搭建说明里明确写了推荐环境: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 --enablerepo=remi-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_user'@'localhost' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES;使用utf8mb4而非utf8是必须的,因为小程序的用户昵称可能包含emoji,utf8存不下四字节的字符。如果MySQL版本低于5.7.7,utf8mb4的索引长度会超出默认限制,请升级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.com,downloadFile合法域名如果有文件下载需求也要配。
提示:微信小程序后台配置服务器域名时,域名必须备案且支持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邀请用户B,B下单后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回滚,比手动删改日志快得多。
本文还有配套的精品资源,点击获取