
简介这是一款面向彩虹易支付站长的 USDT-TRC20 收款插件适合需要为现有易支付系统新增稳定币支付渠道的个人开发者或商户。插件安装后收到的 USDT 直接进入自己钱包不经过任何第三方可降低资金流转成本与信任风险。资源共 5 个文件压缩包约 7KB包含 3 个 PHP 插件文件、1 份 README 说明文档和 1 份 License 授权声明PHP 文件分别承担支付回调、定时对账与插件注册等职责代码简洁、目录清晰便于阅读改造。目前已有 758 人学习下载。通过该插件可快速在易支付后台增加一种调用值为 usdt 的支付方式并支持 PC 与移动端展示适合具备基础 PHP 使用经验的站长参考部署也可以作为易支付插件开发的入门示例。1. 彩虹易支付USDT-TRC20收款插件从收银台到链上确认的最后一公里如果你已经用彩虹易支付搭好收银台会发现上游支付渠道死活接不进来 USDT。支付宝微信的回调是平台帮你推过来而 USDT-TRC20 没有“支付平台”链上只有一次转账事件等着你自己去扫链、自己判确认、自己回写订单。所谓彩虹易支付 USDT-TRC20 收款插件就是这个补齐环节它接收易支付下发的订单生成专用的 usdt 钱包充值地址定时从波场节点拉取该地址的 TRC20 转账记录匹配订单金额后异步通知易支付。做成这个插件商户不需要改易支付主流程用户也不离开原有收银台。适合正在跑彩虹易支付实例、想快速新增稳定币通道的技术人员也适合准备把同类 PHP 收银系统接 USDT 的开发者。2. 从易支付的一次下单流程拆出 USDT 插件该挂在哪彩虹易支付本质是一个 PHP 聚合收银台商户发一笔支付选择一个支付方式代码系统根据这个代码绑定的渠道配置去拉起支付。换到 USDT-TRC20这个“拉起”的动作变成两段先给你的订单生成一个链上充值地址再等链上转账事件。把这两段放进易支付的既有流程里就是插件全部的工作范围。2.1 一条支付通道在彩虹易支付里的完整生命周期常见的彩虹易支付版本对支付方式的定义大致一致一次 USDT 收单会经过五步用户在收银台选择 USDT-TRC20易支付在订单主表落一条新订单状态为等待支付订单号order_sn、金额money、异步通知地址notify_url都是现成的。易支付把订单参数交给插件侧的支付入口插件根据money按配置的汇率换算成 USDT 数量。插件生成一个 TRC20 充值地址把地址和金额渲染到收银台页面上展示二维码。用户支付后插件后台任务轮询波场节点匹配到这笔转账并确认足够块数后回写插件自己的订单状态。插件主动调用易支付的异步通知入口或易支付的订单更新接口易支付再把成功状态推给商户。第 4 步和第 5 步之间的衔接是插件实现得好不好的分水岭。支付宝微信有官方回调保证到达率USDT 没有所以第 5 步的通知必须自己做幂等和重试不能依赖某一次 HTTP 请求碰巧成功。2.2 插件实际要碰的三类数据写插件前先把数据结构看明白。通常我们需要关注三种数据不需要去动易支付的核心支付逻辑。数据位置典型字段插件要做的事易支付订单主表order_sn、money、type、status、notify_url读取订单信息必要时把插件生成的交易号写回支付方式配置支付方式代码、名称、状态在后台或配置里注册 USDT-TRC20 渠道插件自己的扩展表充值地址、加密私钥、tx_id、确认次数新增表与易支付主订单通过order_sn关联不同发行版的目录结构有差异建议先通过order_sn下单入口和notify_url的接收端定位路由不要依赖固定绝对路径。插件的边界是易支付只认order_sn money type插件只要保证这三者能映射到一个明确的 TRC20 充值单号即可。2.3 用 PHP 给订单分配 TRC20 充值地址的最小实现在插件桥接文件里通常会有一个类似下面的方法在易支付生成订单后调用?php // 桥接文件确保彩虹易支付订单生成后给订单分配一个 TRC20 充值地址 function createUsdtReceiveAccount(string $orderSn, float $usdtAmount): string { // 1. 生成新地址地址和私钥都属于本插件不属于易支付核心 $account tron_create_account(); // 返回 [address T..., privateKey ...] if (empty($account[address])) { throw new RuntimeException(tron address create failed); } // 2. 私钥加密后写扩展表明文私钥不能出现在日志与响应里 $secret getenv(USDT_PRIVATE_KEY_SECRET); $iv random_bytes(12); $encrypted openssl_encrypt($account[privateKey], aes-256-gcm, $secret, 0, $iv, $tag); db()-insert(usdt_pay_order, [ order_sn $orderSn, receive_address $account[address], private_key base64_encode($iv . $tag . $encrypted), amount $usdtAmount, expire_at time() 1800, status 0, create_time time(), ]); // 3. 返回地址给收银台渲染二维码使用 return $account[address]; }这里几个参数需要说清楚参数取值说明order_sn易支付生成的唯一订单号扩展表与主订单表的关联键必须唯一usdtAmount法币金额除以 USDT 汇率保留 6 位小数与 TRC20 精度一致expire_attime() 180030 分钟未支付则过期防止地址被长期占用private_keyAES-256-GCM 加密只能解密写入不能从页面或接口回读展示生成地址这步不依赖易支付核心代码只要在「下单成功 → 返回收银台」的桥接函数里调用即可。地址生成后不要复用一个订单一个地址后续扫链逻辑会简单很多。2.4 为什么等确认完成再回调而不是扫到交易就回调TRC20 转账在链上被打包后理论上已经不可逆TRON 是 DPoS出块稳定但出于资金安全大多数收款插件会等若干个区块确认再放行。这个策略和易支付的异步通知重试机制要分开理解易支付通知商户时订单必须已经是「已入账」状态而「扫到交易」只是中间状态。常见做法是插件内部跑一个补偿队列只有确认数达标后才会把订单状态置为已入账然后触发易支付的通知。通知回调如果失败系统会按递增间隔重试一般间隔是 5 分钟、30 分钟、2 小时、6 小时分批重试直到返回成功标志。插件侧的notify_times字段就是干这个用的它和订单状态字段是两个维度别混在一起。3. 扫链监听与确认策略让 TRC20 转账变成可信任的支付事件上一章解决了「订单有了充值地址」的问题这一章解决最难的部分怎么可靠地知道一笔 USDT 到账了。链上没有回调只能主动查询查询的频率、过滤条件、确认规则直接决定会不会漏单、会不会把未确认交易当成真付款。3.1 一单一地址比单地址匹配更适合作插件接入方经常问能不能只用一个固定收款地址靠金额去匹配订单可以但踩坑概率大。两种方案放在一起对比方案优点缺点适用场景一单一地址地址与订单严格绑定解析无歧义私钥数量多管理成本高自动化收银台本插件推荐固定地址 金额匹配私钥只管理一个同日同金额订单会撞车需人工认领低频站内转账固定地址 memo 备注用户可写备注TRC20 转账不存在标准 memo 字段无法可靠携带不推荐对于彩虹易支付这种高频、小额、无人值守的场景一单一地址是最稳的。私钥多的问题可以靠加密存储和目录分级解决不能靠牺牲订单可靠性来解决。3.2 用 TronWeb 生成充值地址并加密保存私钥地址生成用 TronWeb 的createAccount即可不需要维护完整节点// 生成 TRC20 钱包账户 const TronWeb require(tronweb); const tronWeb new TronWeb({ fullHost: https://api.trongrid.io, headers: { TRON-PRO-API-KEY: process.env.TRON_API_KEY } }); async function createDepositAddress() { // TronWeb 5.x 返回结构通常是 { address: { base58, hex }, privateKey } const account await tronWeb.createAccount(); return { address: account.address.base58, // 用户看到的 T 开头地址 hex: account.address.hex, // 以 41 开头的 hex 地址 privateKey: account.privateKey }; }生成后有两件事必须做私钥加密落库地址的 hex 形式一并保存。hex 地址在后续比对to字段时有用因为 TronGrid 返回的to有时是 base58有时是 hex看接口版本。私钥加密的密钥放环境变量或 KMS绝不能写进代码仓库。3.3 用 TronGrid 查询地址的 TRC20 转账记录监听脚本核心是轮询 TronGrid 的账户交易查询接口接口路径是/v1/accounts/{地址}/transactions/trc20。下面的脚本按订单逐个扫用only_confirmedtrue过滤已上链确认的数据再按确认块数决定是否入账// poll-usdt.js 每 60 秒执行一次 const TronWeb require(tronweb); const mysql require(mysql2/promise); const TRON_API https://api.trongrid.io; const USDT_CONTRACT TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf; // USDT-TRC20 合约地址部署前请以官方公告为准 const USDT_DECIMALS 6; const CONFIRM_BLOCKS 1; // 生产环境建议按金额分档 async function main() { const tronWeb new TronWeb({ fullHost: TRON_API, headers: { TRON-PRO-API-KEY: process.env.TRON_API_KEY } }); const db await mysql.createConnection({ host: 127.0.0.1, user: pay, password: ***, database: pay }); // 获取最新区块高度用于计算确认数 const latest (await tronWeb.trx.getCurrentBlock()).block_header.raw_data.number; const [rows] await db.query( SELECT id, order_sn, receive_address, amount FROM usdt_pay_order WHERE status 0 ); for (const row of rows) { const url ${TRON_API}/v1/accounts/${row.receive_address}/transactions/trc20 ?only_confirmedtruecontract_address${USDT_CONTRACT}limit20; const resp await fetch(url, { headers: { TRON-PRO-API-KEY: process.env.TRON_API_KEY } }); const data await resp.json(); for (const tx of data.data || []) { // 只处理打给本订单地址的转账防钓鱼和转错 if (tx.to ! row.receive_address) continue; const confirms latest - tx.blockNumber 1; if (confirms CONFIRM_BLOCKS) continue; // 金额用最小整数单位比较避免浮点误差 const received BigInt(tx.value); const required BigInt(Math.round(row.amount * 10 ** USDT_DECIMALS)); if (received required) continue; await markConfirmed(db, row, tx.transaction_id, confirms); break; // 同一订单只处理第一笔足额入账 } } await db.end(); } main().catch(console.error);关键参数值得逐个说明参数作用踩坑点only_confirmedtrue只返回链上已确认交易如果不加会扫到未打包或已回滚的交易contract_address过滤 USDT 合约漏传会把同地址上的 TRX 或 USDC 转账也算进来tx.to校验接收方为本订单地址一定要校验否则把别人转给本地址的旧账重复入账limit20单页交易条数高频地址建议用fingerprint翻页拉全这个脚本是「按订单扫地址」的模式订单量在几百个 pending 时没问题因为每个订单都是新地址历史交易极少。如果某个地址被复用API 返回的数据可能超过 20 条就得上fingerprint分页了。3.4 金额比较用整数入账状态靠唯一索引兜底tx.value在 TronGrid 返回的是合约最小单位USDT-TRC20 精度是 6也就是说1 USDT在接口里是1000000。代码里用BigInt比较整数避免0.1 0.2这类浮点问题。同时入账标记必须落在数据库唯一索引上ALTER TABLE usdt_pay_order ADD UNIQUE KEY uk_tx_id (tx_id);一个transaction_id只允许入账一次。这样不管脚本重复执行多少遍、手动补单多少次都不会把同一笔链上转账重复入账两次。4. 订单从待支付到已支付状态机、定时任务与常见排错监听脚本解决了「看到钱」但一个能跑进生产环境的插件还必须把「看到钱」转化为「确认支付成功」并在期间处理各种边界情况。这一章把订单状态、定时任务和实际运维中最常遇见的坑讲透。4.1 扩展表结构与状态流转推荐在插件目录下新增扩展表与易支付主订单通过order_sn关联CREATE TABLE usdt_pay_order ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, order_sn VARCHAR(32) NOT NULL COMMENT 彩虹易支付订单号, receive_address VARCHAR(64) NOT NULL COMMENT TRC20 充值地址, receive_hex VARCHAR(64) NOT NULL COMMENT 地址的 hex 形式, private_key VARBINARY(512) NOT NULL COMMENT AES 加密后的私钥, amount DECIMAL(18,6) NOT NULL COMMENT 应到 USDT 数量, tx_id VARCHAR(64) DEFAULT NULL COMMENT 入账交易哈希, confirm_count INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1待确认 2已入账 3失败 4过期, notify_times TINYINT NOT NULL DEFAULT 0, create_time INT NOT NULL, confirm_time INT DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_sn (order_sn), UNIQUE KEY uk_tx_id (tx_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;状态流转建议分成两段写状态含义触发时机0 待支付地址已生成等待链上转账下单创建扩展表记录1 待确认已扫到交易但确认块数不足首次匹配到tx_id2 已入账确认数达标通知易支付成功确认数累计后回写3 失败金额不足或异常入账对账或人工处理后4 过期超过expire_at未支付定时任务批量更新「待确认」状态必须有否则扫到一个交易但确认数差 1 时如果没有中间态兜底下一次扫描可能重复处理或漏掉。扫到交易的第一时间就把tx_id写进去配合唯一索引天然防重复。4.2 cron 编排与并发控制生产环境建议至少两个定时任务一个负责扫链一个负责补发易支付通知*/1 * * * * /usr/bin/node /opt/pay-plugins/poll-usdt.js /var/log/usdt/scan.log 21 */1 * * * * /usr/bin/php /opt/pay-plugins/notify-compensate.php /var/log/usdt/notify.log 21扫链和补发通知拆开避免一次任务耗时过长导致两端互相等待。每分钟扫一次对 TRC20 来说足够了区块确认只需要等几十秒。任务的幂等性靠数据库唯一索引和状态判断保证但还要注意一点poll-usdt.js本身要加并发锁防止上一次还没跑完、下一次 cron 又启动了。提示在同一服务器上执行多个 PHP 支付项目时cron 的PATH和 PHP 版本要显式指定避免因默认环境不一致导致任务安静地失败。4.3 高频坑漏单、重复通知、手续费差额与转错链漏单是 USDT 收款最隐蔽的问题。TronGrid 的账户交易查询接口默认只有约 24 小时的回看窗口如果服务宕机超过一天重启后直接拉当前 pending 订单可能拉不到更早的转账。建议在插件里维护一个last_scanned_block字段按区块高度增量回补而不是只依赖地址维度查询。重复通知靠两件事拦截uk_tx_id唯一索引兜底status2时不再触发易支付回调。易支付的异步通知重试通常按递增间隔最长可能到几个小时所以notify-compensate.php要能扫到超过 24 小时未成功的通知不能只处理最近一小时的。手续费差额属于业务策略问题。用户从交易所提现 USDT 时实际到账金额可能比订单金额少一点。常见做法是设置小额容差比如到账金额不低于订单金额的 99% 即视为成功低于再走人工核对。容差只能用于小额订单大额订单建议严格等额并手动确认。转错链的问题靠页面提示解决收银台页面上显著标明「仅支持 TRC20 网络转账」同时在入账脚本里校验tx.to和合约地址从技术上排除非目标网络资产。5. 生产参数的三个建议确认阈值、订单时效与每日对账插件跑通之后最能拉开差距的是三个生产参数确认块数阈值、订单过期时间、对账频率。它们不直接影响「能不能喊通」但直接影响资金安全和人工工作量。确认块数阈值要按订单金额分档不要所有订单都等同一个数。小额订单等 1 个确认即可约 3 秒常规金额建议等 6 个确认约 20 秒大额订单等 19 个确认约 1 分钟这个数值接近波场生态常用的「安全块数」标准。可以在配置里写成 JSON{ confirm_blocks: [ { min_amount: 0, max_amount: 500, blocks: 1 }, { min_amount: 500, max_amount: 5000, blocks: 6 }, { min_amount: 5000, max_amount: 1000000, blocks: 19 } ] }订单时效也要主动管理。usdt_pay_order里已设置expire_at生产上建议再加一条定时 SQL把超过 1 天的待支付地址作废UPDATE usdt_pay_order SET status 4 WHERE status 0 AND expire_at UNIX_TIMESTAMP() - 86400;过期地址的私钥仍然存在库里但从业务上不能再接受新转账防止误判。每日对账脚本是最后一道防线统计当天status2的订单总额与 TronGrid 返回的该地址 TRC20 余额做比对差额大于 0.1 USDT 就告警。排查时把scan.log和notify.log分开保存用订单号分别 grep 两条日志一眼就能定位是扫链慢了还是回调丢了这个小习惯比把日志全堆在一起后期查起来高效得多。本文还有配套的精品资源点击获取