简介:这份资源是2024年更新的付费进群系统源码,基于ThinkPHP独立开发,面向需要搭建社群付费入群平台的站长、代理运营者及二次开发人员,解决免公众号加群、定位入群与代理分站管理等场景需求。压缩包共2003个文件,约167.45MB,涵盖315个php核心程序、191个js与45个css前端脚本、大量jpg/png/gif界面素材,以及sql数据库脚本、html模板、字体与视频等资源,结构完整便于部署与二次修改。目前已有443人学习下载。功能上新增官方模板与多图模板,支持顶部海报开关、进入弹窗引导、群头像随机逻辑,并修复了单图模板编辑bug与SQL注入漏洞,增强IP定位接口,优化总站、分站与代理后台的登录UI及数据可视化大屏,附带分站与代理后台使用教程菜单。源码无需公众号即可对接易支付,支持代理自定义页面,前台文字图片均可在后台修改,适合快速搭建九块九付费进群、相亲群等多合一社群变现系统。
1. 付费进群系统到底在卖什么:从 9.9 元门槛到自动分账的完整链路
很多人第一次听到「付费进群系统」会以为只是把收款码贴在群公告里,实际上 2024 年还在更新的这类源码,核心早就不是「收钱」这一个动作,而是把「用户扫码 → 支付 → 自动拉群 → 到期踢人 → 续费提醒 → 分账结算」整条链路做成闭环。9.9 元这个价位不是随便定的,它对应的是低客单价、高频次、强依赖自动化的场景,人工审核根本扛不住。你如果打算自己搭一套,或者想改一套现成源码做二次开发,需要先搞清楚它由哪几块拼起来:前端落地页、支付回调服务、群成员管理、定时任务、后台管理面板。这套东西适合有 PHP 或 Node.js 基础、能自己配数据库和定时任务的开发者,纯小白直接买成品大概率会在支付回调那一步翻车。下面按「先跑通最小闭环,再补细节」的顺序拆开讲。
2. 付费进群系统的技术骨架:支付回调、群成员同步、定时踢人怎么串起来
2.1 为什么支付回调是整个系统最容易翻车的一环
支付回调是整条链路里唯一「不可控」的环节。用户付完钱,支付平台会异步通知你的服务器,你的服务器收到通知后要完成三件事:写订单、标记已支付、触发拉群。听起来简单,但实际部署时最常见的现象是:用户钱扣了,群没进去,后台订单还是「待支付」。原因通常有三个:回调地址写的是内网 IP、回调接口没有做幂等、回调返回格式不符合支付平台要求。
我一般会先把回调接口单独拎出来测,不接任何业务逻辑,只做一件事:收到请求就写一条日志,返回支付平台要求的成功标识。确认能收到通知之后,再往里加业务代码。这样排查问题时能快速定位是「收不到」还是「收到了但处理失败」。
<?php // notify.php 支付回调入口,先只做日志和验签 $raw = file_get_contents('php://input'); file_put_contents('/tmp/pay_notify.log', date('Y-m-d H:i:s') . ' ' . $raw . PHP_EOL, FILE_APPEND); $data = json_decode($raw, true); // 验签:不同支付平台字段名不同,这里用 sign 举例 $sign = $data['sign'] ?? ''; unset($data['sign']); ksort($data); $localSign = md5(http_build_query($data) . '&key=你的商户密钥'); if ($localSign !== $sign) { file_put_contents('/tmp/pay_notify.log', 'sign fail' . PHP_EOL, FILE_APPEND); exit('fail'); } // 验签通过后先返回 success,避免支付平台重复通知 echo 'success'; // 业务逻辑放到异步队列或单独脚本里处理 file_put_contents('/tmp/pay_order_queue.log', $data['out_trade_no'] . PHP_EOL, FILE_APPEND);这段代码的关键点有三个:第一,file_get_contents('php://input')拿原始报文,不要用$_POST,因为很多支付平台发的是 JSON;第二,验签前先unset($data['sign']),否则参与签名的字段会多一个;第三,先返回success再处理业务,避免支付平台因为超时重复推送。参数方面,out_trade_no是你自己生成的订单号,必须全局唯一,建议用「日期 + 用户 ID + 随机数」拼,不要用自增 ID,否则容易被遍历。
2.2 群成员同步:用群机器人接口还是模拟客户端
拉群这个动作,2024 年常见的做法有两种:一种是调用群机器人接口,另一种是模拟客户端登录后执行邀请。前者稳定但功能受限,很多群机器人接口不支持直接拉人进群,只能发消息;后者灵活但容易掉线,需要维护登录态。
我一般会优先看目标群平台有没有开放「邀请成员」的接口。如果有,直接走接口,把用户 ID 和群 ID 传进去就行。如果没有,才考虑模拟客户端方案。模拟客户端方案的核心是维护一个长连接,监听「新订单」事件,然后调用邀请方法。这里最大的坑是登录态过期,通常几小时到几天不等,需要写一个定时任务定期检查在线状态,掉线就重新登录。
// 模拟客户端方案的核心逻辑(伪代码,具体 API 看平台文档) const { Client } = require('some-group-sdk'); const client = new Client({ token: '你的登录凭证' }); client.on('ready', () => { console.log('客户端已上线'); }); client.on('order_paid', async (order) => { try { await client.inviteToGroup(order.groupId, order.userId); console.log('拉群成功', order.orderNo); } catch (e) { console.error('拉群失败', order.orderNo, e.message); // 失败写入重试队列,不要直接丢弃 await redis.lpush('invite_retry', JSON.stringify(order)); } }); client.login();参数说明:order.groupId是目标群 ID,order.userId是付款用户在该平台的唯一标识,order.orderNo是你的订单号。重试队列建议用 Redis 的 list,配一个定时任务每 5 分钟消费一次,失败超过 3 次就告警。注意不要在主流程里同步重试,否则回调接口会超时。
2.3 定时踢人:到期自动移出群聊的实现与边界
付费进群通常是按月或按年,到期后要自动把用户移出群聊。这个动作靠定时任务完成,常见做法是每分钟扫一次数据库,找出「到期时间 < 当前时间 且 状态 = 在群」的记录,逐条调用移出接口。
-- 查询待踢出用户 SELECT id, group_id, user_id, expire_at FROM paid_group_members WHERE expire_at < NOW() AND status = 'active' LIMIT 100;这里有几个边界要注意:第一,LIMIT 100是防止一次拉太多把接口打挂,分批处理;第二,踢人之前先发一条续费提醒,给用户一个缓冲,直接踢容易引发投诉;第三,踢人失败要记录原因,常见的是用户已经自己退群了,接口会报「成员不存在」,这种可以直接标记为已处理。
定时任务的频率建议 1 分钟一次,不要设成 1 小时一次,否则用户到期后还能白嫖很久。但也不要设成 1 秒一次,数据库压力大。用 crontab 或者宝塔面板的计划任务都能配。
3. 从零搭一套 9.9 付费进群系统:数据库设计、后台配置、支付对接的实操顺序
3.1 数据库表怎么设计才不用后期改表
我见过太多人一开始只建一张订单表,后面要加续费、分账、优惠券,改表改到崩溃。建议一开始就把这几张表建好:orders(订单)、paid_group_members(群成员)、groups(群配置)、admins(后台账号)、settlements(分账记录)。
CREATE TABLE `orders` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `order_no` VARCHAR(64) NOT NULL UNIQUE, `user_id` VARCHAR(64) NOT NULL, `group_id` INT UNSIGNED NOT NULL, `amount` DECIMAL(10,2) NOT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已退款', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `paid_at` DATETIME DEFAULT NULL, INDEX `idx_user_group` (`user_id`, `group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `paid_group_members` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `group_id` INT UNSIGNED NOT NULL, `user_id` VARCHAR(64) NOT NULL, `order_no` VARCHAR(64) NOT NULL, `expire_at` DATETIME NOT NULL, `status` VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT 'active/expired/kicked', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_group_user` (`group_id`, `user_id`), INDEX `idx_expire` (`expire_at`, `status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;orders表的order_no加唯一索引,防止重复插入;paid_group_members表的uk_group_user保证同一个用户在同一群只有一条有效记录,续费时用INSERT ... ON DUPLICATE KEY UPDATE更新expire_at就行。idx_expire是给定时踢人任务用的,没有这个索引,数据量上万之后扫描会明显变慢。
3.2 后台配置:群信息、价格、分账比例在哪里改
后台管理面板通常用现成的 admin 模板改,重点是把「群配置」和「分账配置」做成可编辑的。群配置包括:群名称、群 ID、价格、有效期天数、是否开启续费提醒。分账配置包括:平台抽成比例、群主收款账号、结算周期。
我一般会把群配置存成 JSON 放在groups表的一个字段里,而不是每个配置项建一个列。这样加新配置不用改表结构,后台渲染时遍历 JSON 就行。但要注意,价格和有效期这种需要参与计算的字段,最好单独建列,方便 SQL 查询和统计。
// 读取群配置 $group = $db->query("SELECT * FROM groups WHERE id = ?", [$groupId])->fetch(); $config = json_decode($group['config'], true); $price = $group['price']; // 单独列,方便统计 $days = $config['days'] ?? 30;分账比例建议做成「平台抽成 + 群主分成」两个字段,结算时按订单金额乘比例算。注意浮点数计算用bcmath扩展,不要直接用*,否则会出现 0.1 + 0.2 不等于 0.3 的经典问题。
3.3 支付对接:选哪家、怎么配、回调地址怎么写
支付对接是整套系统里最需要看文档的部分。2024 年常见的做法是接聚合支付,一家接多家通道,省得自己一个个谈。选的时候重点看三件事:是否支持异步回调、是否有沙箱环境、结算周期是 T+0 还是 T+1。
配置步骤通常是:在支付平台后台创建应用 → 拿到商户号和密钥 → 填到你的系统配置里 → 设置回调地址。回调地址必须是公网可访问的 URL,不能带参数,比如https://你的域名/notify.php。如果你在本地开发,可以用内网穿透工具临时映射一个公网地址,但上线前一定要换成正式域名。
# 测试回调地址是否可达 curl -X POST https://你的域名/notify.php -d '{"test":1}' -H 'Content-Type: application/json' # 预期返回 success 或你定义的标识如果返回 404,检查 Nginx 或 Apache 的 rewrite 规则;如果返回 502,检查 PHP-FPM 是否正常运行;如果返回空白,检查 PHP 错误日志。这一步没有捷径,只能对着日志一条条排。
4. 部署与调试中容易踩的坑:回调收不到、拉群失败、定时任务不执行
4.1 回调收不到:先查网络再查代码
现象:用户支付成功,后台订单状态不变,日志里没有任何回调记录。
原因:八成是回调地址不可达。常见情况有:域名没备案被拦截、服务器防火墙没放行 443 端口、Nginx 配置里把 POST 请求转成了 GET、回调地址写了内网 IP。
解决:先用curl从外网测试回调地址,确认能通;再检查 Nginx 日志有没有收到请求;如果收到了但 PHP 没执行,检查location配置是否正确转发到 PHP-FPM。我一般会在回调文件第一行写file_put_contents('/tmp/notify_hit.log', time());,确认请求到底有没有进来。
4.2 拉群失败:区分是接口报错还是登录态失效
现象:订单显示已支付,但用户没进群,重试队列里堆了一堆记录。
原因:如果是接口报错,通常是参数不对,比如群 ID 写错、用户 ID 格式不对;如果是登录态失效,接口会返回「未登录」或「token 过期」。
解决:把失败原因分类记录,参数错误直接告警人工处理,登录态失效触发重新登录流程。重新登录后不要立刻重试所有队列,先手动测一条,确认能拉成功再批量重试。另外,拉群接口一般有频率限制,重试时加个sleep(1),别把账号搞封了。
4.3 定时任务不执行:crontab 环境变量和 PHP 路径问题
现象:手动执行踢人脚本正常,但 crontab 里就是不跑。
原因:crontab 的环境变量和登录 shell 不一样,php命令可能找不到,或者脚本里的相对路径解析错误。
解决:crontab 里用绝对路径,比如/usr/bin/php /www/wwwroot/你的项目/kick.php,脚本里的文件路径也全部用绝对路径。另外,crontab 的输出默认发邮件,如果没配邮件会静默丢弃,建议在命令后面加>> /tmp/kick.log 2>&1,方便排查。
4.4 续费后到期时间没延长:时区和字段类型问题
现象:用户续费成功,但到期时间还是原来的。
原因:常见的是时区不一致,PHP 用 UTC,MySQL 用东八区,比较时间时出错;或者expire_at字段类型是DATE而不是DATETIME,只精确到天。
解决:统一用DATETIME,PHP 里设置date_default_timezone_set('Asia/Shanghai'),MySQL 连接后执行SET time_zone = '+08:00'。续费逻辑用UPDATE paid_group_members SET expire_at = DATE_ADD(expire_at, INTERVAL 30 DAY) WHERE ...,如果记录不存在再INSERT。
4.5 分账金额对不上:浮点精度和手续费扣除顺序
现象:后台统计的分账金额和实际到账差几分钱。
原因:浮点数计算精度丢失,或者手续费扣除顺序和支付平台不一致。
解决:所有金额计算用bcmath,比如bcadd、bcmul,保留两位小数。手续费是先扣还是后扣,以支付平台文档为准,不要自己猜。结算记录里把「订单金额、手续费、实收、分账」四个字段都存下来,对不上时能快速定位。
5. 让付费进群系统跑得更稳的两个进阶技巧:幂等设计与对账脚本
5.1 用 Redis 做回调幂等,避免重复拉群
支付平台重复推送回调是常态,如果没有幂等处理,用户会被拉进群两次,或者订单被重复标记。最简单的幂等方案是用 Redis 的SETNX,以order_no为 key,设置 5 分钟过期。
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); $lockKey = 'notify_lock:' . $orderNo; if (!$redis->set($lockKey, 1, ['nx', 'ex' => 300])) { // 已经处理过,直接返回 success exit('success'); } // 继续处理业务逻辑参数说明:nx表示 key 不存在时才设置,ex => 300表示 5 分钟自动过期。这样即使处理过程中脚本崩溃,5 分钟后也能重新处理。注意锁的粒度是订单号,不要用用户 ID,否则同一用户多笔订单会互相干扰。
5.2 每天跑一次对账脚本,把「支付平台账单」和「本地订单」对齐
再稳的系统也会有漏单,区别在于你有没有发现。我一般会写一个对账脚本,每天凌晨拉取支付平台前一天的账单,和本地orders表逐条比对,输出三类差异:平台有本地无、本地有平台无、金额不一致。
import csv import pymysql # 读取支付平台账单(假设已下载为 CSV) platform_orders = {} with open('/tmp/platform_bill.csv') as f: for row in csv.DictReader(f): platform_orders[row['order_no']] = row['amount'] # 读取本地订单 conn = pymysql.connect(host='127.0.0.1', user='root', password='', db='paid_group') cursor = conn.cursor() cursor.execute("SELECT order_no, amount FROM orders WHERE DATE(paid_at) = CURDATE() - INTERVAL 1 DAY") local_orders = {row[0]: str(row[1]) for row in cursor.fetchall()} # 比对 for order_no, amount in platform_orders.items(): if order_no not in local_orders: print(f'平台有本地无: {order_no} {amount}') elif local_orders[order_no] != amount: print(f'金额不一致: {order_no} 平台{amount} 本地{local_orders[order_no]}') for order_no in local_orders: if order_no not in platform_orders: print(f'本地有平台无: {order_no}')这个脚本跑完,差异记录直接发到你的运维群或者写入一张reconcile_diff表。坚持跑一个月,你会发现大部分漏单都集中在回调超时和网络抖动上,这时候再针对性优化回调接口的超时时间,比盲目加机器有效得多。
最后说个我自己的习惯:每次改完支付相关代码,先在小号上走一遍完整流程,从扫码到进群到踢人,确认没问题再发版。这套系统涉及钱和用户,没有后悔药可吃,宁可多花十分钟测试,也别半夜被用户投诉叫醒。希望帮到你。
本文还有配套的精品资源,点击获取