pay-server 自带 /_test 测试页:创建订单→显示收款地址+精确金额(可复制)
+过期倒计时→轮询状态到 paid。同源、无 CORS/CSP 摩擦,不碰营销站安全头;
纯调 POST /order + GET /order/{id},无私钥无密钥。生产收银台后续走独角数卡门面。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13 KiB
收款闭环 · 独角数卡 + 自托管 USDT(TRC20)+ webhook JIT 发码(最干净长期方案)
#34。目标:把「下单 → USDT 付款 → 自动发激活码 → 客户端/用户中心兑换 → 订阅生效」端到端打通, 全程自托管、售出才发码、无跑分、无第三方支付主体。
关键前提(已摸底):激活码后端大半已就绪——
POST /v1/redeem(JWT 兑换,含幂等+限频+账户锁)、 批次生成/导出(server/cmd/codegen+server/internal/codes,码为 Crockford Base32 75-bit 带校验位、只存 hash)、发卡店回调POST /webhook/store/codes(HMAC+时间戳+nonce 防重放)都在。本方案新增的核心只有一个 后端端点(JIT 发码/internal/codes/mint)+ 一个独立收款服务(pangolin-pay:TronGrid 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/hdkeychain(BIP32 派生)→ 公钥转 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 HD,watch-only 收款)
目标:一套助记词派生无限个收款地址,热服务只拿 xpub(公钥)派生地址收款、拿不到私钥;私钥冷备用于归集。
- 1.1 离线生成 BIP39 助记词(24 词)。TRON 派生路径
m/44'/195'/0'/0/i(195=TRON coin type)。 工具:离线机器用tronpy/tronweb或硬件钱包(Ledger,导出 account xpub)。生成过程不联网。 - 1.2 密钥分级存放:
- 助记词/私钥 → 冷备(纸/钢板离线)+ Bitwarden 记录,永不上热机、不入 git
- account xpub(公钥) → 给
pangolin-pay用来 watch-only 派生收款地址(无私钥) - HMAC secret(Phase 3/4 用)→ Bitwarden
- 1.3 定冷钱包归集地址(cold sweep 目标)+ 记录 USDT-TRC20 合约地址(主网
TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t,核对无误再写死) - 1.4 备少量 TRX 作归集 gas(TRON 转账吃 energy/bandwidth,账户无 energy 时烧 TRX);记 energy 租赁备选
- 1.5 验证:用 xpub 离线派生前 5 个地址;往 index 0 打 1 USDT 小额测试,确认 watch-only 能查到该地址收款
Phase 2 — pangolin-pay:TronGrid watcher(自建收款侦测)
目标:一个独立 Go 服务,派生每单唯一地址、轮询 TronGrid 侦测到账、确认后置单已付。
- 2.1 建服务骨架
pangolin-pay(Go,复用 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 转入
- endpoint:
GET https://api.trongrid.io/v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT> - 比对:
to==address、token==USDT、value≥expect_amount(含小额容差处理)
- endpoint:
- 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组,无 JWT,HMAC 同 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)存 unused(channel=ChannelStore)→ 返回{code, expires_at} - 3.3 幂等:以
order_ref为幂等键(存order_ref→code_hash映射);同单重放返回同一个码,绝不重复发 - 3.4 secret 走 Bitwarden →
server.env(CODES_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 起)部署:独角数卡(Docker:PHP+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/accountingagent(本项目不做费用台账) - 5.5 监控告警(接现有 Telegram 告警):watcher 存活、TronGrid 配额、到账延迟、mint 失败、订单异常堆积
- 5.6 对账:独角数卡订单 ↔
pay_orders↔ codes(mint 条数/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-pay:
go test ./...(watcher 匹配/确认/超时/幂等/恢复)。 - 真机:一笔真 USDT 小额单,从下单到订阅生效全通;异常路径逐条演练。
- 安全:热服务无私钥、密钥不入 git、每单唯一地址、redeem 幂等限频——逐项核对。
不在本轮
- 支付宝/微信第三方通道(E 层,灰产,另议且当可替换插件)。
- HD 自动 sweep 归集(先人工离线归集,量大再自动化)。
- 多币种(先只 USDT-TRC20)、watcher 高可用/多实例、加密网关备选路子(B 方案)。
- Telegram Stars / Apple IAP 独立收款口(海外补充,另排)。