简介:这是一套面向区块链支付开发者的USDT跑分支付系统完整源码,适合具备一定后端与前端基础、希望研究数字货币支付链路与分销逻辑的开发者及创业者。系统以API监听与自动回调为核心,覆盖支付确认、充值提现等请求的实时处理,并内置三级分销的佣金计算与用户关系管理,可帮助读者理解从接口定义到业务落地的整体架构。压缩包共约2000个文件,整体40.69MB,以609个js脚本、148个php后端逻辑、174个html页面、165个css样式及8个sql建表语句为主,另含json配置、md说明文档与图片资源,目录结构便于按模块检索。目前已有962人学习下载。读者可借此掌握API异步回调设计、并发与事务一致性处理、SQL注入与XSS等安全防护思路,以及分销等级与佣金分发逻辑,对从事区块链支付开发或相关项目实践具有参考价值。
1. 从一次支付回调的“翻车”说起:这套USDT支付系统到底能干什么
去年帮一个做跨境数字商品的小团队看他们的支付后台,用户明明在链上转了USDT,订单却一直卡在“待支付”,客服手动补单补到崩溃。问题不在链上,而在回调——他们的系统靠轮询查链上交易,节点一抖动就漏单。后来我接触到这套USDT跑分支付系统源码,核心卖点就是API监听加自动回调,把“用户转账→系统确认→订单完成→分佣到账”这条链路自动化跑通。它适合谁?做跨境小额收款、需要多级分销结算、又不想自己从零搭链上监听服务的开发者。整套代码围绕支付网关、回调分发、三级分销三个模块展开,下面按“能干什么→怎么跑→怎么改”拆开讲。
2. 架构与目录拆解:支付网关、监听服务、分销引擎怎么串起来
2.1 整体分层:从链上事件到业务订单的四段链路
这套系统的架构不复杂,但分层清晰,常见做法是拆成四层:接入层、监听层、业务层、结算层。接入层对外暴露下单接口和收银台页面;监听层负责盯链上地址的入账事件;业务层处理订单状态机和回调分发;结算层跑三级分销的佣金计算。四层之间用消息队列或数据库状态字段解耦,避免链上抖动直接打到业务库。
我拿到源码后先看的是目录结构,因为目录往往比文档诚实。典型的目录长这样:
usdt-pay/ ├── app/ │ ├── api/ # 对外接口:下单、查单、回调 │ ├── listener/ # 链上监听与事件解析 │ ├── service/ # 订单状态机、分销计算 │ └── model/ # 订单、用户、佣金数据模型 ├── config/ │ ├── chain.php # 节点RPC、合约地址、确认数 │ └── distribute.php # 三级分佣比例 ├── runtime/ # 日志与回调重试队列 ├── public/ │ └── cashier/ # 收银台静态页 └── cron/ └── listen.php # 监听常驻进程入口这个结构里,listener和service是重点。listener只干一件事:把链上事件翻译成内部事件,丢给service。service不关心链,只认内部事件,这样以后换链或加链,业务层不用动。我一般会建议把config/chain.php里的确认数单独拎出来,因为不同链、不同金额的确认策略不一样,写死容易出事。
2.2 监听服务:API轮询与事件解析的关键参数
监听层是这套源码的核心卖点。它没有用重量级的链上索引服务,而是走节点RPC轮询,配合本地游标记录已处理区块。常见做法是每3到5秒拉一次最新区块,对比本地游标,逐块扫描目标地址的入账交易。关键参数有三个:起始区块、确认数、扫描间隔。
// config/chain.php 监听相关配置 return [ 'rpc' => 'http://127.0.0.1:8545', // 节点RPC地址 'contract' => '0x...USDT合约地址', // USDT合约 'start_block' => 18000000, // 首次启动的起始区块 'confirm' => 12, // 入账确认数,12块后视为最终 'scan_interval'=> 3, // 轮询间隔,单位秒 'batch_size' => 200, // 单次扫描区块跨度 ];confirm这个参数最容易被忽视。设太小,链上重组时可能把已确认的订单回滚;设太大,用户等得着急。我一般按金额分档:小额6块,大额12到30块。batch_size控制单次扫描跨度,节点性能差就调小,否则一次拉太多区块容易超时。start_block只在首次启动时生效,之后靠本地游标续扫,所以部署时别填错,填早了扫一堆无关区块,填晚了漏历史单。
2.3 自动回调:状态机与重试队列的设计
回调不是转完账就完事,得保证业务方一定能收到通知。这套源码用了一个简单的状态机:pending → detected → confirmed → notified → done,任何一步失败都进重试队列。回调分发逻辑大致如下:
// app/service/CallbackService.php public function dispatch($order) { // 只处理已确认且未通知的订单 if ($order['status'] !== 'confirmed') { return false; } $payload = [ 'order_no' => $order['order_no'], 'amount' => $order['amount'], 'tx_hash' => $order['tx_hash'], 'sign' => $this->sign($order), // 防篡改签名 ]; $res = $this->httpPost($order['notify_url'], $payload, 5); // 5秒超时 if ($res === 'success') { $this->markDone($order['id']); // 标记完成 } else { $this->pushRetry($order['id']); // 进重试队列 } }sign字段是防篡改的关键,通常用商户密钥对订单号、金额、时间戳做HMAC。httpPost超时设5秒,太短容易误判失败,太长会拖住队列。重试队列我一般建议用指数退避,第一次10秒,第二次30秒,第三次2分钟,超过5次进死信表人工介入。这套源码默认是固定间隔重试,改造时值得换成退避策略。
2.4 三级分销:佣金计算与结算时机
分销引擎挂在订单完成之后,按配置比例给三级上级分佣。常见做法是订单进入done状态时触发一次结算,佣金先记到用户余额的冻结字段,过了风控期再解冻。比例配置在config/distribute.php:
return [ 'level' => [ 1 => 0.30, // 一级佣金比例30% 2 => 0.10, // 二级10% 3 => 0.05, // 三级5% ], 'settle_delay' => 3600, // 结算延迟1小时,防退款 ];这里有个容易翻车的点:佣金基数应该是订单实收金额,不是用户支付金额。如果链上到账有手续费损耗,基数得按实际到账算,否则分佣会超发。settle_delay是后悔药,给退款和风控留窗口,别图快设成0。
3. 跑起来:环境准备、配置修改与启动顺序
3.1 环境依赖与最小可跑配置
这套源码是PHP栈,常见依赖是PHP 7.4以上、MySQL 5.7以上、Redis做队列和游标缓存,外加一个可访问的链上节点RPC。节点可以是自建全节点,也可以用第三方RPC服务,但要注意RPC的稳定性和限流。最小可跑配置清单:
| 组件 | 版本/要求 | 用途 |
|---|---|---|
| PHP | 7.4+ | 运行主程序 |
| MySQL | 5.7+ | 订单、用户、佣金 |
| Redis | 5.0+ | 游标缓存、重试队列 |
| 节点RPC | 支持eth_getLogs | 链上事件查询 |
| 进程管理 | supervisor | 守护监听进程 |
装依赖时最容易卡在PHP扩展上,gmp、bcmath、curl这几个必须开,尤其是做金额计算时bcmath不能少,用浮点数算USDT迟早出精度问题。
3.2 数据库初始化与配置项逐条说明
导入SQL后,重点改三个配置文件。config/chain.php改RPC地址、合约地址、起始区块;config/database.php改库连接;config/distribute.php改分佣比例。改完先别急着启动监听,用命令行跑一次单次扫描验证配置:
# 单次扫描,不常驻,用于验证配置 php cron/listen.php --once --from=18000000 --to=18000200--once表示扫完就退出,--from和--to指定区块范围。如果这条命令能正常输出扫描到的交易,说明RPC和合约地址没问题。如果报method not found,多半是RPC不支持eth_getLogs,换个节点。如果扫到交易但没入库,检查合约地址大小写,有些链对地址校验严格。
3.3 启动监听与回调进程的正确顺序
启动顺序有讲究,先起监听,再起回调,最后开收银台。监听进程负责把链上事件写进订单表,回调进程负责把已确认订单推给商户。顺序反了会出现回调进程空转或重复通知。
# 1. 启动监听常驻进程 php cron/listen.php --daemon # 2. 启动回调重试进程 php cron/callback.php --daemon # 3. 启动收银台下单接口(视部署方式) php -S 0.0.0.0:8080 -t public/用supervisor守护时,autostart=true、autorestart=true,但startsecs别设太小,监听进程启动时要拉一次游标,给个5秒缓冲。启动后看runtime/listen.log,正常会每隔几秒打一行当前扫描区块高度,高度在涨就说明在跑。
4. 避坑与排查:回调漏单、重复通知、分佣错账的现场记录
4.1 现象:订单一直待支付,链上却已到账
原因通常是监听游标卡住或RPC返回空。先看runtime/listen.log最后一行区块高度,如果长时间不动,手动调一次RPC确认节点是否同步。另一个常见原因是合约地址配错,eth_getLogs按地址过滤,地址错了一个事件都扫不到。解决:用区块浏览器查一笔真实交易的to地址,和配置里的合约地址逐字符比对,注意大小写。
4.2 现象:商户收到重复回调,订单被处理两次
原因是回调成功但标记完成失败,或者重试队列没去重。这套源码默认按订单号去重,但如果商户侧没做幂等,重复通知就会重复发货。解决:回调payload里带order_no和tx_hash,商户侧用order_no做唯一键;系统侧在markDone前加一次状态检查,只有confirmed状态才允许通知。
4.3 现象:三级分佣金额对不上,多算或少算
多半是佣金基数取错,或者并发结算导致重复分佣。检查distribute.php的比例之和是否超过100%,三级加起来超过100%就是超发。并发问题常见于同一订单被两个进程同时结算,解决:结算前用Redis锁按订单号加锁,锁超时设10秒,拿不到锁就跳过。
4.4 现象:监听进程跑几天后内存暴涨
PHP常驻进程不释放变量是通病。这套源码在循环里累积了区块数据没清理,跑久了内存就上去了。解决:在扫描循环末尾unset大数组,或者每扫1000个区块重启一次进程。supervisor配stopwaitsecs和定时重启策略,比改代码省事。
4.5 现象:确认数到了但订单没进confirmed
检查确认数计算逻辑,常见错误是用最新区块减交易区块,但没考虑交易所在区块本身。正确算法是latest - tx_block + 1 >= confirm。另外,如果节点回滚过,最新区块可能变小,导致确认数倒退,这种情况要加保护,确认数只增不减。
5. 二次开发:换链、加分销层级与回调幂等的改造思路
5.1 换链改造:从以太坊到其他EVM链
这套源码的监听层和链耦合在config/chain.php和listener里,换链主要改三处:RPC地址、合约地址、起始区块。如果是EVM兼容链,eth_getLogs接口一致,改配置就能跑。但要注意两点:一是不同链的确认数策略不同,BSC可能15块,Polygon可能128块,得按链调confirm;二是原生代币和USDT的精度可能不同,金额计算要用bcmath按实际精度处理,别写死18位。
// 换链时按精度动态计算金额 $decimals = $this->getTokenDecimals($contract); // 查合约decimals $amount = bcdiv($rawValue, bcpow('10', $decimals), $decimals);getTokenDecimals查一次缓存起来,别每笔交易都查。bcdiv的第三个参数是保留小数位,按业务需要设,一般和代币精度一致。
5.2 分销层级扩展:从三级到N级的表结构设计
三级分销写死在配置里,要扩到N级得改表结构和计算逻辑。常见做法是把佣金关系存成闭包表或路径枚举,订单结算时按路径逐级算。表结构大致:
| 字段 | 类型 | 说明 |
|---|---|---|
| user_id | int | 用户ID |
| parent_id | int | 直接上级 |
| path | varchar | 路径,如 1/5/12 |
| level | int | 层级 |
path存从根到当前节点的路径,算佣金时按/拆分,逐级取比例。这样加层级不用改代码,只改配置的比例数组。但要注意路径长度限制,层级太深path字段会超长,一般控制在10级以内。
5.3 回调幂等与签名验证的加固
回调幂等是二次开发里最值得加固的点。这套源码的签名只覆盖了订单号和金额,建议加上时间戳和随机串,防止重放。验证逻辑:
// 商户侧验签示例 $sign = hash_hmac('sha256', $orderNo . $amount . $timestamp . $nonce, $secret); if (!hash_equals($sign, $receivedSign)) { return 'sign error'; } // 时间戳超过5分钟视为过期 if (abs(time() - $timestamp) > 300) { return 'expired'; }hash_equals防时序攻击,别用==。时间戳窗口设5分钟,太短容易因网络延迟误判,太长重放风险大。nonce存Redis做一次性校验,用过就删。
5.4 一个验证改造是否成功的小技巧
改完别急着上生产,用测试网跑一遍全链路。我一般会写一个模拟脚本,往测试地址转一笔小额USDT,然后盯日志看四个状态是否依次流转:detected → confirmed → notified → done。任何一步卡住,日志里都有对应记录。这个习惯是从一次主网漏单事故后养成的,从那以后我每次改监听或回调逻辑,都强制走一遍测试网全链路,确认状态机跑通再合并代码。希望帮到你。
本文还有配套的精品资源,点击获取