feat(pay): 收款模型改为单地址+唯一金额(#34/34A Phase A-C)

从"每单唯一 HD 地址"改为"单个固定收款地址 + 每单唯一金额",归集成本 O(订单数)→O(1)。
- store: pay_orders 加 user_ref/expect_amount(唯一金额)/matched_tx_id;新 orphan_payments 表;
  ActiveOrderByUser(同用户单订单)、AmountRecentlyUsed(迟到窗口内金额不复用)、TxHandled(幂等)、
  RecordOrphan。去掉每单派生游标。
- pay: CreateOrder(userRef,sku,priceMicro)——同用户单订单校验 + 分配唯一金额(base+随机微尾数[1,9999]、
  cooldown 内不复用),address 恒为收款地址。
- tron: Transfer 加 BlockTs(区块时间秒),取 block_timestamp。
- watcher: 单地址取到账,按"金额==expect && block_ts>建单"匹配 → paid;不匹配的到账 → orphan;幂等。
- httpapi: POST /order 加 user_ref,同用户重复 → 409;main 收款地址=PAY_RECEIVE_ADDRESS 或 xpub index0。
- 测试:唯一金额/同地址、同用户单订单、精确匹配、付错成孤儿、迟到不误配新单、付款早于建单不匹配、
  超时、幂等、409,全绿。README 更新为单地址模型+API(user_ref/精确金额/orphan)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-09 15:15:38 +08:00
parent 543a54c606
commit c5949a595a
11 changed files with 535 additions and 240 deletions
+16 -5
View File
@@ -1,8 +1,13 @@
# pangolin-pay
自托管 USDT(TRC20) 收款服务。给每个订单派生一个**唯一收款地址**(watch-only,从账户
xpub 派生,**不持私钥**),轮询 TronGrid 侦测到账、确认后标记订单已付。归集(把钱扫到冷
钱包)是**独立的离线步骤**,本服务不碰私钥。
自托管 USDT(TRC20) 收款服务。**单个固定收款地址 + 每单唯一金额**:所有订单收到同一个地址,
靠**唯一金额**(基准价 + 微尾数,≤0.01 USDT)区分。watcher 轮询 TronGrid,按
**"到账 tx + 精确金额 + 区块时间晚于建单"** 匹配订单;匹配不到活跃订单的到账(付错/迟到)→
`orphan_payments` 人工对账。归集(把钱扫到冷钱包)是**独立离线步骤**,本服务**不持私钥**。
> 收款地址 = 钱包 A 的地址 0(`m/44'/195'/0'/0/0`),配置注入或由 xpub 派生。
> 前端契约(`POST /order` 返回 address+expect_amount)与地址模型解耦——以后要升多地址/GasFree
> 是纯后端换实现,前端零改动。模型定稿见 `docs/pay-single-address-plan.html`。
> 属 #34「独角数卡 + USDT 收款闭环」的加密货币交易引擎(计划见
> `docs/superpowers/plans/2026-07-09-crypto-tx-engine.md`)。概念见 brain
@@ -74,13 +79,19 @@ go run ./cmd/paywatch
## API
```
POST /order {"sku":"pro-year","amount":5000000} # amount = micro-USDT(1e-6)
POST /order {"user_ref":"buyer123","sku":"pro-year","amount":5000000} # amount = 基准价 micro-USDT(1e-6)
→ 201 {"order_no","address","expect_amount","status":"pending","expires_at"}
# expect_amount = 基准 + 唯一微尾数;支付页要显示"请付精确金额 expect_amount"
→ 409 {"error":"user already has an active order"} # 同一 user_ref 同时只能一个活跃订单
GET /order/{orderNo} → 200 {..., "status":"pending|paid|expired","tx_id"}
GET /healthz → 200 ok
```
门面(独角数卡)下单时调 `POST /order` 拿收款地址;支付页轮询 `GET /order/{id}` 直到 `paid`
门面(独角数卡)下单时调 `POST /order`(带 `user_ref`)拿 **address + expect_amount**;
支付页显示"往 address 付**精确的** expect_amount"(可复制),轮询 `GET /order/{id}` 直到 `paid`
用户付错金额 → 该到账进 orphan,需人工对账。
配置:`PAY_RECEIVE_ADDRESS`(单收款地址)或 `PAY_ACCOUNT_XPUB`(自动派生 index 0)。
## Phase D —— 归集(气隙签名,`cmd/sweep`)