Files
pangolin/docs/superpowers/plans/2026-07-09-pay-single-address.md
T
wangjia 543a54c606 docs(pay): 收款模型定稿为单地址+唯一金额,取代地址池 plan(#34/34A)
单个固定收款地址 + 每单唯一金额(base+微尾数≤0.01U)+ 精确==匹配 + 时间戳防迟到误配 +
孤儿人工对账。归集=1地址(激活一次/扫一笔)最省。前端契约不变(POST /order 返 address+amount),
以后升多地址/GasFree 纯后端切。删除已被取代的地址池 plan。双产物 md+html,登记 index。

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

70 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pangolin-pay 单地址 + 唯一金额收款模型(定稿)
> #34/34A 收款模型**定稿**。取代"每单唯一 HD 地址"(激活/归集随订单数线性涨)与"地址池"(仍多地址)。
> 最终选:**单个固定收款地址 + 每单唯一金额**。归集 = **1 个地址**(激活一次、扫一笔),成本最省;
> 订单靠**金额**区分,不靠地址。
>
> **前端契约不变**:`POST /order` 仍返回 `{address, expect_amount}`,client 只用每单返回值 → 以后要升
> 多地址/GasFree 是**纯后端换实现**,前端零改动(见下"契约约定")。
>
> 已实现的 wallet 派生 / tron(TronGrid 读到账、建交易、签名)/ cmd/sweep 复用;主要改 **store / pay(建单)/
> watcher(匹配)**。现有代码是"每单派生新址",本轮改为"单地址 + 唯一金额"。
## 决策(已定)
- **收款地址**:钱包 A 的地址 0(`m/44'/195'/0'/0/0`),从配置注入或由 xpub 派生。所有订单收到**这一个地址**。
- **唯一金额** ⭐:`expect_amount = base + tail`。base = 价格 ×1e6(micro-USDT);**tail 取方案 A(微尾数)**:
`tail ∈ [1, 9999]` micro(偏差 ≤ 0.009999 USDT,**价格几乎不变**)。tail 在**迟到窗口内不复用**。
- **匹配 = 精确 `==`**:watcher 找"到收款地址、`value == expect_amount``block_ts > order.created`"的 TRC20 转入 → paid。
支付页显示**可一键复制的精确金额** + "请付精确金额"提示。
- **15min 超时** → expired → 提示重建订单(该 tail 一段时间内不复用)。
- **同用户单订单**:同时只能一个活跃(pending)订单。
- **孤儿付款**:到该地址但 `value` 不匹配任何活跃订单(付错/迟到抹了尾数)→ 记 `orphan_payments`,人工对账/补发。
- **归集**:定期扫**这一个地址**余额 → 冷钱包(**能量租赁** ~$0.1–1/笔)。激活一次、归集一笔。
## 契约约定(钉死,保证以后单↔多地址纯后端)
> **client(下单页/独角数卡)只使用每单 `POST /order` 返回的 `address` + `expect_amount`,绝不硬编码/缓存地址。**
> 只要守住这条,单地址 ↔ 多地址 ↔ GasFree 都是后端内部换实现,前端与 VPN app 都不改。
## Phase A — 数据模型
- [ ] `pay_orders` 改:加 `user_ref``expect_amount`(唯一金额,micro-USDT)、`matched_tx_id`;`address` 恒为收款地址。
- [ ] tail 分配支撑:记录**当前活跃订单已用 tail**(避免撞)+ **近期已用 tail 冷却**(迟到窗口内不复用);或全局计数器 + 去重校验。
- [ ] `orphan_payments` 表:`tx_id(唯一) / value / block_ts / created_at / handled(bool)`
- [ ] 配置:`PAY_RECEIVE_ADDRESS`(或从 `PAY_ACCOUNT_XPUB` 派生 index 0,与钱包 A 地址 0 一致)。
## Phase B — 建单(pay 服务)
- [ ] `CreateOrder(userRef, sku, priceMicro)`:① 校验**同用户无活跃单**(有则返回现有/拒);② 分配**唯一金额**(base + 未占用 tail);③ 写 `pending` 单(TTL 15min),`address` = 收款地址。
- [ ] **tail 分配**:与当前所有活跃订单不撞 + 迟到窗口内不复用(记录+回收)。
- [ ] `GetOrder`
## Phase C — watcher 改造(匹配 / 超时 / 孤儿)
- [ ] **匹配**:对每个 `pending` 单,查**收款地址**的 TRC20 转入,筛 `value == expect_amount` **且 `block_ts > order.created`**`paid` + 记 `matched_tx_id`
- [ ] **超时**:`pending` 过期 → `expired`
- [ ] **孤儿**:收款地址的到账 tx 匹配不到任何活跃订单 → 记 `orphan_payments`(按 tx_id 去重)。
- [ ] **幂等**(tx_id 全局去重,含 orphan)+ **崩溃恢复**;TronGrid 需取 `block_timestamp` 供时间过滤。
## Phase D — 归集(复用 cmd/sweep)
- [ ] 扫**收款地址**余额 → 冷钱包(**能量租赁**,先手动 runbook);一地址一笔,与订单状态解耦。
## Phase E — 测试 + 真链验证
- [ ] 单测:唯一金额分配不撞、精确匹配、**付错/迟到金额成孤儿**、超时、同用户单订单、幂等、**时间戳过滤**(旧到账不误配新单)、崩溃恢复。
- [ ] 真链:两并发订单**不同唯一金额、同一地址**各自付款到 paid;故意**付错金额** → 成孤儿不误配;归集该地址到冷钱包。
## Verification / 判据
- `go test ./...`(store/pay/watcher 新逻辑全绿,重点覆盖唯一金额/精确匹配/孤儿/时间戳)。
- 真链:并发不同金额 + 付错成孤儿 两场景正确;归集一笔搞定。
- 成本:激活一次、归集 O(1);小额高频不再被激活/归集费拖累。
## 不在本轮 / 以后(纯后端可切)
- 多地址 / 地址池(容错更好但更贵,以后纯后端切,前端不动)。
- GasFree(#35,免 TRX 但每笔固定 1 USDT,适合少地址批量)。
- 能量租赁自动化;发货侧(独角数卡 + /internal/codes/mint)另排。