Files
pangolin/docs/payment-reseller-fulfillment-design.html
wangjia 5fd0d3c148 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>
2026-07-09 15:51:10 +08:00

258 lines
19 KiB
HTML
Raw Permalink 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.
<!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 StarsTG 官方)</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>BAPI 实时签发)</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>