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
+12 -8
View File
@@ -18,10 +18,13 @@ const USDTContractMainnet = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
// Transfer is one confirmed incoming TRC20 transfer to a watched address.
// Value is the raw integer amount (micro-USDT, since USDT has 6 decimals).
// BlockTs is the on-chain block time in **unix seconds** — used to reject a
// payment that arrived before the order it might match was created.
type Transfer struct {
TxID string
To string
Value int64
TxID string
To string
Value int64
BlockTs int64
}
// Fetcher returns confirmed incoming USDT transfers to a given address.
@@ -67,10 +70,11 @@ func (c *Client) IncomingTransfers(ctx context.Context, address string) ([]Trans
}
var body struct {
Data []struct {
TransactionID string `json:"transaction_id"`
To string `json:"to"`
Value string `json:"value"`
Type string `json:"type"`
TransactionID string `json:"transaction_id"`
To string `json:"to"`
Value string `json:"value"`
Type string `json:"type"`
BlockTimestamp int64 `json:"block_timestamp"` // milliseconds
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
@@ -85,7 +89,7 @@ func (c *Client) IncomingTransfers(ctx context.Context, address string) ([]Trans
if err != nil {
continue // skip malformed value rather than fail the whole batch
}
out = append(out, Transfer{TxID: d.TransactionID, To: d.To, Value: v})
out = append(out, Transfer{TxID: d.TransactionID, To: d.To, Value: v, BlockTs: d.BlockTimestamp / 1000})
}
return out, nil
}