USDT 支付配置指南(v3.0.6+)¶
本文档面向运营人员,目标是 30 分钟内把 USDT 收款打通到生产环境。
核心机制:所有用户付到同一个主钱包地址,订单通过「金额尾数」识别(base 价格 + 一个不超过 0.01 USDT 的小尾数)。不再有派生地址,不再需要归集。
1. 选哪几条链?¶
| 链 | 典型手续费 | 推荐场景 | 备注 |
|---|---|---|---|
| BSC (BEP20) | ≈ $0.30 | 🟢 推荐主选 | 速度快、费用低、用户基数大 |
| Solana (SPL) | ≈ $0.0005 | 🟢 推荐 | 极便宜,Phantom / Solflare 一扫即填 |
| TRON (TRC20) | ≈ $1.50 | 🟡 国内/币安习惯用户 | 老用户最熟悉的链 |
| Ethereum (ERC20) | ≈ $5.00 | 🔴 不推荐做主链 | 仅作大额企业用户兜底 |
建议至少同时开 BSC + TRC20 两条,对应「省钱用户」+「币安习惯用户」两群人。
2. 准备主钱包地址¶
每条要开的链准备一个纯收款地址(建议跟交易/经营钱包分离,做收款专用):
| 链 | 地址类型 | 来源 |
|---|---|---|
| TRC20 | T... (base58, 34 字符) |
TronLink / imToken / TP / 任意 TRON 钱包 |
| BEP20 | 0x... (40 字符 EVM) |
MetaMask / TrustWallet — 同一个 EVM 私钥也可用于 ERC20 |
| ERC20 | 0x... (40 字符 EVM) |
通常和 BEP20 用同一个 EVM 地址,省一组管理 |
| SOL | base58 (44 字符左右) | Phantom / Solflare / TP |
⚠️ Solana 必须填钱包地址(不是 USDT ATA),扫描器内部自动定位用户 ATA。
3. 申请区块浏览器 API Key(强烈建议)¶
匿名访问 Etherscan / BscScan 每天 ≤200 次,订单略多就被限流。免费 Key 配额是 100k/月,绝对够用。
| 服务 | 申请页面 | env 变量 |
|---|---|---|
| Etherscan | https://etherscan.io/myapikey | ETHERSCAN_API_KEY |
| BscScan | https://bscscan.com/myapikey | BSCSCAN_API_KEY |
| TronGrid | https://www.trongrid.io/dashboard/keys | TRONGRID_API_KEY |
| Solana RPC | 公开节点免费即可;若高并发可用 Helius 或 QuickNode | SOLANA_RPC_URL |
4. 编辑 .env¶
最小化配置(只开 BSC + TRC20):
# ===== 主开关 =====
USDT_PAY_ENABLED=true
USDT_PAY_ENABLED_CHAINS=TRC20,BEP20
# ===== 收款地址(只填要开的链) =====
USDT_TRC20_ADDRESS=Txxx...你的TRON地址xxxxxxxxxxxx
USDT_BEP20_ADDRESS=0xxxx...你的BSC地址xxxxxxxxxxxxxxxxxxxx
# ===== 浏览器 API Key(推荐) =====
TRONGRID_API_KEY=trongrid-key-here
BSCSCAN_API_KEY=bscscan-key-here
# ===== 金额尾数精度(默认 6 位就够,每单最多额外 1¢) =====
USDT_AMOUNT_SUFFIX_DECIMALS=6
# ===== 入账确认延迟 / 订单过期 =====
USDT_PAY_CONFIRM_SECONDS=30
USDT_PAY_EXPIRE_MINUTES=30
USDT_WORKER_POLL_INTERVAL=30
四条全开的样例直接复制 env.example 里 USDT 段即可。
5. 启动 & 验证¶
5.1 重启后端¶
数据库 schema 会自动迁移(init.sql 里的 DO $$ ... ADD COLUMN IF NOT EXISTS ... 块幂等),不需要任何 psql 手工命令。
5.2 验证链选择器¶
打开前端「会员中心」→ 点任意一个套餐的「立即购买」按钮:
- ✅ 看到「选择支付网络」弹窗,列出你开启的所有链
- ❌ 看不到选项 → 检查 env:链未配地址或不在
USDT_PAY_ENABLED_CHAINS白名单
5.3 发一笔最小订单测试¶
- 选 BSC(最便宜),点「继续支付」
- 弹支付页 → 看到大字号金额
19.99xxxx USDT(xxxx 高亮成红色 = 尾数) - 用 MetaMask / TokenPocket 移动端扫二维码 → 应该自动跳到 USDT 转账页,地址 + 金额都已填好(这就是 EIP-681 deep link 效果)
- 转账完成 → 后端 worker 30s 内扫到 → 订单变成
paid→ 再 30s 后变confirmed→ 会员到期日刷新
5.4 常见自检命令¶
# 看后端 worker 日志
docker compose logs -f api | grep -E "UsdtOrderWorker|USDT reconcile|USDT order"
# 直接 ping API(带登录 Cookie 或 Bearer Token)
curl -H "Authorization: Bearer <token>" http://localhost:5000/api/billing/usdt/chains
正常返回示例:
{
"code": 1,
"data": {
"chains": [
{ "code": "BEP20", "label": "BSC (BEP20)", "address": "0x...", "recommended": true, "typical_fee_usdt": 0.3, ... },
{ "code": "TRC20", "label": "TRON (TRC20)", "address": "Txxx", "recommended": false, "typical_fee_usdt": 1.5, ... }
]
}
}
6. 故障排查¶
| 症状 | 可能原因 | 处理 |
|---|---|---|
| 前端弹「暂无可用的支付网络」 | USDT_PAY_ENABLED_CHAINS 为空 / 所有 USDT_*_ADDRESS 为空 |
至少配一条 |
订单创单返回 usdt_pay_disabled |
USDT_PAY_ENABLED=false |
改为 true 重启 |
订单创单返回 chain_not_available |
用户选的链未配地址或不在白名单 | 见 5.2 |
订单 30 分钟后还 pending,链上明明到账 |
watcher 没运行 / API Key 超限 / 金额没匹配上 | 看 worker 日志;金额匹配是精确匹配,转账时金额必须严格等于订单要求 |
创单返回 amount_collision |
极少见:10 次重试都撞到同 (chain, amount) 唯一索引 | 通常是 USDT_AMOUNT_SUFFIX_DECIMALS=4 太小,改为 6 |
单子 expired 但用户说付了 |
转账金额尾数错(手输金额漏了几位) | 提示用户重新下单;旧订单从客服后台手工兑账 |
7. 钱包兼容性对照¶
| 钱包 | TRC20 URI | EVM (EIP-681) | Solana Pay |
|---|---|---|---|
| MetaMask | – | ✅ 自动填 | – |
| TrustWallet | – | ✅ 自动填 | – |
| imToken | ✅ 自动填 | ✅ 自动填 | ✅ 自动填 |
| TokenPocket (TP) | ✅ 自动填 | ✅ 自动填 | ✅ 自动填 |
| OKX Wallet | ✅ 自动填 | ✅ 自动填 | ✅ 自动填 |
| Coinbase Wallet | – | ✅ 自动填 | – |
| Phantom | – | – | ✅ 自动填 |
| Solflare | – | – | ✅ 自动填 |
| TronLink (旧版) | ⚠️ 只读地址 | – | – |
| Binance Wallet (内置) | ⚠️ 只读地址 | ⚠️ 只读地址 | ⚠️ 只读地址 |
「只读地址」的钱包扫码后只会读到收款地址,用户需要从订单页复制金额(金额按钮就在二维码下方)。订单页对金额做了红色高亮 + 一键复制,最大程度降低漏付/少付的概率。
8. FAQ¶
Q:为什么每单要让用户多付一点点(比如 19.9 → 19.991234)? A:这就是「订单身份证」—— 我们用这个尾数把链上转账匹配回订单。每单最多额外 1¢ 是设计上限。
Q:能不能让金额完全对齐 base 价(不带尾数)? A:可以,但那样多笔金额相同的并发订单会撞车,必须重新引入派生地址。两难取舍,我们选了「+1¢ 容忍」。
Q:碰撞概率有多大? A:6 位精度下尾数 slot 空间 ≈ 10000,30 分钟窗口内同金额同链订单要超过 ~100 个才有可观碰撞率。下单冲突会自动重试 10 次,业务侧基本看不到。
Q:API Key 不申请行不行? A:测试可以,生产强烈不建议。Etherscan / BscScan 匿名 200 次/天,一上量就会限流,导致订单 30 分钟内匹配不到链上转账,用户体验掉到地板。
Q:旧的 xpub 派生订单还能用吗?
A:v3.0.6 不再生成新的派生订单。老订单数据仍保留在 qd_usdt_orders 表里,用户可以查看历史;但 worker 只对新模型订单做匹配(chain ∈ 新四链 + address = 主钱包地址)。需要兑账老订单请走客服后台手工处理。
Q:用户关闭支付窗口后重新打开会怎样?
A:v3.0.6 起 create_order 是幂等的 —— 同一用户在 (plan, chain) 上如果已有一笔 pending 且未过期的订单,再次点购买会直接复用那张单(同样的金额、地址、URI、剩余过期时间),不会插入新行。前端会弹一行 toast「检测到您还有一笔未支付的订单,已为您继续展示」让用户知道这是同一笔。如果用户切换到另一条链,则视为新的支付意图,会创建一张新订单。
Q:金额显示为什么固定 6 位小数?
A:精度由 USDT_AMOUNT_SUFFIX_DECIMALS 控制(默认 6)。DB 列宽 NUMERIC(20,8) 留了 2 位扩展空间,但所有出口(API / URI / 二维码 / UI)统一量化到 suffix_decimals() 位 —— 你看到的金额永远长这样:19.9 → 19.901234、20 → 20.001234、19.99 → 19.991234。多 1 位或少 1 位都属于 bug。
