Files
pangolin/docs/superpowers/plans/2026-07-09-payment-clean-usdt-loop.md
wangjia 5fd0d3c148 feat(pay): 同源收款测试页 GET /_test(Phase E 手动闭环验证)
pay-server 自带 /_test 测试页:创建订单→显示收款地址+精确金额(可复制)
+过期倒计时→轮询状态到 paid。同源、无 CORS/CSP 摩擦,不碰营销站安全头;
纯调 POST /order + GET /order/{id},无私钥无密钥。生产收银台后续走独角数卡门面。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 15:51:10 +08:00

13 KiB
Raw Permalink Blame History

收款闭环 · 独角数卡 + 自托管 USDT(TRC20)+ webhook JIT 发码(最干净长期方案)

#34。目标:把「下单 → USDT 付款 → 自动发激活码 → 客户端/用户中心兑换 → 订阅生效」端到端打通, 全程自托管、售出才发码、无跑分、无第三方支付主体。

关键前提(已摸底):激活码后端大半已就绪——POST /v1/redeem(JWT 兑换,含幂等+限频+账户锁)、 批次生成/导出(server/cmd/codegen + server/internal/codes,码为 Crockford Base32 75-bit 带校验位、只存 hash)、发卡店回调 POST /webhook/store/codesHMAC+时间戳+nonce 防重放)都在。本方案新增的核心只有一个 后端端点JIT 发码 /internal/codes/mint+ 一个独立收款服务(pangolin-payTronGrid watcher + epay 支付口)。

三大组件 / 数据流

买家 ──下单──▶ 独角数卡(门面, 独立VPS)
                   │ 建单 + epay 跳转
                   ▼
           pangolin-pay(自建收款服务, 同VPS)
             ├ 派生每单唯一 TRC20 收款地址(watch-only, 只持 xpub)
             ├ TronGrid watcher 轮询到账 + 确认
             └ 到账 ──epay notify──▶ 独角数卡「已支付」
                       │
                       ▼ 独角数卡 API-提货
             pangolin-pay /issue ──HMAC──▶ pangolin 控制面 POST /internal/codes/mint
                                               └ 生成合法码 + 存 unused + 返回明文
                       │
                       ▼ 独角数卡把码交付买家
买家 ──输码──▶ 客户端/用户中心 POST /v1/redeem ──▶ 订阅生效

安全红线(贯穿全程):① 热服务 pangolin-pay 只持 xpub 派生收款地址、绝不持私钥(能收不能转, 被脱库也转不走钱);② 私钥/助记词冷备 + Bitwarden,不入 git、不上热机;③ 收款终点=自托管钱包,变现经 交易所出金 US LLC,记账走 accounting agent;④ 全程不碰跑分/中国支付。

主体归属pangolin-pay + 独角数卡部署在独立海外小 VPS(不放 pangolin1:内存紧 + 风险隔离), 只经 HTTPS + HMAC 与 pangolin 控制面(api.yanmeiai.com)通信。

参考文档:docs/payment-a-selfhosted-store-plan.html / payment-channels-overview.html / payment-reseller-fulfillment-design.html


Phase 0 — 计划落库 + todo

  • 0.1 本 .md 定稿 + 生成 HTML 阅读版 docs/payment-clean-usdt-plan.html,登记进 docs/index.html「实现计划」
  • 0.2 /todo 把 #34 propose 为「待确认」(tier-1--ref 指向本 .md),等用户 approve 再进 Phase 1

Phase 0.5 — 钱包选型(用哪个钱包)

先厘清:这套架构的「钱包」是两件事,别当成装一个 app。

  • 主种子(钱在哪):一套 BIP39 助记词,冷存。这是资产本体。
  • watcher 收款(派生地址):热服务用从 account **xpub(公钥)**派生每单地址——watch-only,不是钱包 app,也拿不到私钥。
  • 归集签名:需要动钱时,在冷端用私钥离线签 sweep 交易。

所以「用哪个钱包」= 选①主种子怎么冷存 + ②watcher 用哪个派生库。

① 主种子 / 冷存 候选

方案 冷存安全 xpub 导出(给 watcher) 归集签名 结论
离线生成助记词 + 库派生(推荐) 高(纸/钢板离线保存,永不联网) 直接从种子导 account xpub 离线机用库签 最贴合 watch-only 架构、最灵活
Ledger 硬件钱包(可叠加) 最高(私钥永不出设备) 可导 account 公钥(需配套工具) 插 Ledger 签 想再加一层硬件冷存就用它兜底
TronLink / imToken / Trust(手机热钱包) 低(热钱包) 不便批量 per-order 派生 app 内签 不推荐当收款主钱包(热 + 难集成)

② watcher 派生库 候选(给热服务 watch-only 派生 TRON 地址,非钱包)

