彩虹易支付(Epay / 彩虹支付) 本质上是一套 PHP + MySQL 驱动的聚合支付网关源码,对外暴露一套统一的 RESTful 风格支付 API,对内通过插件化通道适配器(Gateway Plugin)对接支付宝、微信支付、QQ钱包、银联云闪付等下游渠道,实现:
源码及演示:fakaysw.top
你的业务站点 → 统一聚合网关(Epay)→ 具体支付渠道 → 异步回调验签 → 订单状态机推进 → 通知业务方
一、系统定位:它到底是什么?
核心卖点就三个词:一次接入、通道热插拔、订单自治。对个人开发者而言,它解决了"我不想挨个去申请每家支付平台的商户号,我只想有个统一接口收钱"的痛点;对企业技术团队而言,它的插件机制和 MVC 分层给了二次开发足够的操作空间。
二、源码技术架构拆解——从入口到回调的完整链路
2.1 目录结构与职责划分

一份标准的 Epay 源码根目录大致长这样(不同二开版本略有差异,但骨架一致):
Epay/
├── api.php # 对外 API 入口(业务方调用支付/查询)
├── pay.php # 支付网关入口(渠道路由 + 跳转/唤起)
├── submit.php # 支付提交前置处理(参数校验、签名、订单预生成)
├── config.php # 全局配置(DB连接、站点密钥、基础开关)
├── cron.php # 定时任务(订单超时扫描、补单)
├── includes/
│ ├── gateway/ # ★ 支付通道插件目录(每个渠道一个子目录)
│ │ ├── alipay/
│ │ ├── wxpay/
│ │ └── ...
│ ├── lib/ # 核心库(签名、加密、HTTP client、日志)
│ ├── db/ # DB 操作封装 / ORM 基础
│ └── func.php # 全局工具函数
├── plugins/ # 扩展插件(部分版本加密存放,注意权限 deny)
├── template/ # 前端模板(收银台页面、QR 展示页)
├── admin/ # 后台管理入口
├── install/ # 安装向导
├── runtime/ # 运行时(缓存、日志、session)——需写权限
├── nginx.txt # Nginx 伪静态规则
├── .htaccess # Apache 伪静态规则
└── SQL/
└── install.sql # 表结构初始化
关键设计思想:includes/gateway/ 下的每个子目录就是一个支付通道适配器,实现统一的 submit() / notify() / query() 接口契约——这意味着新增一个支付渠道 = 新建一个目录 + 实现三个方法,不动核心调度代码。
2.2 支付主链路(核心时序)
[业务方] ──POST /api.php?act=pay──→ [参数校验 + 签名验证]
│
[查商户白名单 + 费率计算]
│
[生成/写入订单到 MySQL]
│
┌──────────┴──────────┐
▒ 跳转到 pay.php?trade_no=XXX
└──────────┬──────────┘
│
[网关路由器:读 channel 字段]
│
┌───────────┴────────────┐
┌───────┤ gateway/alipay/submit.php ├─── 跳转支付宝页面
│ └───────────┬────────────┘
[用户完成支付] │
│ [第三方异步回调 → notify.php]
│ │
│ [验签 ← 核心:公钥/密钥匹配]
│ │
│ [比对 out_trade_no + 金额]
│ [更新订单状态:0→1(待付→成功)]
│ │
│ [回调业务方的 notify_url(带签名)]
│
[前端轮询 / 跳转 return_url]
一句话抓住本质:整个系统的复杂度不在"调起支付",而在回调验签的严密性 + 订单状态机的不可逆性 + 并发下的幂等保障这三件事上。
2.3 订单状态机(你搭的时候最容易翻车的地方)

