docs: P2 计划 HTML 阅读版 + index 改指

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-10 09:52:14 +08:00
parent a4ae5fcb64
commit d1fd0abd73
2 changed files with 108 additions and 1 deletions
+1 -1
View File
@@ -57,7 +57,7 @@
<div class="desc">阅读版;执行真相源 <code>docs/superpowers/plans/2026-07-10-pay-v2-p1-core-model.md</code>(含 checkbox)。<a href="./pay-v2-unified-gateway-design.html">pay v2 设计</a>的首个落地阶段(8 阶段之 P1)。4 个 TDD 任务:money 包(int64 最小单位+币种)→ Order/Attempt/Account/Refund 模型+状态机+<code>:memory:</code> 测试约定 → OrderStore(幂等标付/取消/列表,条件UPDATE+RowsAffected)→ 账户配置注册表(凭证走 env)。沿用 GORM AutoMigrate/glebarez sqlite 惯例;金额从 string 元改 int64。P2-P8(Provider/收款管线/渠道 adapter/退款/路由/对账/codes 库/订阅)各自成计划、落地前细化。</div>
<div class="meta">P1 · 2026-07-10 · Go(Gin+GORM+sqlite) · 待执行</div>
</a>
<a class="doc" href="./superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md">
<a class="doc" href="./pay-v2-p2-plan.html">
<div class="title">pay v2 · P2 收款管线 + Provider 抽象 + webhook v2(计划)</div>
<div class="desc">7 个 TDD 任务:Provider 接口(create/verify_callback/query)+6 render_type+PaidEvent+注册表 → fake provider → OrderStore 扩展 → 一次性收款管线 gateway → 统一入账/开通(复用 P1 MarkAttemptPaid 幂等)→ webhook v2 outbox+Notifier(event_type+HMAC)→ HTTP /v1 接线。复用 P1 全部类型、免 docker。/v1 与 v1 /api/v1 并存。</div>
<div class="meta">P2 · 2026-07-10 · 待执行 · 真相源 .md</div>
+107
View File
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>pay v2 · P2 收款管线 + Provider 抽象(阅读版)</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;--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}
.wrap{max-width:960px;margin:0 auto;padding:44px 24px 96px}
.back{display:inline-block;margin-bottom:18px;font-size:13px}
a{color:var(--accent);text-decoration:none}
h1{font-size:26px;margin:6px 0 8px}
.lead{color:var(--fg-soft);margin:0 0 22px}
h2{font-size:18px;margin:34px 0 10px;color:var(--accent);border-bottom:1px solid var(--border);padding-bottom:8px}
code{font-family:var(--mono);font-size:.85em;background:var(--card-2);padding:1px 6px;border-radius:5px;color:#cdd9e5}
pre{background:#0a0e14;border:1px solid var(--border);border-radius:10px;padding:12px 14px;overflow-x:auto;font-family:var(--mono);font-size:12px;line-height:1.5;color:#cdd9e5}
pre.code .k{color:#ff7b72}pre.code .ty{color:#79c0ff}pre.code .fn{color:#d2a8ff}pre.code .co{color:#8b949e;font-style:italic}
b{color:#fff}
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:13px}
table.fields td:first-child{font-family:var(--mono);font-size:12px;color:#cdd9e5;white-space:nowrap;width:1%}
.card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:14px 18px;margin:12px 0}
.card h3{margin:0 0 6px;color:var(--accent);font-size:15px}
.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}
.warn{background:rgba(210,153,34,.08);border:1px solid rgba(210,153,34,.35);border-radius:12px;padding:12px 16px;margin:14px 0;font-size:13.5px}
.path{color:var(--muted);font-size:12px;font-family:var(--mono);margin-top:6px}
</style>
</head>
<body>
<div class="wrap">
<a class="back" href="index.html">← 返回文档索引</a>
<h1>pay v2 · P2 收款管线 + Provider 抽象 + webhook v2<span style="font-size:14px"> (阅读版)</span></h1>
<p class="lead">执行真相源 <code>docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md</code>(含 checkbox + 每步完整代码/测试)。设计见 <a href="pay-v2-unified-gateway-design.html">pay v2 设计</a>,DAG 见 <a href="pay-v2-roadmap-dag.html">依赖图</a></p>
<div class="lead-box">
<b>P2 目标</b>:在 P1 数据地基上搭起<b>一次性收款的完整管线</b> —— Provider 渠道抽象(6 render_type)+ 建单/查单/重试/取消 + 统一入账/开通(webhook 与 query 都归一成 PaidEvent,复用 P1 的幂等标付)+ pay→业务方 webhook v2(带 <code>event_type</code>)。用一个 <b>fake provider</b> 端到端验证管线,真渠道在 P3。全程复用 P1、免 docker。
</div>
<h2>7 个 TDD 任务</h2>
<div class="card">
<h3>Task 1 · Provider 抽象接口 + render_type + PaidEvent + 注册表</h3>
<p>文件 <code>internal/provider/provider.go</code>。核心契约,P3 所有渠道 adapter 依赖它:</p>
<pre class="code"><span class="k">type</span> <span class="ty">Provider</span> <span class="k">interface</span> {
<span class="fn">Method</span>() <span class="ty">string</span>
<span class="fn">Capabilities</span>() Capabilities
<span class="fn">Create</span>(ctx, CreateRequest) (*Session, <span class="ty">error</span>)
<span class="fn">VerifyCallback</span>(ctx, CallbackInput) (*PaidEvent, <span class="ty">error</span>)
<span class="fn">Query</span>(ctx, providerRef <span class="ty">string</span>) (*PaidEvent, <span class="ty">error</span>)
}
<span class="co">// 可选子接口:RefundingProvider(P4)、RecurringProvider(P8)</span>
<span class="k">type</span> <span class="ty">Session</span> <span class="k">struct</span> { ProviderRef <span class="ty">string</span>; RenderType <span class="ty">string</span>; Payload map[<span class="ty">string</span>]any; ExpiresAt *time.Time }
<span class="k">type</span> <span class="ty">PaidEvent</span> <span class="k">struct</span> { ProviderRef <span class="ty">string</span>; Status PaidStatus; PaidAmountMinor <span class="ty">int64</span>; PaidCurrency, Raw <span class="ty">string</span> }</pre>
<p>6 个 render_type 常量 + PaidPending/Succeeded/Failed;<code>Registry</code>(Register/Get/Methods + ErrUnknownMethod)。</p>
</div>
<div class="card">
<h3>Task 2 · fake provider(测试用)</h3>
<code>internal/provider/fake/fake.go</code> —— 实现 Provider,<code>Method()=="fake"</code>、render=<code>crypto_address</code>;带 <code>SetQueryResult</code> 测试 seam。P2 用它端到端验管线,不接真渠道。
</div>
<div class="card">
<h3>Task 3 · OrderStore 扩展(查询)</h3>
<code>internal/store/order_query.go</code>:<code>GetOrder</code> / <code>AttemptByProviderRef</code> / <code>ListAttemptsByStatus</code> / <code>ExpirePendingAttempts</code> + 哨兵错误。为管线定位订单/尝试用。
</div>
<div class="card">
<h3>Task 4 · 一次性收款管线 gateway</h3>
<code>internal/gateway/gateway.go</code>:<code>CreateOrder</code>(选 provider→create→落 Order+Attempt→返回 <code>{order_no, session:{render_type,payload}}</code>)/ <code>GetOrder</code> / <code>RetryOrder</code> / <code>CancelOrder</code>。接口 <code>ProductResolver.Resolve(sku)</code>(取权威价/币种/subject/bizCode)、<code>WebhookEnqueuer.Enqueue(...)</code>
</div>
<div class="card">
<h3>Task 5 · 统一入账/开通管线 settle</h3>
<code>internal/gateway/settle.go</code>:<code>Settle(ctx, *PaidEvent)</code> = 归一 → 定位(provider_ref→attempt→order)→ 币种/金额核对 → <b>复用 P1 <code>MarkAttemptPaid</code> 幂等</b> → 仅翻转时入队 <code>payment.succeeded</code><code>HandleCallback</code>(webhook 入口)、<code>SyncPendingAttempts</code>(query 兜底)都汇入这条。<code>SettleResult</code>:ignored/not_found/amount_mismatch/duplicate/processed。
</div>
<div class="card">
<h3>Task 6 · webhook v2 outbox + Notifier</h3>
<code>model.WebhookDelivery</code>(<code>uniqueIndex(out_trade_no,event_type)</code> 幂等)+ <code>store.WebhookStore</code>(Enqueue/ListUndelivered/MarkDelivered/MarkFailed)+ <code>webhook.Notifier</code>(DeliverPending/Start,HMAC 头 <code>X-Pay-System/Event/Timestamp/Nonce/Sign</code>,失败重试)。<b>event_type 从 <code>payment.succeeded</code></b>
</div>
<div class="card">
<h3>Task 7 · HTTP /v1 接线</h3>
<code>router.SetupV2</code><code>POST /v1/orders</code><code>GET /v1/orders/:no</code><code>.../retry</code><code>.../cancel</code><code>POST /v1/callback/:method</code>;<code>main.go</code> 装配 provider 注册表 + gateway + notifier,AutoMigrate 加 WebhookDelivery。
</div>
<h2>关键设计取舍(计划 Self-Review 已记)</h2>
<table class="fields">
<thead><tr><th>取舍</th><th>说明</th></tr></thead>
<tbody>
<tr><td>fake provider_ref</td><td>需纳秒唯一化——P1 Attempt 有 <code>uniqueIndex(channel,provider_ref)</code>,retry 同 method 会撞键(真渠道天然不同 ref)</td></tr>
<tr><td>webhook 无 biz_code</td><td>P1 OrderV2 无 biz_code 列,不改已落地 schema;业务方暂用 biz_ref 映射,P3 补</td></tr>
<tr><td>路由取首个账户</td><td>P2 取"首个 enabled 账户",完整路由策略在 P5</td></tr>
<tr><td>币种走部署默认</td><td>多币种在 P3+</td></tr>
<tr><td>/v1 与 /api/v1 并存</td><td>v2 新端点与 v1 旧端点并存,收口在 pay 定稿后</td></tr>
</tbody>
</table>
<div class="warn"><b>审阅重点</b>:① Provider 接口是否够覆盖后续渠道(Task1);② settle 的归一/幂等/金额核对链路(Task5,资金命脉);③ webhook outbox 幂等键 <code>(out_trade_no,event_type)</code> 是否合理(Task6);④ 上面 5 条取舍是否认可(尤其"路由取首个账户""币种默认"延后)。</div>
<p class="path">相关:<a href="pay-v2-unified-gateway-design.html">pay v2 设计</a> · 真相源 docs/superpowers/plans/2026-07-10-pay-v2-p2-pipeline.md</p>
</div>
</body>
</html>