QuantDingerQuantDingerv5.0.1 Docs 文档中心
GitHub

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 公开节点免费即可;若高并发可用 HeliusQuickNode 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 发一笔最小订单测试

  1. 选 BSC(最便宜),点「继续支付」
  2. 弹支付页 → 看到大字号金额 19.99xxxx USDTxxxx 高亮成红色 = 尾数)
  3. 用 MetaMask / TokenPocket 移动端扫二维码 → 应该自动跳到 USDT 转账页,地址 + 金额都已填好(这就是 EIP-681 deep link 效果)
  4. 转账完成 → 后端 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.919.9012342020.00123419.9919.991234。多 1 位或少 1 位都属于 bug。