docs(pay): 支付渠道公开文档(哪吒/支付宝 + stripe/crypto 框架)+ 登记 index

单一入口登记各支付渠道接入信息(类型/结算币种/官方文档/provider 位置/
Method 名/接入形态/凭证位置指针/状态),新渠道后续往这加。不含任何
商户号/密钥/私钥/地址等敏感值,凭证一律指向 Bitwarden 条目名。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-11 12:06:27 +08:00
parent a04947ad80
commit e7f989588e
2 changed files with 172 additions and 0 deletions
+5
View File
@@ -29,6 +29,11 @@
<p class="lead">独立支付服务(Go·Gin·GORM),多业务 × 多渠道(支付宝先行,微信留接口)。新增文档请同步登记此处。</p>
<h2>🎨 设计方案 / 技术方案</h2>
<a class="doc" href="./payment-channels.html">
<div class="title">支付渠道接入登记 ⭐</div>
<div class="desc">公开文档:各支付渠道接入信息的单一登记入口(新渠道往这加)。哪吒支付(开发中)/ 支付宝 / Stripe / Crypto(USDT-TRC20) 四渠道,统一字段:类型/结算币种/官方文档/provider 位置/Method 名/接入形态概要/凭证管理位置指针/状态。不含任何商户号、密钥、私钥、地址等敏感值,凭证一律指向 Bitwarden 条目名。</div>
<div class="meta">v1.0 · 2026-07-11 · 公开 · 持续追加</div>
</a>
<a class="doc" href="./pay-v2-unified-gateway-design.html">
<div class="title">pay v2 · 多产品统一支付 + 履约网关设计 ⭐</div>
<div class="desc">把 pay 干净重设计成 pangolin/jiu/dudu/未来共用的支付层。三个正交关注点:pay 收款(本设计核心)/ codes 兑换(平级共享内核)/ entitlement 开通(各产品自留)。核心模型 Order+Attempt+Account(一单 N 渠道只一个成功·可取消·可退款·多账户路由);6 个 render_type 多态(pay 下发数据不下发 UI,样式归业务方;加同形态新渠道零改 client);Provider adapterverify_callback 吸收渠道差异);webhook v2 加 event_type(退款/订阅/拒付,断代 v2.0.0);退款三向(业务/人工 crypto/平台 IAP);促销折扣归业务、金额权威归 pay;多币种 + 配置驱动渠道 + 国内外同码双部署;crypto(USDT-TRC20 自托管)抗风控兜底;recurring 设计进模型暂不实现。含 SVG 架构图 + 设计决策知识点。</div>
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>pay · 支付渠道接入登记</title>
<style>
:root{--bg:#0d1117;--card:#161b22;--card-2:#1c2330;--border:#283041;--fg:#e6edf3;--fg-soft:#aeb9c7;--muted:#7d8896;--accent:#58a6ff;--ok:#3fb950;--warn:#d29922;--bad:#f85149;--radius:14px;--mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1200px 600px at 80% -10%,rgba(88,166,255,.08),transparent 60%),var(--bg);color:var(--fg);font:15px/1.7 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;-webkit-font-smoothing:antialiased}
.wrap{max-width:1040px;margin:0 auto;padding:44px 24px 96px}
.eyebrow{color:var(--accent);font-weight:600;letter-spacing:.12em;font-size:12px;text-transform:uppercase}
h1{font-size:28px;margin:8px 0 8px}
.lead{color:var(--fg-soft);margin:0 0 20px}
h2{font-size:19px;margin:38px 0 12px;color:var(--accent);border-bottom:1px solid var(--border);padding-bottom:8px}
h3{font-size:16px;margin:22px 0 8px;color:var(--fg)}
p{color:var(--fg-soft)}
a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}
code{font-family:var(--mono);font-size:.85em;background:var(--card-2);padding:1px 6px;border-radius:5px;color:#cdd9e5}
b{color:#fff}
ul,ol{color:var(--fg-soft);padding-left:20px}
li{margin:3px 0}
table{width:100%;border-collapse:collapse;margin:14px 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(--fg-soft);font-weight:600;font-size:12.5px;white-space:nowrap}
td{color:var(--fg-soft)}
table.fields td:first-child{font-family:var(--mono);font-size:12px;color:#cdd9e5;white-space:nowrap;width:1%}
.lead-box{background:linear-gradient(180deg,rgba(88,166,255,.10),transparent);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;margin:0 0 8px}
.notice{border-left:3px solid var(--warn);background:var(--card-2);border-radius:0 8px 8px 0;padding:12px 16px;margin:14px 0;color:var(--fg-soft)}
.notice b{color:var(--warn)}
.tag{display:inline-block;font-size:11.5px;font-weight:600;padding:2px 9px;border-radius:999px;white-space:nowrap}
.tag.ok{background:rgba(63,185,80,.16);color:var(--ok)}
.tag.dev{background:rgba(210,153,34,.16);color:var(--warn)}
.tag.plan{background:rgba(125,136,150,.18);color:var(--muted)}
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:18px 22px;margin:16px 0}
.card h3{margin-top:0;color:var(--accent);display:flex;align-items:center;gap:10px}
.card h3 .tag{font-size:11px}
.kv{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:13.5px;margin:10px 0}
.kv dt{color:var(--muted)}
.kv dd{margin:0;color:var(--fg-soft)}
.svgwrap{overflow-x:auto;background:#0a0e14;border:1px solid var(--border);border-radius:12px;padding:10px 6px;margin:14px 0}
.stub{color:var(--muted);font-size:13.5px}
</style>
</head>
<body>
<div class="wrap">
<div class="eyebrow">pay · Channel Registry</div>
<h1>支付渠道接入登记</h1>
<p class="lead">pay v2 支付渠道接入的<b>单一登记入口</b>——每接一个新渠道,在本文档追加一节。汇总各渠道的官方文档链接、pay 侧 provider 位置、接入形态概要、凭证存放位置指针。</p>
<div class="lead-box">
<b>公开性声明</b>:本文档<b>公开可读</b>,不含任何商户号 / API 密钥 / 私钥 / 证书 / 真实收款地址 / Bitwarden 具体取值。凭证一律只写「走 Bitwarden 某条目,经 rbw 灌 env」这类<b>位置指针</b>,实际值不出现在本文档或 git 仓库任何地方。
</div>
<h2>pay v2 Provider 接口</h2>
<p>每个渠道在 <code>internal/provider/&lt;name&gt;/</code> 下实现统一的 <code>provider.Provider</code> 接口(定义于 <code>internal/provider/provider.go</code>),网关侧不感知渠道差异:</p>
<table class="fields">
<thead><tr><th>方法</th><th>作用</th></tr></thead>
<tbody>
<tr><td>Method()</td><td>渠道注册名(如 <code>alipay</code>/<code>stripe</code>/<code>crypto</code>),也是配置里 <code>method</code> 字段的值</td></tr>
<tr><td>Capabilities()</td><td>渠道能力自述:支持的 render_type、是否支持退款/续订、结算币种、可服务区域</td></tr>
<tr><td>Create(ctx, CreateRequest)</td><td>拿 pay 已定金额/币种/账户去渠道建单,产出 <code>Session{ProviderRef, RenderType, Payload}</code></td></tr>
<tr><td>VerifyCallback(ctx, CallbackInput)</td><td>解析并验签渠道异步回调(HTTP body/头/query),产出归一化 <code>PaidEvent</code></td></tr>
<tr><td>Query(ctx, QueryRequest)</td><td>主动查单兜底(回调丢失时的定时轮询/对账)</td></tr>
</tbody>
</table>
<p>可选扩展接口:<code>RefundingProvider</code>(退款)、<code>RecurringProvider</code> / <code>SubscriptionProvider</code>(续订)、<code>OrphanScanner</code>(自托管渠道的孤儿到账扫描)。</p>
<p><b>render_type</b>(客户端只认这 6 种付款意图形态,不下发任何 UI,样式归业务方自绘):</p>
<table class="fields">
<thead><tr><th></th><th>含义</th><th>当前用者</th></tr></thead>
<tbody>
<tr><td>redirect</td><td>跳转到渠道收银台 URL</td><td>alipaypage/wap)、stripeCheckout</td></tr>
<tr><td>qr</td><td>渠道二维码内容,前端自行渲染</td><td>alipay(当面付 precreate</td></tr>
<tr><td>crypto_address</td><td>收款地址 + 精确到账金额</td><td>crypto</td></tr>
<tr><td>native_pay</td><td>原生支付组件拉起(如小程序/App SDK)</td><td>暂无渠道使用</td></tr>
<tr><td>sdk_handoff</td><td>交由客户端 SDK 处理支付流程</td><td>暂无渠道使用</td></tr>
<tr><td>iap_receipt</td><td>App Store/Play 内购收据校验</td><td>暂无渠道使用</td></tr>
</tbody>
</table>
<h2>渠道总览</h2>
<table class="fields">
<thead><tr><th>渠道</th><th>类型</th><th>结算币种</th><th>Method()</th><th>状态</th></tr></thead>
<tbody>
<tr><td>哪吒支付</td><td>第三方 RSA 聚合支付</td><td>CNY</td><td>nezha</td><td><span class="tag dev">开发中</span></td></tr>
<tr><td>支付宝</td><td>自有商户直连</td><td>CNY</td><td>alipay</td><td><span class="tag ok">已接入</span></td></tr>
<tr><td>Stripe</td><td>自有商户直连(海外网关)</td><td>USD</td><td>stripe</td><td><span class="tag ok">已接入</span></td></tr>
<tr><td>CryptoUSDT-TRC20</td><td>链上自托管</td><td>USDT</td><td>crypto</td><td><span class="tag ok">已接入</span></td></tr>
</tbody>
</table>
<h2>① 哪吒支付(nezha</h2>
<div class="card">
<h3>哪吒支付 <span class="tag dev">开发中</span></h3>
<div class="kv">
<dt>类型</dt><dd>第三方 RSA 聚合支付</dd>
<dt>结算币种</dt><dd>CNY</dd>
<dt>官方文档</dt><dd><a href="https://nzzf.org/doc/index.html" target="_blank" rel="noopener">https://nzzf.org/doc/index.html</a></dd>
<dt>provider 位置</dt><dd><code>internal/provider/nezha/</code>(待建)</dd>
<dt>Method() 名</dt><dd><code>nezha</code></dd>
<dt>凭证管理</dt><dd>Bitwarden 条目 <code>nzzf</code>(商户号 <code>pid</code> + 商户私钥 + 平台公钥),仅按需 <code>rbw get</code> 取用,不入文档/git</dd>
</div>
<p><b>接入形态概要</b>(据官方文档):</p>
<ul>
<li><b>签名</b><code>SHA256WithRSA</code> + Base64。签名串 = 非空参数(排除 <code>sign</code>/<code>sign_type</code>/数组)按参数名 ASCII 升序拼 <code>key=value&amp;...</code>(不拼密钥);商户私钥签、平台公钥验。</li>
<li><b>下单</b><code>POST https://nzzf.org/api/pay/create</code>form-urlencoded)。字段:<code>pid</code> / <code>type</code> / <code>out_trade_no</code> / <code>notify_url</code> / <code>return_url</code> / <code>name</code> / <code>money</code>(元)/ <code>timestamp</code> / <code>sign</code> / <code>sign_type</code>;响应 JSON 含 <code>payurl</code>(收银台跳转,对应 render_type=<code>redirect</code>+ <code>qrcode</code>(二维码,对应 <code>qr</code>)。</li>
<li><b>异步通知</b>GET 回调,字段含 <code>trade_no</code> / <code>out_trade_no</code> / <code>money</code> / <code>trade_status</code> / <code>sign</code>;平台公钥验签;<code>trade_status=TRADE_SUCCESS</code> 为成功;商户须回纯文本 <code>success</code>(对应 <code>VerifyCallback</code>)。</li>
<li><b>查单</b><code>POST https://nzzf.org/api/pay/query</code><code>status=1</code> 表示已支付(对应 <code>Query</code>)。</li>
<li><b>支持的支付方式</b>:支付宝等(由 <code>type=alipay</code> 等参数选择,聚合渠道内部路由)。</li>
</ul>
</div>
<h2>② 支付宝(alipay</h2>
<div class="card">
<h3>支付宝 <span class="tag ok">已接入</span></h3>
<div class="kv">
<dt>类型</dt><dd>自有商户直连</dd>
<dt>结算币种</dt><dd>CNY</dd>
<dt>官方文档</dt><dd><a href="https://opendocs.alipay.com/" target="_blank" rel="noopener">https://opendocs.alipay.com/</a>(当面付 / 手机网站支付 / 电脑网站支付)</dd>
<dt>provider 位置</dt><dd><code>internal/provider/alipay/alipay.go</code></dd>
<dt>Method() 名</dt><dd><code>alipay</code></dd>
<dt>凭证管理</dt><dd>Bitwarden 条目 <code>ali pay key</code>(字段 <code>app_private_key</code> / <code>alipay_public_key</code> / <code>appid</code>),经 <code>rbw get</code> 灌服务器 env(见 <a href="./上线状态与部署Runbook.html">上线状态与部署 Runbook</a></dd>
</div>
<p><b>接入形态概要</b>(源自 <code>internal/provider/alipay/alipay.go</code> + <a href="./支付宝密钥申请与备案依赖.html">支付宝密钥申请与备案依赖</a> / <a href="./支付宝收款页时序图与架构.html">支付宝收款页时序图与架构</a>):</p>
<ul>
<li><b>签名</b>RSA2<code>SHA256WithRSA</code>),商户应用私钥签、支付宝公钥验(<code>smartwalle/alipay/v3</code> SDK 封装)。</li>
<li><b>下单</b>:按 <code>metadata["render"]</code> 与端型三选一——当面付 <code>alipay.trade.precreate</code><code>render=qr</code>,出二维码,render_type=<code>qr</code>);手机端 <code>alipay.trade.wap.pay</code><code>TradeWapPay</code><code>QUICK_WAP_WAY</code>);PC 端 <code>alipay.trade.page.pay</code><code>TradePagePay</code><code>FAST_INSTANT_TRADE_PAY</code>),两者均产出跳转 URLrender_type=<code>redirect</code>)。渠道以 <code>out_trade_no</code> 归位,仅支持 CNY。</li>
<li><b>异步通知</b><code>DecodeNotification</code> 用支付宝公钥验签;交易状态归一化——<code>WAIT_BUYER_PAY</code>→pending<code>TRADE_SUCCESS</code>/<code>TRADE_FINISHED</code>→succeeded<code>TRADE_CLOSED</code>→failed;商户须回 <code>success</code></li>
<li><b>查单兜底</b><code>alipay.trade.query</code><code>TradeQuery</code>),用于回调丢失/超时时的定时轮询。</li>
<li><b>退款</b><code>alipay.trade.refund</code>(同步接口,<code>TradeRefund</code>),<code>out_request_no</code> 作幂等键支持部分退款。</li>
</ul>
</div>
<h2>③ Stripestripe</h2>
<div class="card">
<h3>Stripe <span class="tag ok">已接入</span></h3>
<div class="kv">
<dt>类型</dt><dd>自有商户直连(海外网关)</dd>
<dt>结算币种</dt><dd>USD</dd>
<dt>官方文档</dt><dd><a href="https://docs.stripe.com/" target="_blank" rel="noopener">https://docs.stripe.com/</a>Checkout Sessions / Webhooks / Subscriptions</dd>
<dt>provider 位置</dt><dd><code>internal/provider/stripe/stripe.go</code></dd>
<dt>Method() 名</dt><dd><code>stripe</code></dd>
<dt>凭证管理</dt><dd>BitwardenStripe API Secret Key + Webhook Signing Secret,账户级 <code>CredentialEnvPrefix</code> 对应 env 后缀 <code>SECRET_KEY</code> / <code>WEBHOOK_SECRET</code>),经 rbw 灌 env;具体条目名待生产上线时在 Bitwarden 建立,不写入本文档</dd>
</div>
<p><b>接入形态概要</b>:托管 Checkouthosted Checkout Session),一次性支付走 <code>mode=payment</code>、订阅走 <code>mode=subscription</code>,均产出跳转 URLrender_type=<code>redirect</code>)。Webhook 验签用官方 SDK <code>webhook.ConstructEvent</code>HMAC + 时间容差);处理 <code>checkout.session.completed</code>(入账/订阅激活)、<code>invoice.paid</code>(续费)、<code>customer.subscription.deleted</code>(订阅取消)、<code>invoice.payment_failed</code>(逾期)、<code>charge.dispute.created</code>(拒付)。退款走 <code>/v1/refunds</code><code>Idempotency-Key</code> 幂等);订阅续费由 Stripe 网关自身调度(<code>RecurringKindGatewayScheduled</code>),pay 不主动发起扣款。</p>
</div>
<h2>④ Crypto · USDT-TRC20crypto</h2>
<div class="card">
<h3>CryptoUSDT-TRC20 <span class="tag ok">已接入</span></h3>
<div class="kv">
<dt>类型</dt><dd>链上自托管</dd>
<dt>结算币种</dt><dd>USDT</dd>
<dt>官方文档</dt><dd><a href="https://developers.tron.network/" target="_blank" rel="noopener">https://developers.tron.network/</a>TronGrid API,用于扫链查询)</dd>
<dt>provider 位置</dt><dd><code>internal/provider/crypto/crypto.go</code></dd>
<dt>Method() 名</dt><dd><code>crypto</code></dd>
<dt>凭证管理</dt><dd>Bitwarden(收款地址 watch-only + 可选 TronGrid API Key,账户级 env 后缀 <code>ADDRESS</code> / <code>TRONGRID_KEY</code>),私钥不由 pay 持有;具体条目名不写入本文档</dd>
</div>
<p><b>接入形态概要</b>:每账户一个固定收款地址(watch-only,pay 从不持有私钥),每单在基础价上叠加随机唯一尾数(微 USDT 级)区分订单身份,冷却窗防金额复用误配。无渠道异步回调(<code>VerifyCallback</code> 恒返回 <code>ErrNotSupported</code>),到账完全靠<b>查单</b>:定时扫 TronGrid <code>only_confirmed</code> 已确认交易,按「收款地址 + 精确金额 + 块时晚于建单时间」匹配(render_type=<code>crypto_address</code>)。另实现 <code>OrphanScanner</code>:扫描到账但不匹配任何在途订单的转账,供对账人工核对。资金归集(sweep 到冷存储)是独立的线下操作,不在 pay 范围内。</p>
</div>
<h2>凭证管理约定</h2>
<p class="stub">所有渠道凭证统一走 <b>Bitwarden</b>,按需 <code>rbw get</code> 取出灌入服务器环境变量(<code>CredentialEnvPrefix</code> + 字段名,如 <code>ALIPAY_APP_PRIVATE_KEY</code>),不落配置文件、不落 git 仓库、不进聊天记录。已上线渠道的具体 Bitwarden 条目名见各渠道小节;未上线渠道的条目在生产接入前另建,同样不预先写入本文档。部署细节参见 <a href="./上线状态与部署Runbook.html">上线状态与部署 Runbook</a></p>
</div>
</body>
</html>