feat(pay): 同源收款测试页 GET /_test(Phase E 手动闭环验证)
pay-server 自带 /_test 测试页:创建订单→显示收款地址+精确金额(可复制)
+过期倒计时→轮询状态到 paid。同源、无 CORS/CSP 摩擦,不碰营销站安全头;
纯调 POST /order + GET /order/{id},无私钥无密钥。生产收银台后续走独角数卡门面。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>pangolin-pay 加密货币交易引擎 实现计划</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:29px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||||
h2{font-size:20px;margin:40px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);color:var(--accent)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.85em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.5;color:#cdd3df}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
ul.ck{list-style:none;padding-left:4px}
|
||||
ul.ck li{position:relative;padding-left:26px;margin:7px 0}
|
||||
ul.ck li::before{content:"☐";position:absolute;left:0;color:var(--fg2)}
|
||||
ul{padding-left:22px}
|
||||
li{margin:6px 0}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
b{color:#fff}
|
||||
.warnbox{background:rgba(224,106,106,.08);border:1px solid rgba(224,106,106,.35);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.warnbox b{color:var(--bad)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
<h1>pangolin-pay 加密货币交易引擎</h1>
|
||||
<p class="sub">2026-07-09 · <span class="tag info">阅读版</span> · 执行真相源 <code>docs/superpowers/plans/2026-07-09-crypto-tx-engine.md</code> · #34 的第一块(可独立执行 + 真实小额验证)</p>
|
||||
|
||||
<div class="lead">
|
||||
#34 的<b>第一块、可独立执行、能用真实小额 USDT 端到端验证</b>的基础件。<b>只做加密货币"交易"核心</b>——派生地址、侦测到账、归集(sweep)——<b>不含</b>独角数卡对接、不含发码。成果=独立 Go 服务 <code>pangolin-pay</code>,提供"建单→收款地址→侦测到账→标记已付"+ 离线归集工具。跑通后任何门面都能挂上来。
|
||||
</div>
|
||||
|
||||
<div class="warnbox">
|
||||
<b>安全铁律:</b>热服务只持 <b>xpub</b>(watch-only 派生)+ TronGrid key + 金库收款地址,<b>绝不持私钥</b>;私钥/助记词冷存,归集在离线端签名。关键常量(核对无误再写死):USDT-TRC20 合约 <code>TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t</code>(6 位小数)· 派生 <code>m/44'/195'/0'/0/i</code> · TronGrid <code>api.trongrid.io</code>。
|
||||
</div>
|
||||
|
||||
<h2>Phase A — 钱包与密钥(全程离线)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>A.1</b> 离线生成<b>两套</b>助记词(断网,Ian Coleman/bip_utils,TRX/24词/English):钱包 A 运营收款、钱包 B 冷备金库。</li>
|
||||
<li><b>A.2</b> 导出<b>钱包 A account xpub</b>(<code>m/44'/195'/0'</code>);取<b>钱包 B 地址0</b> 当归集目标。</li>
|
||||
<li><b>A.3</b> 冷存助记词 A/B(分开)+ Bitwarden;只把 xpub_A + sweep_addr_B 放进配置。</li>
|
||||
<li><b>A.4</b> <b>交叉核对</b>:Go 派生出的地址 0..4 必须与 Ian Coleman 逐个一致(防收款地址对不上)。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase B — 服务骨架 + HD 派生</h2>
|
||||
<ul class="ck">
|
||||
<li><b>B.1</b> 建 Go 服务 <code>pangolin-pay</code>(独立)。配置:xpub_A / sweep_addr_B / TronGrid key / 确认数 / 超时。自带 SQLite。</li>
|
||||
<li><b>B.2</b> HD 派生:go-bip39 + hdkeychain 从 xpub_A watch-only 派生 <code>.../0/i</code> 公钥 → TRON 地址(Keccak 后20字节 + <code>0x41</code> + Base58Check)。<b>单测对齐 Ian Coleman</b>。</li>
|
||||
<li><b>B.3</b> 数据模型:<code>pay_orders(order_no, sku, expect_amount, addr_index, address, status, tx_id, confirmations, created_at, expires_at)</code> + <code>addr_cursor(next_index)</code>(地址不复用)。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase C — 收款(建单 + 侦测到账)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>C.1</b> <code>POST /order</code> → cursor++ 派生新地址 → 写单(expect_amount 唯一尾数、expires +15min)→ 返回 <code>{order_no, address, expect_amount, expires_at}</code>。</li>
|
||||
<li><b>C.2</b> <code>GET /order/{order_no}</code> → status(pending/paid/expired)。</li>
|
||||
<li><b>C.3</b> TronGrid watcher 轮询:<code>/v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT></code>,匹配 to+金额(6位小数)+确认 → paid、记 tx_id。</li>
|
||||
<li><b>C.4</b> 幂等(同 tx 只认一次)+ 超时 expired(地址不复用)+ 崩溃恢复。</li>
|
||||
<li><b>C.5</b> 限流退避 + 重试;mock TronGrid 测试:匹配/确认/超时/幂等/恢复。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase D — 归集(sweep,离线签名)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>D.1</b> watcher(xpub)列出有余额地址清单。</li>
|
||||
<li><b>D.2</b> gas:gas 钱包给待归集地址打少量 TRX(或能量租赁)。</li>
|
||||
<li><b>D.3</b> 离线机导入<b>钱包 A 助记词</b> → 派生 address_i 私钥 → 构造 TRC20 <code>transfer(sweep_addr_B, balance)</code> → <b>离线签名</b>。</li>
|
||||
<li><b>D.4</b> 广播:签好的交易经 TronGrid <code>/wallet/broadcasttransaction</code> 上链(私钥不出门)。</li>
|
||||
<li><b>D.5</b> 半自动脚本 + 归集 runbook;阈值/定时触发;热地址不囤大额。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase E — 端到端验证(真实小额)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>E.1</b> 真单:<code>POST /order</code> → 转 1 USDT(TRC20)→ watcher 确认 → status=paid。</li>
|
||||
<li><b>E.2</b> 归集:D 流程 → USDT 到钱包 B 冷地址。</li>
|
||||
<li><b>E.3</b> 安全核对:热服务无私钥、密钥不入 git、地址不复用。</li>
|
||||
</ul>
|
||||
|
||||
<h2>不在本轮</h2>
|
||||
<ul>
|
||||
<li>独角数卡门面对接 + epay 支付口(下一块:接门面)。</li>
|
||||
<li><code>/internal/codes/mint</code> JIT 发码 + 兑换闭环(发货侧,#34 后续)。</li>
|
||||
<li>HD 自动 sweep(先人工)、多币种、watcher 高可用。</li>
|
||||
</ul>
|
||||
|
||||
<p class="small" style="margin-top:32px">相关:<a href="payment-clean-usdt-plan.html">#34 收款闭环全景</a> · <a href="payment-channels-overview.html">渠道选型</a> · 知识:brain <code>notes/dev/crypto-hd-wallet-basics.html</code></p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,244 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>方案A · 自建发卡网(独角数卡)落地细化</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||||
h3{font-size:16px;margin:24px 0 8px;color:var(--fg)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.86em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:#cdd3df}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
|
||||
.card h3{margin-top:0;color:var(--accent2)}
|
||||
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:13.5px}
|
||||
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
||||
th{color:var(--fg2);font-weight:600;font-size:13px}
|
||||
ul,ol{padding-left:22px;margin:8px 0}
|
||||
li{margin:5px 0}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
b{color:#fff}
|
||||
.phase{font-size:19px;margin:40px 0 6px;color:var(--accent);font-weight:700}
|
||||
.diagram{overflow-x:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:20px;margin:16px 0}
|
||||
.diagram svg{display:block;width:100%;height:auto;max-width:840px;margin:0 auto}
|
||||
.legend{display:flex;gap:20px;flex-wrap:wrap;font-size:13px;color:var(--fg2);margin-top:12px;justify-content:center}
|
||||
.legend span{display:inline-flex;align-items:center;gap:6px}
|
||||
.legend i{width:22px;height:0;border-top-width:3px;border-top-style:solid;display:inline-block}
|
||||
.warnbox{background:rgba(224,106,106,.08);border:1px solid rgba(224,106,106,.35);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.warnbox b{color:var(--bad)}
|
||||
.okbox{background:rgba(94,194,122,.07);border:1px solid rgba(94,194,122,.32);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.done{color:var(--ok);font-weight:700}
|
||||
.todo{color:var(--warn);font-weight:700}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
<h1>方案 A · 自建发卡网(独角数卡)落地细化</h1>
|
||||
<p class="sub">2026-07-08 · <span class="tag info">实现计划</span> · 门面自建 + USDT 收款 + 激活码自动发货 · 基于<b>已就绪</b>的 <code>server/internal/codes</code></p>
|
||||
|
||||
<div class="lead">
|
||||
<b>关键前提(好消息):</b>激活码后端<b>基本已就绪</b>——发卡店回调 <code>POST /webhook/store/codes</code>(HMAC + 时间戳 + nonce 防重放)、用户兑换 <code>POST /v1/redeem</code>(JWT)、批次生成/导出/作废都在 <code>server/internal/codes</code>。所以方案 A 的工作量集中在<b>门面(独角数卡)+ 一小段发货胶水 + 运维</b>,<b>不是重写后端</b>。
|
||||
</div>
|
||||
|
||||
<h2>已就绪的后端契约(照这个对接,别新造)</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="done">已实现</span> · 发卡店回调 <code>POST /webhook/store/codes</code></h3>
|
||||
<p>发卡店卖出一个码后调它,把该码登记为 pangolin 的有效激活码。挂在 <code>/v1</code> 之外、无需 JWT,靠三重校验。</p>
|
||||
<pre>POST /webhook/store/codes # 无 JWT
|
||||
Headers:
|
||||
X-Pangolin-Signature: sha256=<hex> # HMAC-SHA256(raw body, secret)
|
||||
X-Pangolin-Timestamp: <unix> # ±5 分钟窗口
|
||||
X-Pangolin-Nonce: <唯一串> # Redis 去重,防重放
|
||||
Body:
|
||||
{ "code": "<明文激活码>", # 必须是 pangolin 格式合法码(见下)
|
||||
"plan": "pro", # free | pro | team
|
||||
"duration_days": 365,
|
||||
"note": "dujiaoka #订单号" } # 可选备注
|
||||
→ 201 {"status":"created"}
|
||||
→ 200 {"status":"already_exists"} # code_hash 已存在,幂等
|
||||
→ 200 {"status":"duplicate_ignored"} # nonce 重复,幂等
|
||||
→ 400 / 401 # 校验失败</pre>
|
||||
<p class="small">仅存 <b>hash</b>(明文不落库)。secret 走 Bitwarden,配到 <code>server.env</code>。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="done">已实现</span> · 用户兑换 <code>POST /v1/redeem</code></h3>
|
||||
<pre>POST /v1/redeem (JWT)
|
||||
Body: { "code": "PGL-XXXX-..." }
|
||||
→ 200 { "idempotent":false, "plan":"pro", "duration_days":365,
|
||||
"expires_at":"2027-07-08T00:00:00Z" }
|
||||
错误码: CODE_NOT_FOUND / CODE_REDEEMED / CODE_VOID / INVALID_CODE / RATE_LIMITED / ACCOUNT_LOCKED</pre>
|
||||
<p class="small">已含幂等(同用户同码重放返回 idempotent=true)+ 限频 + 账户锁。用户中心/客户端只要有"输码兑换"入口即可。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="done">已实现</span> · 批次生成 / 导出 / 作废</h3>
|
||||
<p><code>server/cmd/codegen</code> 生成一批合法码(Crockford Base32、75-bit、带校验位、可 <code>Canonicalize</code> 纠错),<code>export.go</code> 导出明文,<code>VoidBatch</code> 批量作废未使用码。<b>码格式带校验位——第三方要产出合法码不能乱编</b>(见下模型 B 的待补点)。</p>
|
||||
</div>
|
||||
|
||||
<h2>整体拓扑</h2>
|
||||
<div class="diagram">
|
||||
<svg viewBox="0 0 840 360" role="img" aria-label="自建发卡网拓扑">
|
||||
<defs>
|
||||
<marker id="a-code" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#5fb0c9"/></marker>
|
||||
<marker id="a-money" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#e0884f"/></marker>
|
||||
</defs>
|
||||
<!-- 用户 -->
|
||||
<rect x="30" y="150" width="130" height="60" rx="10" fill="#1d2129" stroke="#272c36"/>
|
||||
<text x="95" y="177" text-anchor="middle" font-size="13" font-weight="700" fill="#e6e8ee">用户</text>
|
||||
<text x="95" y="195" text-anchor="middle" font-size="11" fill="#a8afbd">浏览器 / TG</text>
|
||||
|
||||
<!-- 独角数卡 (独立 VPS) -->
|
||||
<rect x="240" y="40" width="230" height="130" rx="12" fill="#141922" stroke="#5fb0c9"/>
|
||||
<text x="355" y="66" text-anchor="middle" font-size="13" font-weight="700" fill="#5fb0c9">独角数卡(独立 VPS)</text>
|
||||
<text x="355" y="87" text-anchor="middle" font-size="11" fill="#a8afbd">门面 · 商品/SKU · 卡密库存</text>
|
||||
<text x="355" y="104" text-anchor="middle" font-size="11" fill="#a8afbd">USDT 支付插件 → 收款</text>
|
||||
<text x="355" y="121" text-anchor="middle" font-size="11" fill="#a8afbd">PHP + MySQL + Redis</text>
|
||||
<text x="355" y="145" text-anchor="middle" font-size="11" fill="#e0b84f">发货 hook → 回调 pangolin</text>
|
||||
|
||||
<!-- USDT 钱包 -->
|
||||
<rect x="240" y="250" width="230" height="60" rx="10" fill="#1d2129" stroke="#5ec27a" stroke-dasharray="4 3"/>
|
||||
<text x="355" y="277" text-anchor="middle" font-size="12.5" font-weight="700" fill="#e6e8ee">USDT 钱包 / 网关</text>
|
||||
<text x="355" y="295" text-anchor="middle" font-size="11" fill="#a8afbd">TRC20 · 你 US LLC 自托管</text>
|
||||
|
||||
<!-- pangolin -->
|
||||
<rect x="560" y="40" width="250" height="130" rx="12" fill="#141922" stroke="#e0884f"/>
|
||||
<text x="685" y="66" text-anchor="middle" font-size="13" font-weight="700" fill="#e0884f">Pangolin 控制面</text>
|
||||
<text x="685" y="87" text-anchor="middle" font-size="11" fill="#a8afbd">POST /webhook/store/codes</text>
|
||||
<text x="685" y="104" text-anchor="middle" font-size="11" fill="#a8afbd">codes:登记/作废/批次</text>
|
||||
<text x="685" y="126" text-anchor="middle" font-size="11" fill="#a8afbd">POST /v1/redeem(兑换)</text>
|
||||
<text x="685" y="146" text-anchor="middle" font-size="11" fill="#5ec27a">→ 订阅/时长生效</text>
|
||||
|
||||
<!-- 客户端/用户中心 -->
|
||||
<rect x="560" y="250" width="250" height="60" rx="10" fill="#1d2129" stroke="#272c36"/>
|
||||
<text x="685" y="277" text-anchor="middle" font-size="12.5" font-weight="700" fill="#e6e8ee">客户端 / 用户中心</text>
|
||||
<text x="685" y="295" text-anchor="middle" font-size="11" fill="#a8afbd">输码兑换 → /v1/redeem</text>
|
||||
|
||||
<!-- arrows -->
|
||||
<line x1="160" y1="170" x2="238" y2="130" stroke="#e0884f" stroke-width="2.2" marker-end="url(#a-money)"/>
|
||||
<text x="150" y="128" font-size="10.5" fill="#e0884f">①下单付款</text>
|
||||
<line x1="355" y1="170" x2="355" y2="248" stroke="#e0884f" stroke-width="2.2" marker-end="url(#a-money)"/>
|
||||
<text x="362" y="215" font-size="10.5" fill="#e0884f">②USDT 收款</text>
|
||||
<line x1="470" y1="95" x2="558" y2="95" stroke="#5fb0c9" stroke-width="2.2" marker-end="url(#a-code)"/>
|
||||
<text x="482" y="86" font-size="10.5" fill="#5fb0c9">③发货 hook 登记码</text>
|
||||
<line x1="160" y1="188" x2="558" y2="285" stroke="#5fb0c9" stroke-width="2.2" marker-end="url(#a-code)"/>
|
||||
<text x="250" y="245" font-size="10.5" fill="#5fb0c9">④用户拿码 → 输码兑换</text>
|
||||
<line x1="685" y1="248" x2="685" y2="172" stroke="#5fb0c9" stroke-width="2.2" marker-end="url(#a-code)"/>
|
||||
</svg>
|
||||
<div class="legend">
|
||||
<span><i style="border-color:#e0884f"></i>钱流</span>
|
||||
<span><i style="border-color:#5fb0c9"></i>码流</span>
|
||||
<span><i style="border-color:#5ec27a;border-top-style:dashed"></i>自托管资产</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>部署选址 · 风险隔离</h2>
|
||||
<div class="warnbox">
|
||||
<b>独角数卡必须独立部署,别放 pangolin1。</b> 两个理由:① pangolin1 只有 ~1GB 内存,跑控制面+agent+sing-box 已经紧,再塞 PHP+MySQL+Redis 会 OOM;② <b>风险隔离</b>——发卡/收款站点和 VPN 控制面绑一起,一处被盯上会牵连另一处。放<b>独立小 VPS</b>(海外,2C/2G 起),与控制面只经 HTTPS webhook 通信。
|
||||
</div>
|
||||
|
||||
<h2>两种发货模型(映射到已有能力)</h2>
|
||||
<table>
|
||||
<thead><tr><th></th><th>模型 A · 预充卡密库存 <span class="tag ok">最快 MVP</span></th><th>模型 B · webhook JIT 登记 <span class="tag info">更安全</span></th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>怎么做</td><td><code>codegen</code> 生成一批 → 导出明文 → 充进独角数卡卡密库 → 发货直接给码</td><td>独角数卡发货后调 <code>/webhook/store/codes</code> 才把码登记为有效</td></tr>
|
||||
<tr><td>码何时有效</td><td>生成即有效(在 codes 表 status=unused)</td><td>付款+发货后才有效</td></tr>
|
||||
<tr><td>泄漏风险</td><td>独角数卡库被脱 → 码可被无票兑换</td><td>未售出的码无效,脱库也没用 <span class="tag ok">优</span></td></tr>
|
||||
<tr><td>要写的胶水</td><td>几乎零(导入卡密即可)</td><td>发货 hook 签 HMAC 调 webhook + 解决"码由谁生成"</td></tr>
|
||||
<tr><td>webhook</td><td>不用</td><td>用 <code>/webhook/store/codes</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="okbox">
|
||||
<b>建议:MVP 先用模型 A</b>(零胶水、当天能卖),跑通量后升级模型 B 拿安全性。<br>
|
||||
<b class="todo">模型 B 的待补点:</b>webhook 要求发卡店发送<b>pangolin 格式合法码(带校验位)</b>。独角数卡自己产不出合法码,两条路二选一:(1) 在独角数卡侧复刻 <code>GenerateCode</code> 生成算法;(2) 给 pangolin 加一个受保护的 <code>POST /internal/codes/mint</code>(HMAC 同款)让发卡网"先领一个合法码再发货登记"。<b>推荐 (2)</b>,格式单源、不重复实现。
|
||||
</div>
|
||||
|
||||
<h2>商品 / SKU 映射</h2>
|
||||
<p>独角数卡里每个商品 = 一个套餐档,映射到 webhook 的 <code>plan</code> + <code>duration_days</code>:</p>
|
||||
<table>
|
||||
<thead><tr><th>独角数卡商品</th><th>plan</th><th>duration_days</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Pro · 月</td><td>pro</td><td>30</td></tr>
|
||||
<tr><td>Pro · 年</td><td>pro</td><td>365</td></tr>
|
||||
<tr><td>Team · 月 / 年</td><td>team</td><td>30 / 365</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="small">档位/定价对齐 <code>design/CLAUDE.md §7</code> 与官网 Pricing;SKU 表落进配置,别散在代码里。</p>
|
||||
|
||||
<h2>USDT 收款接入</h2>
|
||||
<ul>
|
||||
<li><b>路子 1 · 独角数卡 USDT 插件 / 加密网关</b>(NOWPayments/Cryptomus 等):最快,插件配好即"支付完成"回调独角数卡触发发货。查 AUP/抽成/是否非托管。</li>
|
||||
<li><b>路子 2 · 自建 TronGrid watcher</b> 作为独角数卡的自定义支付网关:单地址+唯一金额匹配(MVP)或 HD 每单地址(规模化),到账 N 确认后回调独角数卡。最干净,代码量小。详见 <a href="payment-reseller-fulfillment-design.html">发卡+发货架构</a> 的 USDT 一节。</li>
|
||||
</ul>
|
||||
|
||||
<h2>要建的胶水(很小)</h2>
|
||||
<ol>
|
||||
<li><b>发货 hook → pangolin webhook</b>(仅模型 B):独角数卡发货成功后,组 body + 签 <code>X-Pangolin-Signature</code>(HMAC)+时间戳+nonce,POST <code>/webhook/store/codes</code>。独角数卡支持"自动发货 API 商品/webhook",写个小中间脚本或插件即可。</li>
|
||||
<li><b>HMAC secret</b>:生成一把,存 Bitwarden;配 pangolin <code>server.env</code> 与独角数卡侧。</li>
|
||||
<li><b>(模型 B 推荐)</b><code>POST /internal/codes/mint</code>:受 HMAC 保护,返回一个合法码给发卡网。<span class="tag warn">需新增</span></li>
|
||||
<li><b>兑换入口自检</b>:确认客户端/用户中心已有"输码兑换 → /v1/redeem"的 UI(后端已就绪)。</li>
|
||||
</ol>
|
||||
|
||||
<h2>落地步骤</h2>
|
||||
<div class="phase">第 1 步 · 门面起来(半天)</div>
|
||||
<ul>
|
||||
<li>独立小 VPS 部署独角数卡(Docker 最省事),配域名+HTTPS。建 Pro/Team 商品与 SKU。</li>
|
||||
</ul>
|
||||
<div class="phase">第 2 步 · 收款接上(USDT)</div>
|
||||
<ul>
|
||||
<li>选路子 1(网关插件)或路子 2(自建 watcher),配到独角数卡,小额实测"付款→自动发货"。</li>
|
||||
</ul>
|
||||
<div class="phase">第 3 步 · 发货接 pangolin</div>
|
||||
<ul>
|
||||
<li><b>MVP(模型 A)</b>:<code>codegen</code> 生成一批 → 导出 → 充进独角数卡卡密库。<b>此步即可开卖。</b></li>
|
||||
<li><b>升级(模型 B)</b>:加 <code>/internal/codes/mint</code> + 发货 hook 调 webhook,切到"售出才有效"。</li>
|
||||
</ul>
|
||||
<div class="phase">第 4 步 · 闭环验证</div>
|
||||
<ul>
|
||||
<li>真机走一遍:下单→USDT付款→拿码→客户端/用户中心 <code>/v1/redeem</code>→订阅生效、全端同步。</li>
|
||||
<li>对账:独角数卡订单 ↔ codes 登记/核销记录,日结核对。</li>
|
||||
</ul>
|
||||
|
||||
<h2>密钥 · 备份 · 风险边界</h2>
|
||||
<ul>
|
||||
<li>HMAC secret、USDT 种子/私钥、独角数卡 DB → 全走 Bitwarden,不入 git、不落明文。</li>
|
||||
<li>独角数卡 DB 定期备份(订单/卡密)。</li>
|
||||
<li><b>红线</b>:这条链全程不碰跑分/中国支付;USDT 进你自托管钱包,法币结算走 US LLC。支付宝/微信便利若要,另起第三方通道(E 层),当可替换插件,不进本链核心。</li>
|
||||
</ul>
|
||||
|
||||
<h2>待你拍板 / 下一步</h2>
|
||||
<ul>
|
||||
<li>门面选 <b>独角数卡</b> 还是同类?独立 VPS 放哪(海外区域/配置)?</li>
|
||||
<li>USDT 走<b>路子 1(网关)</b>还是<b>路子 2(自建 watcher)</b>?</li>
|
||||
<li>起步用<b>模型 A(预充)</b>先开卖,确认?后续再上模型 B + <code>/internal/codes/mint</code>。</li>
|
||||
<li>确认后我把上面拆成 todo(门面部署 / USDT 接入 / mint 端点 / 发货 hook / 闭环验证)进入落地。</li>
|
||||
</ul>
|
||||
|
||||
<p class="small" style="margin-top:32px">相关:<a href="payment-channels-overview.html">支付渠道选型总览</a> · <a href="payment-reseller-fulfillment-design.html">发卡/Reseller 收款 + 激活码自动发货架构</a></p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pangolin 支付渠道选型总览</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||||
h3{font-size:16px;margin:24px 0 8px;color:var(--fg)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.86em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:#cdd3df}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
|
||||
.card h3{margin-top:0;color:var(--accent2)}
|
||||
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:13.5px}
|
||||
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
||||
th{color:var(--fg2);font-weight:600;font-size:13px}
|
||||
ul,ol{padding-left:22px;margin:8px 0}
|
||||
li{margin:5px 0}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
b{color:#fff}
|
||||
.warnbox{background:rgba(224,106,106,.08);border:1px solid rgba(224,106,106,.35);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.warnbox b{color:var(--bad)}
|
||||
.clean{color:var(--ok);font-weight:600}
|
||||
.grey{color:var(--warn);font-weight:600}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
<h1>支付渠道选型总览</h1>
|
||||
<p class="sub">2026-07-08 · <span class="tag info">调研 / 选型</span> · 各支付渠道候选与甄别标准(研究起点,非背书;合规/AUP/风评自行核实)</p>
|
||||
|
||||
<div class="lead">
|
||||
<b>先破一个概念:</b>「发卡平台」不是一个东西,而是<b>两个可分开的决策</b>——① <b>门面</b>(storefront / 发卡网:展示、下单、自动发卡密);② <b>收款通道</b>(channel:实际把钱收进来的管子)。<b>灰 / 干净的分水岭在通道,不在门面。</b>把两者绑在一个第三方身上,是被套牢和被跑路的根源。
|
||||
</div>
|
||||
|
||||
<h2>决策框架:门面 × 通道</h2>
|
||||
<table>
|
||||
<thead><tr><th>层</th><th>选择</th><th>建议</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>门面</b></td><td>自建发卡网 / 上第三方发卡市场</td><td class="clean">自建(你拥有门面+数据+发货节奏,只外包"收款"一环)</td></tr>
|
||||
<tr><td><b>通道</b></td><td>USDT 自收 / 加密网关 / 官方鹅 / 支付宝微信第三方</td><td>干净优先;支付宝/微信第三方=灰产层,最后再叠加、且当可替换插件</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>候选清单(A–E)</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>A · 自建发卡网(门面 · 开源自托管)<span class="tag ok">推荐起步</span></h3>
|
||||
<p>你完全拥有门面 + 自动发卡逻辑(卡密库存 / API 发货),对接 pangolin <code>codes</code> 模块。</p>
|
||||
<ul>
|
||||
<li><b>独角数卡 dujiaoka</b>(GitHub <code>assimon/dujiaoka</code>)— PHP,最主流,卡密库存 + 多支付插件。</li>
|
||||
<li><b>acg-faka</b>(异次元 / 彩虹发卡)— PHP,插件多。</li>
|
||||
<li><b>KamiFaka</b> — Vue + PHP,轻量。</li>
|
||||
</ul>
|
||||
<p class="small">看点:卡密/API 发货是否好接 codes、支付插件生态、维护活跃度。→ <a href="payment-a-selfhosted-store-plan.html">已细化为落地方案(方案 A)</a></p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>B · 加密支付网关(让 USDT 收款像普通支付,API+webhook)<span class="tag info">懒人版 USDT</span></h3>
|
||||
<ul>
|
||||
<li><b>NOWPayments</b> — 非托管、币种多、API/webhook 齐。</li>
|
||||
<li><b>Cryptomus</b> — TRC20 友好、商户后台。</li>
|
||||
<li><b>CoinGate</b> — 欧洲、合规相对正。</li>
|
||||
<li><b>Plisio / 0xProcessing</b> — 同类可比。</li>
|
||||
</ul>
|
||||
<p class="small">看点:①是否<b>非托管</b>(直接进你钱包)②抽成 ③<b>AUP 是否禁 VPN</b>(逐家查)④是否强制 KYC/公司资料 ⑤TRC20 支持。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>C · 自建 USDT 监听(DIY,最干净)<span class="tag ok">干净</span></h3>
|
||||
<ul>
|
||||
<li><b>TronGrid</b>(TRON 官方 API,免费额度)— 轮询到账。</li>
|
||||
<li>节点服务:<b>GetBlock / QuickNode</b>,或自建 TRON 节点。</li>
|
||||
<li>GitHub 搜 <code>tron usdt payment</code> / <code>trc20 watcher</code> 有大量参考实现。</li>
|
||||
</ul>
|
||||
<p class="small">看点:确认数、TRX gas 归集、种子/私钥托管。见 <a href="payment-reseller-fulfillment-design.html">发卡+发货架构</a> 的“USDT 自动收款”一节。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>D · 官方鹅(合规补充)<span class="tag ok">干净</span></h3>
|
||||
<ul>
|
||||
<li><b>Telegram Stars</b> — TG 官方,bot 内收款(走 Apple/Google IAP,抽成)。</li>
|
||||
<li><b>Apple / Google IAP</b> — 海外区。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>E · 支付宝/微信 第三方通道(灰产层)<span class="tag bad">不列名单</span></h3>
|
||||
<p>跑分 / 四方聚合。<b>不给候选名单</b>——合法性与跑路风险无法背书。自研路子:</p>
|
||||
<ul>
|
||||
<li>站长圈打听历史:<code>hostloc</code>、LowEndTalk 中文区、相关 TG 发卡/站长群,看<b>实际跑路/冻卡口碑</b>。</li>
|
||||
<li>一律<b>小额实测提现</b>跑通再放量。</li>
|
||||
<li>做成<b>可热插拔多通道</b>,账上不留余额。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>决策矩阵</h2>
|
||||
<table>
|
||||
<thead><tr><th>渠道</th><th>干净度</th><th>接入难度</th><th>抽成</th><th>主要风险</th><th>适合阶段</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>A 自建发卡网</td><td class="clean">门面干净</td><td>低(部署即用)</td><td>—(只付通道费)</td><td>看你插什么通道</td><td>起步基座</td></tr>
|
||||
<tr><td>B 加密网关</td><td class="clean">干净</td><td>低(API)</td><td>~0.5%–1.5%</td><td>AUP 可能禁 VPN、KYC</td><td>快速上 USDT</td></tr>
|
||||
<tr><td>C 自建 USDT 监听</td><td class="clean">最干净</td><td>中(写 watcher)</td><td>~0(仅链上手续费)</td><td>种子安全、gas 归集</td><td>长期主力</td></tr>
|
||||
<tr><td>D 官方鹅</td><td class="clean">干净</td><td>低</td><td>15%–30%</td><td>抽成高、中国区不可用</td><td>海外补充</td></tr>
|
||||
<tr><td>E 支付宝/微信第三方</td><td class="grey">灰(跑分)</td><td>低</td><td>8%–20%</td><td>冻卡、跑路、周期性死</td><td>可选便利、可替换</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>尽调清单(仅当接 E · 支付宝/微信第三方通道)</h2>
|
||||
<ul>
|
||||
<li><b>结算周期</b>:优先 T+0 / T+1,压你钱三五天的直接 pass。</li>
|
||||
<li><b>保证金方向</b>:它押给你?还是你押给它?<b>你押钱=高危</b>。</li>
|
||||
<li><b>口碑与跑路史</b>:先在站长圈/TG 群打听历史,有跑路记录排除。</li>
|
||||
<li><b>小额实测</b>:先小金额跑通提现,确认到账再放量。</li>
|
||||
<li><b>通道稳定性 + 热切换</b>:注定周期性死;同时接 2–3 家,一家挂立刻切。</li>
|
||||
<li><b>费率</b>:别只看费率——压钱/爱跑路的更坑。</li>
|
||||
</ul>
|
||||
|
||||
<div class="warnbox">
|
||||
<b>红线:</b>Pangolin 主体(尤其中国岩美)永不直接对接跑分/四方聚合、永不用中国支付账户收 VPN 款。E 层的冻卡/跑路风险只允许存在于第三方通道那一层;到你手里的必须是它们的<b>结算款</b>(法币/USDT,走 US LLC)。
|
||||
</div>
|
||||
|
||||
<h2>推荐研究顺序 + 起步组合</h2>
|
||||
<ol>
|
||||
<li>先啃 <b>A(独角数卡)+ C(TronGrid 自建监听)</b>——这条让你干净地跑起来,"付完自动发码"体验和灰产 bot 一致。</li>
|
||||
<li><b>B(加密网关)</b>作为"懒人版 USDT"备选(省掉写 watcher,但查 AUP/抽成/是否非托管)。</li>
|
||||
<li><b>D</b> 作海外补充;<b>E</b> 最后再碰、只当可替换插件。</li>
|
||||
</ol>
|
||||
<p><b>建议起步组合:独角数卡(门面)+ USDT(C 自建监听 或 B 网关)。</b> 已细化 → <a href="payment-a-selfhosted-store-plan.html">方案 A · 自建发卡网落地细化</a>。</p>
|
||||
|
||||
<h2>相关文档</h2>
|
||||
<ul>
|
||||
<li><a href="payment-reseller-fulfillment-design.html">支付落地方案 · 发卡/Reseller 收款 + 激活码自动发货</a>(总架构:钱流/码流/结算 + 两种对接模型)</li>
|
||||
<li><a href="payment-a-selfhosted-store-plan.html">方案 A · 自建发卡网(独角数卡)落地细化</a>(基于已就绪的 codes webhook/redeem)</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>收款闭环 · 独角数卡 + 自托管 USDT + webhook JIT 实现计划</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:29px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||||
h3{font-size:16px;margin:24px 0 8px;color:var(--fg)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.85em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.5;color:#cdd3df}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
|
||||
.card h3{margin-top:0;color:var(--accent2)}
|
||||
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:13.5px}
|
||||
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
||||
th{color:var(--fg2);font-weight:600;font-size:13px}
|
||||
ul,ol{padding-left:22px;margin:8px 0}
|
||||
li{margin:6px 0}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
b{color:#fff}
|
||||
.phase{font-size:18px;margin:38px 0 6px;color:var(--accent);font-weight:700}
|
||||
.ck{list-style:none;padding-left:4px}
|
||||
.ck li{position:relative;padding-left:26px}
|
||||
.ck li::before{content:"☐";position:absolute;left:0;color:var(--fg2)}
|
||||
.diagram{overflow-x:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px;margin:16px 0}
|
||||
.diagram pre{background:transparent;border:none;padding:0;color:#cdd3df;font-size:12px}
|
||||
.warnbox{background:rgba(224,106,106,.08);border:1px solid rgba(224,106,106,.35);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.warnbox b{color:var(--bad)}
|
||||
.okbox{background:rgba(94,194,122,.07);border:1px solid rgba(94,194,122,.32);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
<h1>收款闭环 · 独角数卡 + 自托管 USDT(TRC20) + webhook JIT 发码</h1>
|
||||
<p class="sub">2026-07-09 · <span class="tag info">阅读版</span> · 执行真相源 <code>docs/superpowers/plans/2026-07-09-payment-clean-usdt-loop.md</code>(带 checkbox)· #34(tier-1)</p>
|
||||
|
||||
<div class="lead">
|
||||
<b>目标:</b>把「下单 → USDT 付款 → 自动发激活码 → 客户端/用户中心兑换 → 订阅生效」端到端打通,<b>全程自托管、售出才发码、无跑分、无第三方支付主体</b>。<br>
|
||||
<b>关键前提(已摸底):</b>激活码后端大半已就绪——<code>POST /v1/redeem</code>(兑换,幂等+限频)、批次生成/导出、发卡店回调 <code>/webhook/store/codes</code>(HMAC+时间戳+nonce)都在。本方案<b>新增核心只有一个后端端点</b>(JIT 发码 <code>/internal/codes/mint</code>)+ 一个独立收款服务 <code>pangolin-pay</code>(TronGrid watcher + epay 支付口)。
|
||||
</div>
|
||||
|
||||
<h2>三大组件 / 数据流</h2>
|
||||
<div class="diagram">
|
||||
<pre>买家 ──下单──▶ 独角数卡(门面, 独立 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 ──▶ 订阅生效</pre>
|
||||
</div>
|
||||
|
||||
<div class="warnbox">
|
||||
<b>安全红线(贯穿全程):</b>① 热服务 <code>pangolin-pay</code> <b>只持 xpub 派生收款地址、绝不持私钥</b>(能收不能转,脱库也转不走钱);② 私钥/助记词冷备 + Bitwarden,不入 git、不上热机;③ 收款终点=自托管钱包,变现经交易所出金 US LLC,记账走 accounting;④ 全程不碰跑分/中国支付。<b>部署在独立海外小 VPS,不放 pangolin1</b>(内存紧 + 风险隔离),只经 HTTPS+HMAC 与控制面 <code>api.yanmeiai.com</code> 通信。
|
||||
</div>
|
||||
|
||||
<h2>已就绪 vs 本轮新增</h2>
|
||||
<table>
|
||||
<thead><tr><th>能力</th><th>状态</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>POST /v1/redeem</code> 兑换(幂等+限频+账户锁)</td><td><span class="tag ok">已实现</span></td></tr>
|
||||
<tr><td>批次生成/导出(Crockford Base32 75-bit 带校验位,只存 hash)</td><td><span class="tag ok">已实现</span></td></tr>
|
||||
<tr><td>发卡店回调 <code>/webhook/store/codes</code>(HMAC+时间戳+nonce)</td><td><span class="tag ok">已实现</span>(留给未来第三方发卡店)</td></tr>
|
||||
<tr><td><code>POST /internal/codes/mint</code> JIT 发码</td><td><span class="tag warn">本轮新增</span></td></tr>
|
||||
<tr><td><code>pangolin-pay</code>:TronGrid watcher + epay 支付口</td><td><span class="tag warn">本轮新增</span></td></tr>
|
||||
<tr><td>自托管 TRC20 HD 钱包 + 归集/变现 runbook</td><td><span class="tag warn">本轮新增</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Phase 0.5 — 钱包选型(用哪个钱包)</h2>
|
||||
<p><b>先厘清:这套架构的「钱包」是两件事,别当成装一个 app。</b></p>
|
||||
<ul>
|
||||
<li><b>主种子(钱在哪)</b>:一套 BIP39 助记词,冷存。资产本体。</li>
|
||||
<li><b>watcher 收款</b>:热服务用<b>库</b>从 account <b>xpub(公钥)</b>派生每单地址——<b>watch-only,不是钱包 app</b>,拿不到私钥。</li>
|
||||
<li><b>归集签名</b>:动钱时在冷端用私钥离线签 sweep 交易。</li>
|
||||
</ul>
|
||||
<p>所以「用哪个钱包」= 选 ① 主种子怎么冷存 + ② watcher 用哪个派生库。</p>
|
||||
|
||||
<h3>① 主种子 / 冷存 候选</h3>
|
||||
<table>
|
||||
<thead><tr><th>方案</th><th>冷存安全</th><th>xpub 导出</th><th>归集签名</th><th>结论</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><b>离线生成助记词 + 库派生</b> <span class="tag ok">推荐</span></td><td>高(纸/钢板离线,永不联网)</td><td>从种子导 account xpub</td><td>离线机用库签</td><td>最贴合 watch-only 架构、最灵活</td></tr>
|
||||
<tr><td>Ledger 硬件钱包(可叠加)</td><td>最高(私钥不出设备)</td><td>可导 account 公钥(需工具)</td><td>插 Ledger 签</td><td>想再加一层硬件冷存就用它兜底</td></tr>
|
||||
<tr><td>TronLink / imToken / Trust(手机热钱包)</td><td>低(热钱包)</td><td>不便批量派生</td><td>app 内签</td><td><span class="tag bad">不推荐</span>当收款主钱包</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>② watcher 派生库 候选(给热服务 watch-only 派生,非钱包)</h3>
|
||||
<table>
|
||||
<thead><tr><th>语言</th><th>库</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Go(与 server 同栈,推荐)</td><td><code>tyler-smith/go-bip39</code> + <code>btcd/btcutil/hdkeychain</code>(BIP32)→ 公钥转 TRON base58 地址;或成熟 TRON Go SDK</td></tr>
|
||||
<tr><td>Python</td><td><code>tronpy</code> + <code>bip_utils</code>(一步派生 TRON 地址)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="okbox">
|
||||
<b>推荐:</b>主种子用<b>离线生成 BIP39 助记词</b>(想再稳叠一层 <b>Ledger 冷存</b>);watcher 用 <b>Go 库从 account xpub watch-only 派生</b>(与 server 同栈、好集成、热机无私钥)。<b>收款钱包 = 你自己的 HD 种子,不是某个 app。</b>
|
||||
</div>
|
||||
<ul class="ck">
|
||||
<li><b>0.5.1</b> 定主种子方案:离线助记词(推荐)/ 是否叠加 Ledger 冷存;记录选型。</li>
|
||||
<li><b>0.5.2</b> 定 watcher 派生库(Go bip39+hdkeychain→TRON / Python tronpy+bip_utils),跑通派生并与钱包 app 交叉核对同一地址(防派生不一致收不到钱)。</li>
|
||||
<li><b>0.5.3</b> 定归集冷签工具(离线机脚本 / Ledger),确认能对 sweep 交易离线签名。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 1 — 自托管钱包(TRC20 HD,watch-only 收款)</h2>
|
||||
<p>一套助记词派生无限个收款地址;热服务只拿 xpub 派生地址收款、拿不到私钥;私钥冷备用于归集。</p>
|
||||
<ul class="ck">
|
||||
<li><b>1.1</b> <b>离线</b>生成 BIP39 助记词(24 词),TRON 派生路径 <code>m/44'/195'/0'/0/i</code>(195=TRON)。工具:离线机 tronpy/tronweb 或硬件钱包(Ledger 导出 xpub),<b>生成不联网</b>。</li>
|
||||
<li><b>1.2</b> 密钥分级:助记词/私钥→冷备+Bitwarden(永不上热机);account xpub→给 pangolin-pay watch-only 派生;HMAC secret→Bitwarden。</li>
|
||||
<li><b>1.3</b> 定冷钱包归集地址 + 记 USDT-TRC20 合约地址(主网 <code>TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t</code>,核对无误再写死)。</li>
|
||||
<li><b>1.4</b> 备少量 <b>TRX</b> 作归集 gas(TRON 转账吃 energy/bandwidth)。</li>
|
||||
<li><b>1.5</b> 验证:xpub 离线派生前 5 地址;往 index 0 打 1 USDT 测试,确认 watch-only 查得到。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 2 — pangolin-pay:TronGrid watcher(自建收款侦测)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>2.1</b> 建 Go 服务 <code>pangolin-pay</code>(自带轻量 SQLite)。</li>
|
||||
<li><b>2.2</b> 数据模型:<code>pay_orders(order_no, sku, plan, duration_days, addr_index, address, expect_amount, status, tx_id, confirmations, created_at, expires_at)</code> + <code>addr_cursor(next_index)</code>。</li>
|
||||
<li><b>2.3</b> <b>每单唯一地址</b>:建单时从 xpub 派生 <code>m/.../0/next_index</code>(最准,免金额撞车)。次选单地址+唯一金额。</li>
|
||||
<li><b>2.4</b> TronGrid 轮询:<code>GET /v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT></code>,比对 to/token/金额(含容差)。</li>
|
||||
<li><b>2.5</b> 确认:只认已固化交易或等 ≥N 区块(约 1 分钟)→ <code>status=paid</code> + 记 tx_id。</li>
|
||||
<li><b>2.6</b> 到账触发发货;<b>幂等</b>:同 tx_id 只认一次、一单只发一次。</li>
|
||||
<li><b>2.7</b> 超时(默认 15min)→ <code>expired</code>、释放地址(不复用,避免串单)。</li>
|
||||
<li><b>2.8</b> TronGrid key + 限流退避 + 重试;崩溃可从 DB 未决单恢复。</li>
|
||||
<li><b>2.9</b> 测试:mock TronGrid → 金额匹配/确认门槛/超时/幂等/恢复。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 3 — 控制面:JIT 发码端点 <code>POST /internal/codes/mint</code></h2>
|
||||
<p>付款确认时<b>即时生成一个合法码</b>——这才是 webhook JIT 的落点:售出才产码、才有效。自建场景由 pangolin 产码(保证格式+校验位单源),比让外部产码更干净。</p>
|
||||
<pre>POST /internal/codes/mint # /internal 组, 无 JWT, HMAC 同 webhook
|
||||
Headers: X-Pangolin-Signature: sha256=<hmac> · X-Pangolin-Timestamp(±5min) · X-Pangolin-Nonce
|
||||
Body: { "plan":"pro", "duration_days":365, "order_ref":"<独角数卡订单号>" }
|
||||
→ 200 { "code":"PGL-XXXX-...", "expires_at":"..." } # 以 order_ref 幂等, 同单返同码</pre>
|
||||
<ul class="ck">
|
||||
<li><b>3.1</b> 新增端点,HMAC+时间戳+nonce(复用 webhook 那套验签,抽公共中间件)。</li>
|
||||
<li><b>3.2</b> 复用 <code>codes.GenerateCode()</code> 产码 + <code>Store.CreateCode</code> 存 unused(channel=ChannelStore)→ 返回明文。</li>
|
||||
<li><b>3.3</b> <b>幂等</b>:<code>order_ref</code> 为幂等键(存 order_ref→code_hash),同单重放返回同一码。</li>
|
||||
<li><b>3.4</b> secret → Bitwarden → <code>server.env</code>(<code>CODES_MINT_HMAC_SECRET</code>);<code>main.go</code> 挂路由。</li>
|
||||
<li><b>3.5</b> 保留现有 <code>/webhook/store/codes</code> 供未来第三方发卡店。</li>
|
||||
<li><b>3.6</b> 测试:验签/时间戳/nonce/order_ref 幂等/产码格式。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 4 — 独角数卡对接(门面 + 自动发货)</h2>
|
||||
<ul class="ck">
|
||||
<li><b>4.1</b>(研究·先做)摸清所用独角数卡版本的 <b>epay 支付接口</b> + <b>API-提货/自动发货商品</b>机制(版本相关,实测出对接点清单再实现)。</li>
|
||||
<li><b>4.2</b> pangolin-pay 实现 <b>epay 兼容支付接口</b>:独角数卡建单 → 跳我方支付页(USDT 地址+应付金额+倒计时+TRC20 强提示)。</li>
|
||||
<li><b>4.3</b> 到账 → 按 epay 规范 <b>notify 独角数卡</b>(签名)→ 标「已支付」。</li>
|
||||
<li><b>4.4</b> 商品设 <b>API-提货</b>:发货时调 pangolin-pay <code>/issue</code> → 内部 HMAC 调 <code>/internal/codes/mint</code> 取码 → 交付买家。</li>
|
||||
<li><b>4.5</b> <b>SKU 映射表</b>(配置化):独角数卡商品 → plan+duration_days(Pro 月/年、Team 月/年;对齐官网 Pricing)。</li>
|
||||
<li><b>4.6</b> 幂等贯通:独角数卡 order_no 作为 order_ref 一路透传到 mint,一单一码。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 5 — 部署 · 运维 · 安全</h2>
|
||||
<ul class="ck">
|
||||
<li><b>5.1</b> 独立海外小 VPS(2C/2G)部署:独角数卡(Docker)+ pangolin-pay + 域名 + HTTPS(<b>不放 pangolin1</b>)。</li>
|
||||
<li><b>5.2</b> 密钥落位核对:pangolin-pay 只有 xpub + TronGrid key + epay 签名密钥 + mint HMAC secret,<b>无私钥/助记词</b>(冷备)。全 Bitwarden。</li>
|
||||
<li><b>5.3</b> <b>归集 runbook</b>:定期把各收款地址 USDT sweep 到冷钱包(<b>离线签名</b>),备 TRX gas。</li>
|
||||
<li><b>5.4</b> <b>变现 runbook</b>:冷钱包 → 交易所(能出金 US LLC 的)→ 卖 USDT→USD → 银行;记账交 accounting agent。</li>
|
||||
<li><b>5.5</b> 监控告警(接现有 Telegram):watcher 存活、TronGrid 配额、到账延迟、mint 失败、订单堆积。</li>
|
||||
<li><b>5.6</b> <b>对账</b>:独角数卡订单 ↔ pay_orders ↔ codes(mint/redeem)三方日结,差异告警。</li>
|
||||
</ul>
|
||||
|
||||
<h2>Phase 6 — 端到端验证</h2>
|
||||
<ul class="ck">
|
||||
<li><b>6.1</b> 真单闭环:下单→USDT付款→watcher确认→mint码→独角数卡交付→<code>/v1/redeem</code>→订阅生效、全端同步。</li>
|
||||
<li><b>6.2</b> 异常路径:超时未付/金额不足/重复付/确认期抖动/mint 重放。</li>
|
||||
<li><b>6.3</b> 安全核对:热服务无私钥、密钥不入 git、每单唯一地址不复用、redeem 幂等+限频。</li>
|
||||
</ul>
|
||||
|
||||
<div class="okbox">
|
||||
<b>验收判据:</b>后端 <code>go test ./internal/codes/...</code> 含 mint 验签/幂等/格式全绿;pangolin-pay <code>go test</code> 含匹配/确认/超时/幂等/恢复;真机一笔真 USDT 小额单全通 + 异常逐条演练;安全逐项核对。
|
||||
</div>
|
||||
|
||||
<h2>不在本轮</h2>
|
||||
<ul>
|
||||
<li>支付宝/微信第三方通道(E 层,灰产,另议且当可替换插件)。</li>
|
||||
<li>HD 自动 sweep 归集(先人工离线归集)。</li>
|
||||
<li>多币种、watcher 高可用、加密网关备选路子(B 方案)。</li>
|
||||
<li>Telegram Stars / Apple IAP 独立收款口(海外补充,另排)。</li>
|
||||
</ul>
|
||||
|
||||
<p class="small" style="margin-top:32px">相关:<a href="payment-channels-overview.html">支付渠道选型总览</a> · <a href="payment-a-selfhosted-store-plan.html">方案A 自建发卡网细化</a> · <a href="payment-reseller-fulfillment-design.html">发卡+发货架构</a></p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pangolin 支付落地方案 · 发卡/Reseller 收款 + 激活码自动发货</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||||
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
|
||||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||||
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
|
||||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||||
h3{font-size:16px;margin:24px 0 8px;color:var(--fg)}
|
||||
p{margin:10px 0}
|
||||
code{font-family:var(--mono);font-size:.86em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||||
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:#cdd3df}
|
||||
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
|
||||
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||||
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||||
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
|
||||
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||||
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
|
||||
.card h3{margin-top:0;color:var(--accent2)}
|
||||
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
|
||||
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
|
||||
th{color:var(--fg2);font-weight:600;font-size:13px}
|
||||
ul,ol{padding-left:22px;margin:8px 0}
|
||||
li{margin:5px 0}
|
||||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||||
.small{color:var(--fg2);font-size:13px}
|
||||
a{color:var(--accent2)}
|
||||
.back{display:inline-block;margin-bottom:24px;font-size:13px}
|
||||
b{color:#fff}
|
||||
.phase{font-size:19px;margin:40px 0 6px;color:var(--accent);font-weight:700}
|
||||
.diagram{overflow-x:auto;background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:20px;margin:16px 0}
|
||||
.diagram svg{display:block;width:100%;height:auto;max-width:820px;margin:0 auto}
|
||||
.legend{display:flex;gap:20px;flex-wrap:wrap;font-size:13px;color:var(--fg2);margin-top:12px;justify-content:center}
|
||||
.legend span{display:inline-flex;align-items:center;gap:6px}
|
||||
.legend i{width:22px;height:0;border-top-width:3px;border-top-style:solid;display:inline-block}
|
||||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:14px 0}
|
||||
@media(max-width:680px){.grid2{grid-template-columns:1fr}}
|
||||
.rec{border-left:3px solid var(--ok);padding-left:12px}
|
||||
.warnbox{background:rgba(224,106,106,.08);border:1px solid rgba(224,106,106,.35);border-radius:12px;padding:14px 18px;margin:14px 0}
|
||||
.warnbox b{color:var(--bad)}
|
||||
.num{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:var(--accent);color:#0f1117;font-weight:700;font-size:13px;margin-right:8px;flex-shrink:0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a class="back" href="index.html">← 文档索引</a>
|
||||
<h1>Pangolin 支付落地方案</h1>
|
||||
<p class="sub">2026-07-08 · <span class="tag info">设计方案</span> · 发卡 / Reseller 收款 + 激活码自动发货(“付完秒发货”体验,收款风险不落在你的主体上)</p>
|
||||
|
||||
<div class="lead">
|
||||
<b>一句话:</b>把「收钱」和「你的品牌 VPN」彻底解耦——<b>收款外包给发卡平台 / Reseller</b>(他们承担支付宝/微信跑分、冻卡、跑路风险),你只做一件事:<b>把一段「激活码」交付出去</b>。所有渠道(发卡法币、USDT、Telegram Stars)进来的钱最后都汇成同一种“货币”——激活码;客户端与用户中心<b>只认码</b>。你们的 <code>codes</code> 激活码模块已实现,支付层只是「入账 → 发码 → 核销」的适配器。
|
||||
</div>
|
||||
|
||||
<h2>为什么是这个形状(约束)</h2>
|
||||
<p>上一轮已经确认的硬约束,直接决定方案形状:</p>
|
||||
<ul>
|
||||
<li><b>中国主体 / 中国支付不能碰</b>:支付宝 / 微信 / 银联 + 岩美(北京)主体卖 VPN → 冻结商户号 + 法律风险。<span class="tag bad">红线</span></li>
|
||||
<li><b>灰产 bot 的做法 = 跑分</b>:那些“资源入群助手”用的是<b>四方聚合支付 + 个人账户跑分</b>(帮信罪 / 冻卡 / 平台跑路)。你自己的主体<b>绝不直接接跑分</b>。</li>
|
||||
<li><b>能保留“支付宝/微信便利”的唯一干净姿势</b>:把这一环<b>外包</b>——发卡平台/Reseller 用他们的通道收人民币,风险在他们;你拿结算 + 发激活码。或主力走 <b>USDT(你自己钱包,链上干净)</b> + <b>Telegram Stars(TG 官方)</b>。</li>
|
||||
</ul>
|
||||
|
||||
<h2>整体流程图</h2>
|
||||
<p>一笔订单从下单到订阅生效的完整流转。<b>橙色 = 钱流,青色 = 码流,绿色虚线 = 结算/对账</b>。收款与跑分风险全部圈在「发卡平台 / Reseller」内,不进入 Pangolin 主体。</p>
|
||||
|
||||
<div class="diagram">
|
||||
<svg viewBox="0 0 820 610" role="img" aria-label="发卡/Reseller 收款 + 激活码自动发货流程图">
|
||||
<defs>
|
||||
<marker id="ah-money" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#e0884f"/></marker>
|
||||
<marker id="ah-code" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#5fb0c9"/></marker>
|
||||
<marker id="ah-settle" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#5ec27a"/></marker>
|
||||
</defs>
|
||||
|
||||
<!-- boxes: main column x=70..560 (w=490) -->
|
||||
<!-- ① 用户 -->
|
||||
<rect x="70" y="24" width="490" height="50" rx="10" fill="#1d2129" stroke="#272c36"/>
|
||||
<circle cx="96" cy="49" r="12" fill="#e0884f"/><text x="96" y="53" text-anchor="middle" font-size="13" font-weight="700" fill="#0f1117">1</text>
|
||||
<text x="122" y="46" font-size="14" font-weight="700" fill="#e6e8ee">用户 · 在下单页 / 发卡平台选套餐</text>
|
||||
<text x="122" y="63" font-size="12" fill="#a8afbd">Pro 月/年、Team……(对应你的定价档)</text>
|
||||
|
||||
<!-- ② 发卡平台收款 (risk) -->
|
||||
<rect x="70" y="118" width="490" height="70" rx="10" fill="#1d2129" stroke="#e06a6a" stroke-width="1.5"/>
|
||||
<circle cx="96" cy="143" r="12" fill="#e0884f"/><text x="96" y="147" text-anchor="middle" font-size="13" font-weight="700" fill="#0f1117">2</text>
|
||||
<text x="122" y="140" font-size="14" font-weight="700" fill="#e6e8ee">发卡平台 / Reseller · 收款</text>
|
||||
<text x="122" y="158" font-size="12" fill="#a8afbd">支付宝/微信(跑分)· USDT · 国际代付 —— 通道与</text>
|
||||
<text x="122" y="174" font-size="12" fill="#e06a6a">冻卡/跑路风险都在这一层,不进入 Pangolin 主体</text>
|
||||
|
||||
<!-- ③ 取激活码 -->
|
||||
<rect x="70" y="232" width="490" height="70" rx="10" fill="#1d2129" stroke="#272c36"/>
|
||||
<circle cx="96" cy="257" r="12" fill="#e0884f"/><text x="96" y="261" text-anchor="middle" font-size="13" font-weight="700" fill="#0f1117">3</text>
|
||||
<text x="122" y="254" font-size="14" font-weight="700" fill="#e6e8ee">取一个激活码</text>
|
||||
<text x="122" y="272" font-size="12" fill="#a8afbd">A · 从你预充的「卡密库存」取一张(最松耦合)</text>
|
||||
<text x="122" y="288" font-size="12" fill="#a8afbd">B · 支付成功 webhook 调你 API → 实时签发一张</text>
|
||||
|
||||
<!-- ④ 秒发码 -->
|
||||
<rect x="70" y="346" width="490" height="50" rx="10" fill="#1d2129" stroke="#272c36"/>
|
||||
<circle cx="96" cy="371" r="12" fill="#5fb0c9"/><text x="96" y="375" text-anchor="middle" font-size="13" font-weight="700" fill="#0f1117">4</text>
|
||||
<text x="122" y="368" font-size="14" font-weight="700" fill="#e6e8ee">发卡平台把激活码「秒发」给用户</text>
|
||||
<text x="122" y="385" font-size="12" fill="#a8afbd">这就是灰产 bot 的“付完自动发货”体验</text>
|
||||
|
||||
<!-- ⑤ 兑换 + 核销 -->
|
||||
<rect x="70" y="440" width="490" height="70" rx="10" fill="#1d2129" stroke="#5fb0c9" stroke-width="1.5"/>
|
||||
<circle cx="96" cy="465" r="12" fill="#5fb0c9"/><text x="96" y="469" text-anchor="middle" font-size="13" font-weight="700" fill="#0f1117">5</text>
|
||||
<text x="122" y="462" font-size="14" font-weight="700" fill="#e6e8ee">用户在 Pangolin 客户端/用户中心输码兑换</text>
|
||||
<text x="122" y="480" font-size="12" fill="#a8afbd">→ 后端 codes 模块核销(一次性、幂等、防重放)</text>
|
||||
<text x="122" y="496" font-size="12" fill="#5ec27a">→ 订阅/时长生效,全端同步</text>
|
||||
|
||||
<!-- 结算 side box -->
|
||||
<rect x="612" y="118" width="180" height="70" rx="10" fill="#141922" stroke="#5ec27a" stroke-dasharray="4 3"/>
|
||||
<text x="702" y="143" text-anchor="middle" font-size="13" font-weight="700" fill="#e6e8ee">你 · US LLC</text>
|
||||
<text x="702" y="161" text-anchor="middle" font-size="11.5" fill="#a8afbd">定期结算(扣佣)</text>
|
||||
<text x="702" y="177" text-anchor="middle" font-size="11.5" fill="#a8afbd">法币 / USDT 到账</text>
|
||||
|
||||
<!-- arrows down -->
|
||||
<line x1="315" y1="74" x2="315" y2="116" stroke="#e0884f" stroke-width="2.5" marker-end="url(#ah-money)"/>
|
||||
<text x="325" y="99" font-size="11.5" fill="#e0884f">付款 ¥ / USDT</text>
|
||||
<line x1="315" y1="188" x2="315" y2="230" stroke="#e0884f" stroke-width="2.5" marker-end="url(#ah-money)"/>
|
||||
<text x="325" y="213" font-size="11.5" fill="#e0884f">订单支付成功</text>
|
||||
<line x1="315" y1="302" x2="315" y2="344" stroke="#5fb0c9" stroke-width="2.5" marker-end="url(#ah-code)"/>
|
||||
<text x="325" y="327" font-size="11.5" fill="#5fb0c9">交付激活码</text>
|
||||
<line x1="315" y1="396" x2="315" y2="438" stroke="#5fb0c9" stroke-width="2.5" marker-end="url(#ah-code)"/>
|
||||
<text x="325" y="421" font-size="11.5" fill="#5fb0c9">用户输码</text>
|
||||
|
||||
<!-- settle dashed arrow from ② to settlement box -->
|
||||
<line x1="560" y1="153" x2="610" y2="153" stroke="#5ec27a" stroke-width="2" stroke-dasharray="4 3" marker-end="url(#ah-settle)"/>
|
||||
</svg>
|
||||
<div class="legend">
|
||||
<span><i style="border-color:#e0884f"></i>钱流</span>
|
||||
<span><i style="border-color:#5fb0c9"></i>码流(激活码)</span>
|
||||
<span><i style="border-color:#5ec27a;border-top-style:dashed"></i>结算 / 对账</span>
|
||||
<span><i style="border-color:#e06a6a"></i>风险边界(发卡层内)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>逐环节详解</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">1</span>用户下单</h3>
|
||||
<p>入口有三种形态(见后文“落地节奏”):① 你的 Telegram bot 菜单;② 用户中心内“获取激活码”下单页;③ 第三方发卡平台的商品页。用户选套餐档位(Pro 月/年、Team),点支付。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">2</span>发卡平台收款 <span class="tag bad">风险都在这层</span></h3>
|
||||
<p>发卡平台/Reseller 用<b>他们自己的通道</b>收人民币(支付宝/微信走跑分)、USDT、国际代付。<b>关键:收款主体、收款码、跑分账户、冻卡与跑路风险,全部是发卡平台的,不是你的。</b>你和 Pangolin 主体永远不出现在这条法币收款链上。你付出的代价是<b>佣金 / 折扣</b>(业界常见 8%–20%,视通道与结算周期)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">3</span>取激活码(两种对接模型)</h3>
|
||||
<p>这是唯一需要你出工程的地方,二选一(下一节详述):</p>
|
||||
<ul>
|
||||
<li><b>A · 预充卡密库存</b>:你批量生成激活码 → 充值进发卡平台的“卡密库存” → 平台卖一张发一张。你系统<b>零对接</b>,只需生成码 + 补货。</li>
|
||||
<li><b>B · API 实时签发</b>:用户支付成功 → 发卡平台 <code>webhook</code> 通知你后端 → 你实时签发一张激活码返回 → 平台转发。库存不积压、可控性强,需对接一个回调接口。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">4</span>秒发货</h3>
|
||||
<p>发卡平台把激活码即时发给用户(页面展示 / bot 消息 / 邮件)。用户体验和那些灰产 bot 的“付完自动发”<b>完全一致</b>——差别只在风险归属。</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><span class="num">5</span>兑换 + 核销</h3>
|
||||
<p>用户在 Pangolin 客户端或用户中心输入激活码 → 后端 <code>codes</code> 模块<b>核销</b>:校验有效性、一次性消费(幂等 + 防重放 + 并发锁)、把对应套餐时长写进账户 → 订阅生效、全端同步。这一步你们<b>已经实现</b>,是整套方案的“落地点”。</p>
|
||||
</div>
|
||||
|
||||
<h2>两种对接模型对比</h2>
|
||||
<table>
|
||||
<thead><tr><th>维度</th><th>A · 预充卡密库存 <span class="tag ok">MVP 首选</span></th><th>B · API 实时签发</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>你要出的工程</td><td>几乎为零:批量生成激活码导出即可</td><td>一个签发回调接口 + 验签 + 幂等</td></tr>
|
||||
<tr><td>库存管理</td><td>要盯库存、及时补货(卖光即断供)</td><td>无库存概念,按需签发</td></tr>
|
||||
<tr><td>可控性 / 风控</td><td>码一旦充进平台就“出手”了,作废要靠平台配合</td><td>你实时决定发不发、发什么档、可即时止血</td></tr>
|
||||
<tr><td>对账</td><td>按“充进多少 / 平台报售出多少”对</td><td>按你签发条数对,最准</td></tr>
|
||||
<tr><td>换平台成本</td><td>低(码是通用的,换平台重充即可)</td><td>中(每个平台对接一次回调)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="rec"><b>建议:</b>起步用 <b>A(预充库存)</b>——零工程、当天能卖。跑通量之后,对主力发卡平台升级到 <b>B(API 实时签发)</b>拿回控制权与精准对账;两者可并存(不同渠道用不同模型)。</p>
|
||||
|
||||
<h2>落地节奏(先能卖,再自动化)</h2>
|
||||
|
||||
<div class="phase">阶段 0 · MVP(当天可开卖,零/极少开发)</div>
|
||||
<ul>
|
||||
<li>你们 <code>codes</code> 模块已可批量生成激活码 → 手动生成一批 → 挂到 <b>1 个发卡平台</b>(预充库存模型 A)或直接 Telegram 人工收 USDT/发卡平台链接。</li>
|
||||
<li>用户付款 → 平台秒发码 → 用户在客户端兑换。<b>验证需求、跑通话术与定价</b>。</li>
|
||||
</ul>
|
||||
|
||||
<div class="phase">阶段 1 · 半自动(API 实时签发 + 自动对账)</div>
|
||||
<ul>
|
||||
<li>后端加 <b>发卡回调接口</b> <code>POST /v1/codes/issue</code>(B 模型):验签 → 幂等键(平台订单号)→ 签发激活码返回。</li>
|
||||
<li>加<b>对账任务</b>:平台售出流水 ↔ 你签发/核销记录,日结核对,异常告警(防超发/漏发/重放)。</li>
|
||||
</ul>
|
||||
|
||||
<div class="phase">阶段 2 · 自助下单页(可选,长期)</div>
|
||||
<ul>
|
||||
<li>用户中心 / 官网内做“获取激活码”自助页:接 <b>USDT 自动收款</b>(链上 watcher 到账→签发,你自己钱包,最干净)+ 可选 <b>Telegram Stars</b>。这条不依赖第三方发卡,抽成低、主体干净,但要自己做收款系统。</li>
|
||||
<li>发卡/Reseller 继续作为“支付宝/微信便利”的并行兜底渠道。</li>
|
||||
</ul>
|
||||
|
||||
<h2>激活码系统要补的接口(阶段 1)</h2>
|
||||
<div class="card">
|
||||
<h3>B 模型 · 发卡平台签发回调</h3>
|
||||
<pre>POST /v1/codes/issue # 发卡平台在“支付成功”后调用(服务端对服务端)
|
||||
headers: X-Reseller-Sign # HMAC 验签(每个 reseller 一个密钥,Bitwarden 存)
|
||||
body: {
|
||||
reseller_id, reseller_order_no, # 幂等键 = (reseller_id, reseller_order_no)
|
||||
sku, # 套餐档位 → 映射时长/等级
|
||||
amount, currency
|
||||
}
|
||||
→ 200 { code: "PGL-XXXX-XXXX-XXXX", expires_at } # 幂等:同一订单号重复调用返回同一张码</pre>
|
||||
<p class="small">要点:① <b>幂等</b>——同一 <code>reseller_order_no</code> 只签发一张(防平台重试超发);② <b>验签</b>——HMAC + 时间戳防伪造/重放;③ <b>SKU 映射表</b>——reseller 的商品 ↔ 你的套餐;④ 记录 <code>issued_by=reseller</code> 便于对账与止血作废。</p>
|
||||
</div>
|
||||
|
||||
<h2>对账 · 防滥用 · 风险边界</h2>
|
||||
<ul>
|
||||
<li><b>对账</b>:每个渠道的“进货/售出”对上“签发/核销”。A 模型对充值批次,B 模型对签发流水。日结 + 异常告警。</li>
|
||||
<li><b>防滥用</b>:激活码一次性、绑定套餐、可设过期;核销侧限频;发现某 reseller 泄漏/超卖可<b>整批作废</b>未核销的码。</li>
|
||||
<li><b>风险边界(务必守住)</b>:见下。</li>
|
||||
</ul>
|
||||
<div class="warnbox">
|
||||
<b>红线:</b>Pangolin 主体(尤其中国岩美)<b>永不</b>直接对接跑分/四方聚合、永不用中国支付账户收 VPN 款。法币收款的通道与冻卡/跑路风险,<b>只允许存在于发卡平台/Reseller 那一层</b>。你和用户之间流动的只有<b>激活码</b>,钱到你手里时已经是发卡平台的<b>结算款</b>(法币/USDT,走 US LLC)。挑选 reseller 时优先<b>预付结算 / 短结算周期</b>,降低平台跑路敞口。
|
||||
</div>
|
||||
|
||||
<h2>主体与渠道归属</h2>
|
||||
<table>
|
||||
<thead><tr><th>渠道</th><th>收款主体</th><th>干净度</th><th>定位</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>发卡/Reseller(支付宝/微信)</td><td>发卡平台(非你)</td><td>风险外包,你侧干净</td><td>大陆“便利”主力</td></tr>
|
||||
<tr><td>USDT 自动收款</td><td>你 · US LLC 钱包</td><td><span class="tag ok">干净</span></td><td>大陆技术型用户 / 长期主力</td></tr>
|
||||
<tr><td>Telegram Stars</td><td>TG 官方 → 你</td><td><span class="tag ok">干净</span></td><td>补充(抽成,走 Apple/Google IAP)</td></tr>
|
||||
<tr><td>App Store IAP(海外区)</td><td>Apple → 你</td><td><span class="tag ok">干净</span></td><td>海外华人补充</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="small">加密/结算入账的会计处理归 <code>code/accounting</code> 专门 agent,不在本项目做费用台账。</p>
|
||||
|
||||
<h2>待定 / 下一步</h2>
|
||||
<ul>
|
||||
<li>选定 <b>1 个起步发卡平台</b>(先 A 预充库存跑 MVP)——需要你定候选。</li>
|
||||
<li>确认 <b>SKU ↔ 套餐</b> 映射与各档定价(对齐 <code>design/CLAUDE.md §7</code> / 官网 Pricing)。</li>
|
||||
<li>阶段 1 排期:<code>POST /v1/codes/issue</code> 回调 + 对账任务(落 todo,走 codes 模块)。</li>
|
||||
<li>是否并行启动 <b>阶段 2 的 USDT 自动收款</b>(自建下单页),还是先只靠发卡 + 人工。</li>
|
||||
</ul>
|
||||
|
||||
<p class="small" style="margin-top:32px">相关:灰产 bot 收款机制分析(跑分/四方聚合)见对话记录;本方案是其“合规化替身”——同样的“付完秒发货”,风险不落在你的主体上。</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
# pangolin-pay 加密货币交易引擎(钱包 + watcher + 收款侦测 + 归集)
|
||||
|
||||
> #34 的**第一块、可独立执行、能用真实小额 USDT 端到端验证**的基础件。**只做加密货币的"交易"核心**——
|
||||
> 生成/派生地址、侦测链上到账、归集(sweep)——**不含**独角数卡对接、不含 `/internal/codes/mint` 发码
|
||||
> (那是"发货侧",本引擎跑通后再接)。
|
||||
>
|
||||
> **成果物**:一个独立 Go 服务 `pangolin-pay`(部署在独立海外 VPS),对外提供"建单→给收款地址→侦测到账→
|
||||
> 标记已付"的 HTTP 接口 + 一套离线归集工具。跑通后,任何门面(独角数卡 / 自建下单页)都能挂上来。
|
||||
>
|
||||
> **安全铁律(贯穿)**:热服务只持 **xpub**(watch-only 派生地址)+ TronGrid key + 金库收款地址,**绝不持私钥**;
|
||||
> 私钥/助记词冷存,归集在离线端签名。参考知识:brain `notes/dev/crypto-hd-wallet-basics.html`。
|
||||
>
|
||||
> **关键常量(开工核对无误再写死)**:USDT-TRC20 合约 `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`(6 位小数);
|
||||
> TRON 派生 `m/44'/195'/0'/0/i`;TronGrid `https://api.trongrid.io`。
|
||||
|
||||
---
|
||||
|
||||
## Phase A — 钱包与密钥(全程离线)
|
||||
|
||||
- [ ] A.1 **离线**生成**两套**助记词(断网 Mac 跑 Ian Coleman standalone / `bip_utils`,选 Coin=TRX、24 词、English):
|
||||
- **钱包 A · 运营收款**;**钱包 B · 冷备金库**(另一套,独立冷存)
|
||||
- [ ] A.2 导出**钱包 A 的 account xpub**(`m/44'/195'/0'` 的 Account Extended **Public** Key);取**钱包 B 的地址0**(`T...`)当**归集目标**
|
||||
- [ ] A.3 冷存:助记词 A/B **分开**手抄/钢板 + Bitwarden;**只把** xpub_A + sweep 地址_B 放进 `pangolin-pay` 配置(Bitwarden 管密钥)
|
||||
- [ ] A.4 **交叉核对**:B 阶段的 Go 派生实现算出的地址 0..4,必须与 Ian Coleman 显示的**逐个一致**(防派生实现不一致 → 收款地址对不上 → 收不到钱)
|
||||
|
||||
---
|
||||
|
||||
## Phase B — 服务骨架 + HD 派生
|
||||
|
||||
- [ ] B.1 建 Go 服务 `pangolin-pay`(独立 cmd 或独立仓)。配置:`xpub_A` / `sweep_addr_B` / `TRONGRID_API_KEY` / 确认数 / 订单超时。自带 SQLite
|
||||
- [ ] B.2 **HD 派生模块**:`tyler-smith/go-bip39` + `btcsuite/.../hdkeychain` 从 **xpub_A** watch-only 派生 `.../0/i` 公钥 →
|
||||
TRON 地址(公钥 → Keccak-256 取后 20 字节 → 前缀 `0x41` → Base58Check → `T...`)。**单测:派生地址对齐 Ian Coleman(A.4)**
|
||||
- [ ] B.3 数据模型:
|
||||
- `pay_orders(order_no PK, sku, expect_amount, addr_index, address, status, tx_id, confirmations, created_at, expires_at)`
|
||||
- `addr_cursor(next_index)` — HD 派生游标,单调递增,地址**不复用**
|
||||
|
||||
---
|
||||
|
||||
## Phase C — 收款(建单 + 侦测到账)
|
||||
|
||||
- [ ] C.1 `POST /order`:入参 `sku`/基准金额 → cursor++ 派生**新收款地址** → 写单(`expect_amount` 做**唯一尾数**去重、`expires=+15min`)→ 返回 `{order_no, address, expect_amount, expires_at}`
|
||||
- [ ] C.2 `GET /order/{order_no}`:返回 `status`(pending/paid/expired)+ 到账信息
|
||||
- [ ] C.3 **TronGrid watcher**(轮询):对每个 pending 单地址查 TRC20 转入
|
||||
- `GET /v1/accounts/{addr}/transactions/trc20?only_confirmed=true&contract_address=<USDT>&limit=...`(头带 `TRON-PRO-API-KEY`)
|
||||
- 匹配:`to==address` 且 `token==USDT` 且 `value≥expect_amount`(USDT 6 位小数,注意单位换算)且已确认 → `status=paid`、记 `tx_id`
|
||||
- [ ] C.4 **幂等**(同 `tx_id` 只认一次)+ **超时** pending→expired(地址不复用)+ **崩溃恢复**(从 DB 未决单继续轮询)
|
||||
- [ ] C.5 TronGrid 限流退避 + 重试;测试用 mock TronGrid 覆盖:金额匹配 / 确认门槛 / 超时 / 幂等 / 恢复
|
||||
|
||||
---
|
||||
|
||||
## Phase D — 归集(sweep,离线签名)
|
||||
|
||||
- [ ] D.1 **归集清单**:watcher(xpub)列出有余额地址 `[(index, address, balance)]`
|
||||
- [ ] D.2 **gas**:从一个 `gas 钱包`给每个待归集地址打少量 **TRX**(TRC20 转账吃 energy;收款地址无 TRX);量大改**能量租赁**降本
|
||||
- [ ] D.3 **离线签名**(离线机,导入**钱包 A 助记词**):派生 `address_i` 私钥 → 构造 TRC20 `transfer(sweep_addr_B, balance)` → **离线签名**
|
||||
- [ ] D.4 **广播**:把签好的交易经 TronGrid `POST /wallet/broadcasttransaction` 上链(**只有签名后的交易出门,私钥不出门**)
|
||||
- [ ] D.5 半自动脚本(离线端)+ **归集 runbook**;触发=阈值(攒够 X)或定时;热派生地址不长期囤大额
|
||||
|
||||
---
|
||||
|
||||
## Phase E — 端到端验证(真实小额 USDT)
|
||||
|
||||
- [ ] E.1 真单:`POST /order` → 往返回的地址转 **1 USDT(TRC20)** → watcher 侦测 + 确认 → `GET /order` 变 `paid`
|
||||
- [ ] E.2 归集:对该地址执行 D 流程 → USDT 到达**钱包 B 冷地址**
|
||||
- [ ] E.3 安全核对:`pangolin-pay` 进程/配置**无任何私钥/助记词**(只有 xpub);密钥不入 git(红线扫描);地址不复用
|
||||
|
||||
---
|
||||
|
||||
## Verification(判据)
|
||||
|
||||
- `go test ./...`:HD 派生对齐 Ian Coleman、watcher 匹配/确认/超时/幂等/恢复全绿。
|
||||
- 真机:一笔真 1 USDT 从建单→侦测→确认→归集到冷地址全通。
|
||||
- 安全:热服务无私钥、密钥不入 git、每单唯一地址不复用——逐项核对。
|
||||
|
||||
## 不在本轮
|
||||
|
||||
- 独角数卡门面对接 + epay 支付口(下一块:接门面)。
|
||||
- `/internal/codes/mint` JIT 发码 + 兑换闭环(发货侧,#34 后续)。
|
||||
- HD 自动 sweep(先人工离线归集)、多币种(先只 USDT-TRC20)、watcher 高可用。
|
||||
@@ -0,0 +1,185 @@
|
||||
# 收款闭环 · 独角数卡 + 自托管 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`(含小额容差处理)
|
||||
- [ ] 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/accounting` agent(本项目不做费用台账)
|
||||
- [ ] 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 独立收款口(海外补充,另排)。
|
||||
Reference in New Issue
Block a user