fix(pay): 测试页对齐官网暖色风格 + 修地址逐字换行布局
- 布局 bug:收款地址原挤在 flex 行里(与复制键争宽)→ word-break 逐字换行。
改为独立整行 .addr 块(全宽自然换行),复制键下移全宽。
- 风格:改用官网 light 暖色 token(sand/clay,主色 #B96A3D、sage 绿
#5B8C5A、JetBrains Mono),内联对齐 design/prototype/tokens.css。
- 页脚补一句「测什么」:收款订单闭环 POST /order + GET /order/{id} 轮询到 paid。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,11 @@ import "net/http"
|
||||
// money-critical Phase E loop (create order → pay exact amount → watch paid),
|
||||
// NOT the production checkout (that lives on the storefront). It only creates
|
||||
// orders and polls status — no secrets, no keys.
|
||||
//
|
||||
// Styling mirrors the marketing site's warm sand/clay palette (design tokens in
|
||||
// design/prototype/tokens.css, light theme default, brand = clay-500 #B96A3D).
|
||||
// Values are inlined here because pangolin-pay is a separate origin and cannot
|
||||
// @import the site's stylesheet; keep them in sync if the brand palette changes.
|
||||
func (h *Handler) testPage(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
@@ -20,55 +25,78 @@ const testPageHTML = `<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="theme-color" content="#B96A3D">
|
||||
<title>pangolin-pay · 收款测试</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0b0f14; --card:#131a22; --card2:#0f151c; --line:#223040;
|
||||
--fg:#e6edf3; --muted:#8899a6; --accent:#3fb950; --accent2:#58a6ff;
|
||||
--warn:#d29922; --err:#f85149; --radius:14px;
|
||||
--bg:#FAF8F4; --surface:#FFFFFF; --surface-2:#F2EEE7;
|
||||
--fg1:#1F1C18; --fg2:#6B6253; --fg3:#8C8270;
|
||||
--accent:#B96A3D; --accent-hover:#9E5630; --accent-subtle:#FAF3ED; --accent-border:#E6C7AC;
|
||||
--border:#E6DFD3; --border-strong:#D2C8B8;
|
||||
--success:#5B8C5A; --success-subtle:#E9F0E6;
|
||||
--warning:#D69A3C; --warning-subtle:#F8EED6;
|
||||
--danger:#C0533B; --danger-subtle:#F6E1DA;
|
||||
--radius-lg:14px; --radius-md:10px; --radius-sm:8px;
|
||||
--font-display:'Sora','Noto Sans SC',system-ui,sans-serif;
|
||||
--font-sans:'Manrope','Noto Sans SC',system-ui,sans-serif;
|
||||
--font-mono:'JetBrains Mono',ui-monospace,'SFMono-Regular',Menlo,monospace;
|
||||
--shadow-md:0 4px 14px rgba(45,30,20,.08);
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:var(--bg);color:var(--fg);
|
||||
font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;
|
||||
display:flex;justify-content:center;padding:32px 16px;}
|
||||
.wrap{width:100%;max-width:520px}
|
||||
h1{font-size:19px;margin:0 0 4px;font-weight:650}
|
||||
.sub{color:var(--muted);font-size:13px;margin:0 0 20px}
|
||||
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:20px;margin-bottom:16px}
|
||||
label{display:block;font-size:12px;color:var(--muted);margin:12px 0 5px}
|
||||
input{width:100%;background:var(--card2);border:1px solid var(--line);border-radius:10px;
|
||||
color:var(--fg);padding:11px 12px;font-size:15px;outline:none;font-family:inherit}
|
||||
input:focus{border-color:var(--accent2)}
|
||||
button{width:100%;margin-top:18px;background:var(--accent);color:#04130a;border:0;border-radius:10px;
|
||||
padding:13px;font-size:15px;font-weight:650;cursor:pointer}
|
||||
body{margin:0;background:var(--bg);color:var(--fg1);
|
||||
font-family:var(--font-sans);font-size:15px;line-height:1.55;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
display:flex;justify-content:center;padding:40px 16px;}
|
||||
.wrap{width:100%;max-width:480px}
|
||||
h1{font-family:var(--font-display);font-size:22px;margin:0 0 5px;font-weight:700;letter-spacing:-.02em}
|
||||
h1 .dot{color:var(--accent)}
|
||||
.sub{color:var(--fg2);font-size:13px;margin:0 0 24px}
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);
|
||||
padding:22px;margin-bottom:16px;box-shadow:var(--shadow-md)}
|
||||
label{display:block;font-size:12px;color:var(--fg2);margin:14px 0 6px;font-weight:500}
|
||||
label:first-child{margin-top:0}
|
||||
input{width:100%;background:var(--bg);border:1px solid var(--border-strong);border-radius:var(--radius-md);
|
||||
color:var(--fg1);padding:11px 13px;font-size:15px;outline:none;font-family:inherit;transition:border-color .15s}
|
||||
input:focus{border-color:var(--accent)}
|
||||
button{width:100%;margin-top:20px;background:var(--accent);color:#fff;border:0;border-radius:var(--radius-md);
|
||||
padding:13px;font-size:15px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}
|
||||
button:hover:not(:disabled){background:var(--accent-hover)}
|
||||
button:disabled{opacity:.5;cursor:not-allowed}
|
||||
button.ghost{background:transparent;border:1px solid var(--line);color:var(--fg);font-weight:500;margin-top:10px}
|
||||
.row{display:flex;align-items:center;gap:8px}
|
||||
.kv{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;
|
||||
padding:11px 0;border-bottom:1px solid var(--line)}
|
||||
.kv:last-child{border-bottom:0}
|
||||
.kv .k{color:var(--muted);font-size:13px;white-space:nowrap}
|
||||
.kv .v{text-align:right;word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}
|
||||
.amt{font-size:26px;font-weight:700;color:var(--accent);font-family:ui-monospace,monospace}
|
||||
.amt small{font-size:14px;color:var(--muted);font-weight:400;margin-left:4px}
|
||||
.copy{background:var(--card2);border:1px solid var(--line);color:var(--accent2);
|
||||
border-radius:7px;padding:3px 9px;font-size:12px;cursor:pointer;flex:none}
|
||||
.copy:active{background:var(--line)}
|
||||
.badge{display:inline-block;padding:3px 11px;border-radius:20px;font-size:12px;font-weight:600}
|
||||
.b-pending{background:#3a2d0a;color:var(--warn)}
|
||||
.b-paid{background:#0f2a17;color:var(--accent)}
|
||||
.b-expired{background:#2a1215;color:var(--err)}
|
||||
.note{font-size:12px;color:var(--muted);margin-top:14px;line-height:1.6}
|
||||
.err{color:var(--err);font-size:13px;margin-top:12px;min-height:18px}
|
||||
button.ghost{background:transparent;border:1px solid var(--border-strong);color:var(--fg1);font-weight:500;margin-top:12px}
|
||||
button.ghost:hover:not(:disabled){background:var(--surface-2)}
|
||||
button.copy{margin-top:10px;background:var(--accent-subtle);border:1px solid var(--accent-border);
|
||||
color:var(--accent-hover);font-weight:600;font-size:13px;padding:9px}
|
||||
button.copy:hover:not(:disabled){background:#F5E7D8}
|
||||
.kv{display:flex;justify-content:space-between;align-items:center;gap:12px;
|
||||
padding:12px 0;border-bottom:1px solid var(--border)}
|
||||
.kv .k{color:var(--fg2);font-size:13px;white-space:nowrap;flex:none}
|
||||
.kv .v{text-align:right;font-family:var(--font-mono);font-size:13px;word-break:break-all}
|
||||
.amtbox{text-align:center;padding:20px 0 6px}
|
||||
.amtbox .cap{color:var(--fg2);font-size:12px;margin-bottom:8px}
|
||||
.amtbox .cap b{color:var(--warning)}
|
||||
.amt{font-family:var(--font-mono);font-size:30px;font-weight:700;color:var(--success);letter-spacing:-.01em}
|
||||
.amt small{font-size:14px;color:var(--fg3);font-weight:400;margin-left:5px;font-family:var(--font-sans)}
|
||||
.addrblock{margin:6px 0 2px}
|
||||
.addrblock .k{color:var(--fg2);font-size:12px;margin-bottom:6px}
|
||||
.addr{font-family:var(--font-mono);font-size:14px;background:var(--surface-2);border:1px solid var(--border);
|
||||
border-radius:var(--radius-md);padding:12px 14px;word-break:break-all;line-height:1.5;color:var(--fg1)}
|
||||
.badge{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:600;font-family:var(--font-mono)}
|
||||
.b-pending{background:var(--warning-subtle);color:#9A6B1F}
|
||||
.b-paid{background:var(--success-subtle);color:#3E6B3D}
|
||||
.b-expired{background:var(--danger-subtle);color:#8F3826}
|
||||
.note{font-size:12px;color:var(--fg2);margin-top:16px;line-height:1.65}
|
||||
.note b{color:var(--fg1)}
|
||||
.err{color:var(--danger);font-size:13px;margin-top:12px;min-height:18px}
|
||||
.hidden{display:none}
|
||||
.warnbox{background:#3a2d0a33;border:1px solid #d2992244;border-radius:10px;
|
||||
padding:11px 13px;font-size:12.5px;color:var(--warn);margin-top:14px;line-height:1.6}
|
||||
code{background:var(--card2);padding:1px 5px;border-radius:5px;font-size:12px}
|
||||
.warnbox{background:var(--warning-subtle);border:1px solid #EBD7A6;border-radius:var(--radius-md);
|
||||
padding:12px 14px;font-size:12.5px;color:#7A5514;margin-top:16px;line-height:1.65}
|
||||
.warnbox b{color:#5E4110}
|
||||
code{background:var(--surface-2);border:1px solid var(--border);padding:1px 5px;border-radius:5px;font-size:12px;font-family:var(--font-mono)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>pangolin-pay · 收款测试</h1>
|
||||
<h1>pangolin-pay <span class="dot">·</span> 收款测试</h1>
|
||||
<p class="sub">USDT-TRC20 · 单地址 + 唯一金额 · 手动测试页(非生产收银台)</p>
|
||||
|
||||
<div class="card" id="form-card">
|
||||
@@ -91,19 +119,20 @@ const testPageHTML = `<!doctype html>
|
||||
<span class="k">状态</span>
|
||||
<span class="v"><span class="badge" id="o-status"></span></span>
|
||||
</div>
|
||||
<div style="text-align:center;padding:14px 0 6px">
|
||||
<div style="color:var(--muted);font-size:12px;margin-bottom:4px">请支付<b style="color:var(--warn)">精确金额</b>(多付少付都不匹配)</div>
|
||||
|
||||
<div class="amtbox">
|
||||
<div class="cap">请支付<b>精确金额</b>(多付少付都不匹配)</div>
|
||||
<div class="amt" id="o-amount"></div>
|
||||
<button class="copy" id="copy-amt" style="margin-top:8px">复制金额</button>
|
||||
</div>
|
||||
<div class="kv">
|
||||
<span class="k">收款地址</span>
|
||||
<span class="row" style="justify-content:flex-end">
|
||||
<span class="v" id="o-addr"></span>
|
||||
<button class="copy" id="copy-addr">复制</button>
|
||||
</span>
|
||||
<button class="copy" id="copy-amt">复制金额</button>
|
||||
|
||||
<div class="addrblock">
|
||||
<div class="k">收款地址</div>
|
||||
<div class="addr" id="o-addr"></div>
|
||||
</div>
|
||||
<div class="kv">
|
||||
<button class="copy" id="copy-addr">复制地址</button>
|
||||
|
||||
<div class="kv" style="margin-top:6px">
|
||||
<span class="k">TxID</span>
|
||||
<span class="v" id="o-tx">—</span>
|
||||
</div>
|
||||
@@ -111,18 +140,20 @@ const testPageHTML = `<!doctype html>
|
||||
<span class="k">过期倒计时</span>
|
||||
<span class="v" id="o-ttl"></span>
|
||||
</div>
|
||||
|
||||
<div class="warnbox">
|
||||
发往此地址、且金额<b>正好等于</b>上面数字的 USDT-TRC20 转账,会在 watcher 下一轮轮询后自动标记
|
||||
<code>paid</code>。金额不符 → 进 orphan 需人工对账。<br>
|
||||
⚠️ 需 <code>TRONGRID_API_KEY</code> 已配置,否则 watcher 被限流可能侦测很慢。
|
||||
⚠️ 需 <code>TRONGRID_API_KEY</code> 已配置,否则 watcher 被限流、侦测很慢。
|
||||
</div>
|
||||
<button class="ghost" id="reset">新建另一单</button>
|
||||
<div class="err" id="order-err"></div>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
地址模型:所有订单收到<b>同一地址</b>,靠<b>唯一金额</b>(基准价 + 微尾数 ≤0.01U)区分。
|
||||
本页仅调 <code>POST /order</code> 与 <code>GET /order/{id}</code>,无私钥、无密钥。
|
||||
测什么:<b>收款订单闭环</b> —— <code>POST /order</code> 建单拿地址+唯一金额、<code>GET /order/{id}</code>
|
||||
轮询到 <code>paid</code>。地址模型:所有订单收到<b>同一地址</b>,靠<b>唯一金额</b>(基准价 + 微尾数
|
||||
≤0.01U)区分。本页无私钥、无密钥。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user