语言
Go(与 server 同栈,推荐) tyler-smith/go-bip39(助记词)+ btcsuite/btcd/btcutil/hdkeychainBIP32 派生)→ 公钥转 TRON base58 地址(0x41 前缀 + Keccak);或成熟 TRON Go SDK
Python tronpy + bip_utils(一步到位派生 TRON 地址)

推荐: 主种子用 离线生成 BIP39 助记词(想再稳就叠一层 Ledger 冷存);watcher 用 Go 库从 account xpub watch-only 派生(与 server 同栈、好集成、热机无私钥)。收款钱包 = 你自己的 HD 种子,不是某个 app。

  • 0.5.1 定主种子方案:离线助记词(推荐)/ 是否叠加 Ledger 冷存;记录选型入本计划
  • 0.5.2 定 watcher 派生库(Go bip39+hdkeychain→TRON 地址 / Python tronpy+bip_utils),跑通派生并与钱包 app 交叉核对同一地址(防派生实现不一致收不到钱)
  • 0.5.3 定归集冷签工具(离线机脚本 / Ledger),确认能对 sweep 交易离线签名

Phase 1 — 自托管钱包(TRC20 HDwatch-only 收款)

目标:一套助记词派生无限个收款地址,热服务只拿 xpub(公钥)派生地址收款、拿不到私钥;私钥冷备用于归集。

  • 1.1 离线生成 BIP39 助记词(24 词)。TRON 派生路径 m/44'/195'/0'/0/i195=TRON coin type)。 工具:离线机器用 tronpy / tronweb 或硬件钱包(Ledger,导出 account xpub)。生成过程不联网。
  • 1.2 密钥分级存放:
    • 助记词/私钥 → 冷备(纸/钢板离线)+ Bitwarden 记录,永不上热机、不入 git
    • account xpub(公钥) → 给 pangolin-pay 用来 watch-only 派生收款地址(无私钥)
    • HMAC secretPhase 3/4 用)→ Bitwarden
  • 1.3 定冷钱包归集地址cold sweep 目标)+ 记录 USDT-TRC20 合约地址(主网 TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t核对无误再写死
  • 1.4 备少量 TRX 作归集 gasTRON 转账吃 energy/bandwidth,账户无 energy 时烧 TRX);记 energy 租赁备选
  • 1.5 验证:用 xpub 离线派生前 5 个地址;往 index 0 打 1 USDT 小额测试,确认 watch-only 能查到该地址收款

Phase 2 — pangolin-payTronGrid watcher(自建收款侦测)

目标:一个独立 Go 服务,派生每单唯一地址、轮询 TronGrid 侦测到账、确认后置单已付。

  • 2.1 建服务骨架 pangolin-payGo,复用 server 的 DB 方言层思路;独立仓/独立 cmd 均可)。自带轻量 DB(SQLite 即可)
  • 2.2 数据模型:
    • pay_orders(order_no PK, sku, plan, duration_days, addr_index, address, expect_amount, status, tx_id, confirmations, created_at, expires_at)
    • addr_cursor(next_index) — HD 派生游标,单调递增
  • 2.3 每单唯一地址:建单时从 xpub 派生 m/.../0/next_index 分配给该单(最准,免金额撞车)。次选:单地址+唯一金额(备选降级)
  • 2.4 TronGrid 轮询:对每个 pending 单地址查 TRC20 转入
    • endpointGET https://api.trongrid.io/v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT>
    • 比对:to==addresstoken==USDTvalue≥expect_amount(含小额容差处理)
  • 2.5 确认数:只认 only_confirmed(已固化)交易,或等 ≥N 区块(TRON 约 1 分钟)。到账+确认 → status=paid、记 tx_id
  • 2.6 到账后触发发货(Phase 4 的 epay notify);幂等:同 tx_id 只认一次,一个 order 只发一次货
  • 2.7 订单超时(默认 15min)未付 → status=expired、释放地址(地址不复用,避免旧付款串单)
  • 2.8 TronGrid API key + 限流退避 + 失败重试;watcher 崩溃可恢复(从 DB 未决单继续轮询)
  • 2.9 测试:mock TronGrid → 单测 金额匹配 / 确认门槛 / 超时 / 幂等(同 tx 重放)/ 崩溃恢复

Phase 3 — pangolin 控制面:JIT 发码端点 POST /internal/codes/mint

