面向个人站长 / 小团队的技术解析文:一套 PHP 发卡网源码应该怎么架构、自动发卡逻辑怎么落地、支付回调怎么防重复发货、宝塔怎么部署,以及「免授权源码」里那些不能碰的坑。
源码:fakaysw.top
一、什么是个人发卡网源码
发卡网的本质不是「商城」,而是虚拟商品履约系统:
用户下单 → 支付成功 → 服务端回调验签 → 从卡密池取一条未售出卡密 → 绑定订单 → 返回给用户。
整个过程没有物流,核心资产就是「卡密池」和「订单状态」。
适合用 PHP 发卡网源码的场景通常是:
- 软件激活码 / 授权码
- 游戏点卡、CDK、会员兑换码
- 课程码、资源提取码
- 账号类虚拟商品(需合规)
- 自助售后、自动补发
个人版和企业版的区别不在「能不能卖」,而在:
- 是否多商户
- 是否带分销
- 是否带风控引擎
- 支付通道是自带还是接易支付
- 源码是否加密、是否可二开
二、系统总体架构
典型 PHP 发卡网是「单站前后端一体」或「轻前后端分离」:
浏览器 / 手机端 │ ▼ Nginx + PHP-FPM │ ▼ index.php 入口(伪静态转发) │ ┌─────┴────────────┐ │ 控制器层 │ │ 商品 / 下单 / 支付 / 订单查询 │ └─────┬────────────┘ ▼ 服务层:订单服务 / 卡密服务 / 支付服务 ▼ MySQL(商品、卡密、订单) Redis(锁、缓存、限流) ▼ 支付网关异步回调(notify_url)技术栈常见组合:
- PHP 7.4 / 8.0+
- MySQL 5.7+ 或 MariaDB
- Nginx + 伪静态
- Redis(高并发取卡用)
- 原生 PHP / ThinkPHP 均可
三、核心功能模块
一套能用的个人发卡源码,至少要有这些模块:
1. 商品与分类
- 多级分类
- 商品名 / 售价 / 成本价
- 库存数量(关联卡密表)
- 限购、库存预警
- 购买前公告
2. 卡密管理
- 批量导入(换行 / CSV)
- 卡密状态:未售出 / 已锁定 / 已售出
- 批次管理、有效期
- 导出剩余卡密
- 先进先出 / 随机发货
3. 订单模块
状态建议做成明确状态机:
待支付 → 已支付待发货 → 已发货 ↘ 已关闭 ↘ 退款中 → 已退款自动发卡的动作,只在支付回调里触发,前端跳转不等于发货。
4. 支付模块
- 微信支付
- 支付宝
- 易支付 / 码支付
- 余额支付
- 自定义回调接口
5. 前台用户
- 商品页
- 下单页
- 订单查询(订单号 + 邮箱 / QQ)
- 卡密查看页
6. 后台管理
- 商品管理
- 卡密导入
- 订单查询
- 支付配置
- 操作日志
- RBAC(企业版才有)
四、数据库设计要点
最小可用表结构:
4.1 商品表goods
id category_id title price cost stock sales limit_per_userstatuscreate_time4.2 卡密表cards
id goods_id card_key(加密存储) batch_no expire_timestatus-- 0未售 1已售order_id sold_time卡密别明文存。数据库被拖库就等于资产全丢。
4.3 订单表orders
id order_no(唯一索引) user_key(下单身份) goods_id amount pay_amount pay_type trade_nostatus-- 0待付 1已付待发 2已发 3异常create_time pay_time4.4 配置表
支付密钥、站点名、通知方式、风控开关。
五、自动发卡核心流程
标准链路:
- 用户打开商品页
- 提交购买数量 → 生成订单(status=0)
- 跳支付 / 生成二维码
- 支付平台异步通知
notify_url - 服务端验签
- 查订单,确认金额一致
- 加锁
- 从
cards取一条status=0 - 标记卡密已售 + 写
order_id - 订单改
status=2 - 返回
success给支付网关 - 前台轮询或跳转查看卡密
关键原则:
卡密出库只能在回调里做,不能在前端支付成功页做。
六、支付回调怎么写才不出事
这是发卡网最容易翻车的地方。
常见问题:
- 付了钱没发卡
- 同一条订单发两次
- 别人伪造 POST 骗卡密
- 金额被前端篡改
6.1 回调处理顺序
正确顺序固定是:
- 收原始参数
- 验签(失败直接 exit)
- 查订单
- 校验金额
- 幂等锁
- 行锁查状态
- 发货
- 返回 success
6.2 伪代码
// 1. 验签(以易支付为例)if(!verifySign($_POST)){exit('fail');}$orderNo=$_POST['out_trade_no'];$tradeNo=$_POST['trade_no'];$money=$_POST['money'];// 2. Redis 幂等锁$lockKey='pay_notify:'.$orderNo.':'.$tradeNo;if(!redis()->set($lockKey,1,['nx','ex'=>300])){exit('success');// 已处理过}// 3. 查订单 + 行锁$order=db()->query("SELECT * FROM orders WHERE order_no=? FOR UPDATE",[$orderNo]);if(!$order)exit('success');if($order['status']!=0)exit('success');// 已处理兜底// 4. 金额校验if(bccomp($order['amount'],$money,2)!==0){exit('fail');}// 5. 取卡密$card=db()->query("SELECT * FROM cards WHERE goods_id=? AND status=0 ORDER BY id ASC LIMIT 1 FOR UPDATE",[$order['goods_id']]);if(!$card){// 库存不足,转人工或记录异常db()->update("orders",['status'=>3],$order['id']);exit('success');}// 6. 出库db()->update("cards",['status'=>1,'order_id'=>$order['id'],'sold_time'=>time()],$card['id']);db()->update("orders",['status'=>2,'trade_no'=>$tradeNo,'pay_time'=>time()],$order['id']);exit('success');三层防重复:
- Redis SETNX
- 数据库行锁
- 订单状态判断
七、防超卖:高并发取卡
库存只有 10 张,100 人同时付,如何处理?
错误写法
SELECT*FROMcardsWHEREstatus=0LIMIT1;UPDATEcardsSETstatus=1WHEREid=xxx;两个请求同时读到同一条,就会重复发。
正确写法
方案 A:事务 + 行锁
BEGIN;SELECT*FROMcardsWHEREgoods_id=?ANDstatus=0LIMIT1FORUPDATE;UPDATEcardsSETstatus=1WHEREid=?;COMMIT;方案 B:Redis 预扣库存
$remain=redis()->decr("stock:{$goodsId}");if($remain<0){redis()->incr("stock:{$goodsId}");thrownewException('库存不足');}适合爆单场景,但最终仍以 MySQL 落库为准。
方案 C:trade_no 唯一索引兜底
订单表加trade_no唯一索引,哪怕逻辑锁失效,数据库也会抛错拦截重复发货。
八、卡密分配策略
别所有商品都「随机发」就完事。
先进先出 FIFO
ORDERBYidASC适合有有效期的卡密,老库存先出。
随机发货
ORDERBYRAND()LIMIT1适合怀疑某批卡有问题,均匀稀释投诉。
批次绑定
大系统会按batch_no管:
- 入库时间
- 供应商
- 有效期
- 预警时间
快过期自动下架或标低价。
九、宝塔部署流程
以常见 PHP 发卡源码为例。
1. 环境
- PHP 8.0+(部分老源码用 7.4)
- MySQL 5.7+
- 扩展:pdo、curl、gd、openssl、bcmath、session
2. 建站
宝塔 → 网站 → 添加站点
- 绑域名
- 建数据库
- PHP 版本选对应
3. 上传源码
解压到根目录,注意别多套一层文件夹。
有些源码入口在public/,运行目录要改成/public。
4. 伪静态(Nginx)
ThinkPHP 类:
location / { if (!-e $request_filename){ rewrite ^(.*)$ /index.php?s=$1 last; } }Laravel 类:
location / { try_files $uri $uri/ /index.php?$query_string; }不配伪静态 → 除了首页全 404。
5. 导入数据库
phpMyAdmin 导入db.sql
改配置文件:
DB_HOST=127.0.0.1DB_NAME=fakaDB_USER=faka_userDB_PASS=******6. 权限
chmod-R755runtimechmod-R755uploads7. 后台初始化
访问/install.php或/admin
默认密码一定改。
支付密钥别留示例值。
十、前端下单交互(简化)
// 创建订单constres=awaitfetch('/api/order/create',{method:'POST',body:JSON.stringify({goods_id:12,num:1})})// 拿到 pay_url 跳转location.href=res.data.pay_url// 轮询订单状态setInterval(()=>{checkOrder(orderNo)},2000)注意:
- 前端只轮询
- 不信任前端「支付成功」
- 卡密只在订单
status=2后展示
十一、风控:个人发卡也得做
自动发卡最怕三件事:
- 伪造回调白拿卡
- 同 IP 批量刷
- 付完马上退款撸卡
基础风控:
- 回调验签
- 金额比对
- 同 IP 限频
- 同订单号去重
- 关键商品延迟发货
- 黑名单 QQ / 邮箱 / IP
if($ipHitCount>5){return'触发风控';}高价值商品可加「人工确认后发货」。
十二、二开示例
1. 卡密加密存储
$cipher=openssl_encrypt($plain,'AES-128-CBC',$key,0,$iv);读取时解密,数据库落库是密文。
2. 订单查询页
$order=db()->get("orders",['order_no'=>$orderNo,'user_key'=>$userKey]);if($order['status']!=2){echo'未支付或处理中';}3. 库存预警
if($goods['stock']<=5){notifyAdmin('库存不足:'.$goods['title']);}4. 发货失败补偿
回调里失败写failed_delivery表
cron 每分钟扫一次:
*/1 * * * * php /www/wwwroot/faka/cron/retry.php十三、「免授权源码」的安全坑
这部分必须说重一点。
网上很多「PHP发卡网源码免授权破解版」风险极高:
- 源码加密混淆
- 藏
eval(base64_decode(...)) - 定时回传订单 / 支付密钥到外部服务器
- 后台无鉴权接口
- install 目录没删
- 支付密钥明文
下载后先自检
- 搜危险函数
eval base64_decode system exec shell_exec file_put_contents- 比对官方仓库哈希
- 删
install.php - 改
APP_KEY/ 数据库前缀 - 关调试模式
- 支付回调目录不允许列目录
正版 / 开源版更稳的场景
- acg-faka 类开源项目
- ThinkPHP 明源版
- 自己二开
- 商业授权可查
别贪「免授权」三个字,支付密钥被回传一次,亏的不是卡密钱,是全部订单数据。
十四、合规边界(个人站长必看)
发卡网本身不是问题,卖什么才是问题。
正规可做:
- 软件授权
- 课程码
- 游戏点卡(有资质渠道)
- 会员兑换码
- 自建社区 VIP
不建议碰:
- 盗版账号
- 破解软件
- 灰产引流码
- 无证支付代收
- 多层返利盘
支付通道也会查类目,别把发卡站做成「免实名收款页」。
十五、选型检查清单
拿一套 PHP 发卡网源码前对照:
- PHP 版本是否匹配(7.4 / 8.0+)
- 是否伪静态友好
- 订单状态机是否清晰
- 回调是否验签
- 是否防重复发货
- 卡密是否明文存
- 是否支持批量导入
- 是否带库存预警
- 后台能否改默认密码
- 有无 install 后门
- 是否真开源可二开
- 支付回调地址是否 HTTPS
十六、总结
个人发卡网源码看起来简单,核心其实就一句话:
支付回调可信 + 卡密出库原子化 + 订单状态唯一
页面好不好看是次要,
真正决定你「会不会半夜被撸空」的是这三处:
- 回调验签
- 取卡事务锁
- 源码有没有后门
如果是学习二开,建议直接选开源明源版,自己加:
- 卡密 AES 加密
- Redis 预扣库存
- 订单查询页
- 基础 IP 限频
- 发货失败重试