典型的订单状态定义:
| 状态码 | 含义 | 允许的前置态 |
|---|---|---|
0 |
待支付(订单已创建,未收到回调) | —(初始态) |
1 |
支付成功 | 0 |
2 |
支付失败 | 0 |
3 |
已退款 | 1 |
4 |
已关闭/超时 | 0 |
工程要点:
- 回调处理逻辑里,必须先
SELECT ... FOR UPDATE或加分布式锁(哪怕你用 RedisSETNX),防止同一笔notify并发到达导致重复执行业务逻辑; - 金额比对永远做
abs(received_amount - order_amount) < 0.01,别直接==浮点数; - 回调业务方之前,先把本侧订单状态落盘——宁可自己先一致,再通知别人。
三、环境准备与依赖清单
以下以 LNMP(Linux + Nginx + MySQL + PHP) 路线为主,这也是彩虹易支付在生产中最常见的运行栈。
3.1 最低 / 推荐配置
| 组件 | 最低 | 推荐 |
|---|---|---|
| OS | CentOS 7 / Ubuntu 18.04 | CentOS 7.9+ / Ubuntu 22.04 LTS |
| Web Server | Nginx 1.14+ / Apache 2.4+ | Nginx 1.24+ |
| PHP | ≥ 7.4(很多版本要求 8.0) | PHP 8.0–8.2 |
| MySQL | 5.6+ | MySQL 5.7 / MariaDB 10.5+ |
| 服务器规格 | 1C2G | 2C4G(有商户量后再扩容) |
3.2 PHP 必开扩展
# 核心扩展(缺任何一个都可能白屏或回调失败)
curl # HTTP 请求(回调转发、渠道接口调用)
openssl # 签名 / 验签 / AES
pdo_mysql # 数据库驱动
mbstring # 中文参数处理
json # API 输入输出
session # 管理会话
可选但强烈建议:
redis(做锁、缓存通道配置、限流)fileinfo(上传相关)opcache(生产性能)
四、一键搭建全流程(命令行级实操)
下面走一条最常用、最稳的宝塔面板 + 手工命令互补的路线。不用宝塔也完全可以纯命令行,本质一样。
Step 0:服务器初始化
# 防火墙放行 80/443(如果用 ufw)
ufw allow 80/tcp
ufw allow 443/tcp
ufw reload
# 或用 firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
Step 1:装 Web 环境(宝塔示例,最快路径)
# 安装宝塔(CentOS 示例)
yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && bash install.sh
装完后进宝塔面板 → 软件商店 → 装:
- Nginx 1.24
- PHP 8.0(装完点"设置"→ 安装扩展:勾选
curl / openssl / pdo_mysql / mbstring / json / session) - MySQL 5.7 或 8.0
- 打开 PHP 设置 → 禁用函数 里把
exec、shell_exec等按需放行(如果你用到 cron 命令行的话)
Step 2:建站点 + 建数据库
宝塔 → 网站 → 添加站点:
- 域名填你的(
pay.xxx.com) - 数据库选"创建数据库" → 记下:
db_name / db_user / db_pass
# 假设你网站根目录在
cd /www/wwwroot/pay.xxx.com

Step 3:上传源码 & 设权限
# 方式A:你本地先下载 zip,SFTP 传上去然后
unzip epay.zip -d /www/wwwroot/pay.xxx.com/
# 确保文件在根目录下(有时 zip 会多包一层目录,要挪平)
# 关键:设可写目录
chown -R www:www /www/wwwroot/pay.xxx.com/
chmod -R 755 /www/wwwroot/pay.xxx.com/
chmod -R 777 /www/wwwroot/pay.xxx.com/runtime # 或至少 755+www可写
chmod -R 777 /www/wwwroot/pay.xxx.com/uploads # 如有上传目录
Step 4:配置伪静态(Nginx 版——这一步不做会 404)
宝塔 → 站点 → 伪静态,粘贴(标准规则,来自 nginx.txt):
location / {
if (!-e $request_filename){
rewrite ^/(.+)$ /index.php?s=$1 last;
}
}
# 保护内部目录
location ^~ /includes {
deny all;
}
location ^~ /plugins {
deny all;
}
点保存,重载 Nginx。
Step 5:跑安装向导
浏览器访问:http://pay.xxx.com/install/index.php
依次填入:
- 数据库地址:
127.0.0.1 - 库名 / 用户 / 密码:刚才宝塔建的
- 管理员账号密码:自己设,别用 admin / 123456
- 站点 URL 填
https://pay.xxx.com(等你 SSL 配好后再改 https 也行,但建议一开始就规划)
⚠️ 安装完成后,立刻删除
/install/目录或改名(安全红线):rm -rf /www/wwwroot/pay.xxx.com/install
Step 6:上 HTTPS(强烈不建议裸 HTTP 跑支付)
宝塔 → 站点 → SSL → Let's Encrypt → 勾选域名 → 申请 → 强制 HTTPS 跳转 ✅
五、支付通道配置:原理与实操
5.1 通道的本质 = 一组密钥 + 一个插件适配器
以 支付宝当面付 / 网站支付 为例,后台填的其实就三类信息:
| 配置项 | 来源 | 作用 |
|---|---|---|
| AppID | 蚂蚁商户平台 | 标识应用 |
| 商户私钥(PKCS1/PKCS8) | 你本地生成 | 请求签名 |
| 支付宝公钥 | 蚂蚁平台下载 | 回调验签 |