目标:付款确认时即时生成一个合法激活码(这才是 webhook JIT 的落点:售出才产码、才有效)。 现有 /webhook/store/codes 要求调用方自带合法码;自建场景由 pangolin 产码更干净(保证格式+校验位单源)。

  • 3.1 新增 POST /internal/codes/mint(挂 /internal 组,无 JWTHMAC 同 webhook X-Pangolin-Signature: sha256=<hmac> + X-Pangolin-Timestamp(±5min) + X-Pangolin-Nonce(Redis 去重)
  • 3.2 body {plan: free|pro|team, duration_days, order_ref} → 复用 codes.GenerateCode() 产码 + Store.CreateCode(hash, planID, duration, batchID) 存 unusedchannel=ChannelStore)→ 返回 {code, expires_at}
  • 3.3 幂等:以 order_ref 为幂等键(存 order_ref→code_hash 映射);同单重放返回同一个码,绝不重复发
  • 3.4 secret 走 Bitwarden → server.envCODES_MINT_HMAC_SECRET);路由挂载(main.go),仅 HMAC 保护、不经公网 JWT
  • 3.5 复用现有 webhook 的验签/时间戳/nonce 逻辑(抽公共中间件,避免重复实现)
  • 3.6 测试:验签通过/失败、时间戳越界、nonce 重放、order_ref 幂等、产码格式(Canonicalize 可还原+校验位对)

Phase 4 — 独角数卡对接(门面 + 自动发货)

目标:独角数卡做门面/订单/交付,pangolin-pay 做 USDT 支付口 + 发货时取码。

  • 4.1 (研究·先做)摸清所用独角数卡版本的支付接口(epay 兼容规范)API-提货/自动发货商品机制 (版本相关,需实测;产出对接点清单再实现)
  • 4.2 pangolin-pay 实现 epay 兼容支付接口:独角数卡建单 → 跳我方支付页(展示该单 USDT 地址 + 应付金额 + 倒计时 + 网络必须 TRC20 强提示)
  • 4.3 到账(Phase 2.5)→ 按 epay 规范 notify 独角数卡(带签名)→ 独角数卡标「已支付」
  • 4.4 商品设为 API-提货:独角数卡发货时调 pangolin-pay /issue → 内部以 HMAC 调 pangolin /internal/codes/mint 取合法码 → 返回独角数卡 → 交付买家(页面/邮件/站内)
  • 4.5 SKU 映射表(配置化,不散在代码):独角数卡商品 → plan + duration_days Pro 月=pro/30、Pro 年=pro/365、Team 月/年=team/30|365;对齐 design/CLAUDE.md §7 与官网 Pricing
  • 4.6 幂等贯通:独角数卡 order_no 作为 order_ref 一路透传到 mint,保证一单一码

Phase 5 — 部署 · 运维 · 安全

  • 5.1 独立海外小 VPS(2C/2G 起)部署:独角数卡(DockerPHP+MySQL+Redis+ pangolin-pay + 域名 + HTTPS 不放 pangolin1
  • 5.2 密钥落位核对:pangolin-pay 只有 xpub + TronGrid key + epay 签名密钥 + mint HMAC secret 无私钥/助记词(冷备)。全部 Bitwarden,不入 git
  • 5.3 归集 runbook:定期把各收款地址 USDT sweep 到冷钱包(离线签名,热机不碰私钥);备 TRX gas
  • 5.4 变现 runbook:冷钱包 → 交易所(能出金到 US LLC 的,自行核实)→ 卖 USDT→USD → US LLC 银行; 入账交 code/accounting agent(本项目不做费用台账)
  • 5.5 监控告警(接现有 Telegram 告警):watcher 存活、TronGrid 配额、到账延迟、mint 失败、订单异常堆积
  • 5.6 对账:独角数卡订单 ↔ pay_orders ↔ codesmint 条数/redeem 条数)三方日结,差异告警

Phase 6 — 端到端验证

  • 6.1 真单闭环:下单 → USDT 付款(TRC20)→ watcher 确认 → mint 码 → 独角数卡交付 → 客户端/用户中心 POST /v1/redeem → 订阅/时长生效、全端同步
  • 6.2 异常路径:超时未付 / 金额不足 / 重复付款 / 确认期网络抖动 / mint 重放 → 各自表现符合预期
  • 6.3 安全核对:热服务 otool/进程无私钥;密钥不在 git(红线扫描);redeem 幂等+限频生效; 每单唯一地址不复用

Verification(端到端判据)

  • 后端:cd server && go test ./internal/codes/...(新增 mint 端点验签/幂等/格式测试全绿)。
  • pangolin-paygo test ./...watcher 匹配/确认/超时/幂等/恢复)。
  • 真机:一笔真 USDT 小额单,从下单到订阅生效全通;异常路径逐条演练。
  • 安全:热服务无私钥、密钥不入 git、每单唯一地址、redeem 幂等限频——逐项核对。

不在本轮

  • 支付宝/微信第三方通道(E 层,灰产,另议且当可替换插件)。
  • HD 自动 sweep 归集(先人工离线归集,量大再自动化)。
  • 多币种(先只 USDT-TRC20)、watcher 高可用/多实例、加密网关备选路子(B 方案)。
  • Telegram Stars / Apple IAP 独立收款口(海外补充,另排)。