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:
@@ -75,10 +75,21 @@ func main() {
|
||||
|
||||
log := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
|
||||
xpub := os.Getenv("PAY_ACCOUNT_XPUB")
|
||||
if xpub == "" {
|
||||
log.Error("PAY_ACCOUNT_XPUB is required (watch-only account xpub, m/44'/195'/0')")
|
||||
os.Exit(1)
|
||||
// Single fixed receiving address: either given directly, or derived as index 0
|
||||
// of the watch-only account xpub (= wallet A address 0).
|
||||
receiveAddr := os.Getenv("PAY_RECEIVE_ADDRESS")
|
||||
if receiveAddr == "" {
|
||||
xpub := os.Getenv("PAY_ACCOUNT_XPUB")
|
||||
if xpub == "" {
|
||||
log.Error("set PAY_RECEIVE_ADDRESS, or PAY_ACCOUNT_XPUB to derive address 0")
|
||||
os.Exit(1)
|
||||
}
|
||||
a, err := wallet.AddressFromAccountXpub(xpub, 0, 0)
|
||||
if err != nil {
|
||||
log.Error("derive receive address from xpub", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
receiveAddr = a
|
||||
}
|
||||
dbPath := env("PAY_DB", "pay.db")
|
||||
addr := env("PAY_ADDR", ":8090")
|
||||
@@ -94,9 +105,10 @@ func main() {
|
||||
}
|
||||
defer func() { _ = st.Close() }()
|
||||
|
||||
svc := pay.New(st, pay.Config{AccountXpub: xpub, OrderTTL: 15 * time.Minute})
|
||||
svc := pay.New(st, pay.Config{ReceiveAddress: receiveAddr, OrderTTL: 15 * time.Minute})
|
||||
fetcher := tron.NewClient(env("TRONGRID_BASE", ""), env("USDT_CONTRACT", ""), os.Getenv("TRONGRID_API_KEY"))
|
||||
w := watcher.New(st, fetcher, log)
|
||||
w := watcher.New(st, fetcher, receiveAddr, log)
|
||||
log.Info("receiving address", "address", receiveAddr)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
Reference in New Issue
Block a user