回调地址(notify_url) 通常填:
https://pay.xxx.com/includes/gateway/alipay/notify.php
回跳地址(return_url)按模板走即可。
5.2 自验签回路测试(排错黄金法则)
如果你发现"支付成功了但订单还是待支付",按这个顺序查:
# 1. 看回调有没有进来
tail -f /www/wwwroot/pay.xxx.com/runtime/log/*.log
# 2. 看 notify_url 能否被公网 GET/POST 到(用 postman 模拟一个假 notify 测连通性)
# 3. 看 include/gateway/xxx/notify.php 里的验签分支:是不是公钥不匹配?
# 4. 看 MySQL:订单表里 money 字段是 DECIMAL 还是 FLOAT?(必须是 DECIMAL)
90% 的"能付但不到账"都是 验签失败静默丢弃 或 金额精度 float 误差 导致的。
六、业务方如何对接——最小 API 调用示例
下面这段是业务方(你的商城 / SaaS / 下载站)调用易支付网关发起订单的最小感知示例(以 application/x-www-form-urlencoded POST 为例):
6.1 构造签名
假设网关约定的签名串拼接规则为:
<?php
function buildSign(array $params, string $key): string {
unset($params['sign']);
ksort($params); // 字典序
$str = http_build_query($params); // a=1&b=2&...
$str .= '&key=' . $key; // 商户密钥拼尾
return strtoupper(md5($str)); // 或 HMAC-SHA256 看你版本
}
$payload = [
'pid' => 1001, // 商户ID
'type' => 'alipay', // 通道类型
'out_trade_no' => 'ORD' . time() . rand(1000,9999),
'money' => '9.90',
'name' => 'VIP月卡',
'notify_url' => 'https://shop.xxx.com/notify',// 你的回调
'return_url' => 'https://shop.xxx.com/ok',
];
$payload['sign'] = buildSign($payload, '你的商户密钥');
$payload['sign_type'] = 'MD5';
?>
6.2 跳转/表单提交
最简单的方式——直接 POST 到一个隐藏表单自动提交(跨域无 JS SDK 依赖):
<form id="f" method="post" action="https://pay.xxx.com/api.php?act=pay">
<input name="pid" value="<?= $payload['pid'] ?>">
<input name="type" value="<?= $payload['type'] ?>">
<input name="out_trade_no" value="<?= $payload['out_trade_no'] ?>">
<input name="money" value="<?= $payload['money'] ?>">
<input name="name" value="<?= $payload['name'] ?>">
<input name="notify_url" value="<?= $payload['notify_url'] ?>">
<input name="return_url" value="<?= $payload['return_url'] ?>">
<input name="sign" value="<?= $payload['sign'] ?>">
<input name="sign_type" value="MD5">
</form>
<script>document.getElementById('f').submit();</script>
6.3 业务方一侧的 notify 接收(幂等骨架)
<?php
// shop.xxx.com/notify
$in = $_POST;
// 1. 验签(镜像网关逻辑)
if (buildSign($in, YOUR_KEY) !== $in['sign']) {
http_response_code(403); exit('sign_fail');
}
// 2. 幂等:查本地是否已处理过这笔 trade_no
if (order_exists_and_success($in['out_trade_no'])) {
echo 'success'; exit; // 告诉网关"收到了"
}
// 3. 金额复核(防止篡改金额绕过)
if (abs($in['money'] - query_order_amount($in['out_trade_no'])) > 0.009) {
exit('money_mismatch');
}
// 4. 开权益(发货 / 开通 VIP / 加余额)
grant_benefit($in['out_trade_no']);
echo 'success'; // ★ 必须输出网关期望的成功标识,否则会反复重试
?>
七、性能与高可用:让聚合网关真的"高性能"
7.1 瓶颈在哪?
| 瓶颈 | 对策 |
|---|---|
| 回调风暴(同一笔 notify 重发多次) | Redis SETNX 锁 / DB FOR UPDATE |
| 订单表爆炸 | 按月分表或归档历史订单;给 status + out_trade_no 建复合索引 |
| PHP-FPM 阻塞 | 把 cron 扫描超时订单改成 CLI 脚本 + crontab,别用 web 触发 |
| 单点 DB | 初期不用慌,但规划好:读写分离 / 连接池上限 |
7.2 一个实用的 cron 兜底脚本
# crontab -e
*/3 * * * * php /www/wwwroot/pay.xxx.com/cron.php >> /var/log/epay_cron.log 2>&1
cron.php 内部干的事:扫 status=0 AND create_time < UNIX_TIMESTAMP()-600,尝试主动查询渠道接口补状态——这是解决"回调丢了但钱扣了"的最终防线。
八、安全加固清单(做完才叫"上线")
☐ 删除 /install 目录
☐ 后台路径改名(别留 /admin 裸奔)
☐ 管理员密码 ≥12位 + 定期轮换
☐ 数据库表前缀不要是默认的(如果安装时可自定义)
☐ includes/ 和 plugins/ 目录 deny all(已完成)
☐ 关 PHP 错误回显(production 环境下 display_errors=Off)
☐ HTTPS Only + HSTS
☐ 给 notify_url 加 IP 白名单(可选:只允许支付宝/微信 回调 IP段)
☐ 日志不要记录明文密钥/敏感 header
☐ 定期 mysqldump 备份 + offsite 存储

技术归技术:彩虹易支付的架构价值在于它把一个混沌问题——"N 个渠道 × M 个商户 × 回调幂等 × 订单一致性"——收敛成了一个清晰的 Gateway 插件 + 状态机 + 签名体系,这套设计范式即使你将来换成 Java / Go 重写,也一样成立。
如果你希望我继续往下拆某一块(比如"从零手写一个最小化 Gateway 插件适配微信 V3 API"或"订单表结构设计 + 分表迁移方案"),告诉我你的运行环境版本,我可以给出精确到文件和行号的改造路径。