Appearance
结账页面前端开发规范
本文档说明支付 checkout 页面(Payment Intent / Invoice)的前端状态机设计原则,供维护和二次开发参考。
支付成功的判断依据
前端只负责判断"交易是否已上链",区块确认由后端独立完成。
- 只要后端通过 Alchemy webhook 捕获到
PaymentReceived事件,即视为支付成功。 - 6 次区块确认是后端
listener-service的内部逻辑,前端无需等待、展示进度,也无需轮询。 - 前端收到
listener_pending或onchain_confirmed阶段时,应直接显示"支付成功",不应向用户传达"等待中"或"确认中"的不确定感。
CheckoutPhase 状态机
| Phase | 触发时机 | 前端展示 |
|---|---|---|
loading | 初始加载 intent | 加载中 |
ready | intent 加载完成,status = requires_payment | 等待用户操作 |
already_paid | intent 加载时 status = confirmed | processing | 订单已支付,禁止操作 |
unavailable | intent 加载失败或不可用 | 错误提示,禁止操作 |
switching_network | 切换链网络中 | 准备中 |
checking_allowance | 查询 ERC20 授权额度 | 准备中 |
approving | 等待用户在钱包确认授权 | 等待用户操作 |
submitting_payment | 等待用户在钱包确认支付 | 等待用户操作 |
signing_gasless | 等待用户签名(免 Gas 路径) | 等待用户操作 |
submitting_relay | 中继服务提交交易中 | 进行中 |
onchain_confirmed | wagmi 本地确认交易落块 | 支付成功 |
listener_pending | 后端收到链上事件,区块确认进行中 | 支付成功 |
listener_confirmed | 后端完成区块确认 | 支付成功 |
redirecting | 支付成功,正在倒计时跳转商家页 | 支付成功 |
failed | 任意环节出错或用户拒绝 | 失败,可重试 |
前端"支付成功"的触发点
onchain_confirmed 起即为支付成功,对应逻辑链路:
用户钱包确认 → wagmi 等到 1 次本地确认 → phase = onchain_confirmed
→ 后端 Alchemy webhook 收到事件 → phase = listener_pending
→ 后端完成 6 次区块确认 → phase = listener_confirmed三个 phase 在前端的表现完全一致:绿色按钮、"支付成功"文案。
已支付订单的防护
加载 intent 时,若后端返回 status = "confirmed" 或 "processing",前端直接设为 already_paid:
- 按钮禁用,显示绿色"订单已支付"
- 不展示连接钱包 / 更换钱包等操作
- 禁止任何支付流程入口
这是前端的幂等保护。链上层面的防重放由合约 processedOrder mapping 保证(同一 (merchant, orderId) 只能支付一次,重复调用会 revert)。
Gasless 支付路径
当商家启用 Gas 赞助时,支付流程改为签名路径:
用户签名 EIP-712 → 前端发送签名到后端 relay 接口
→ 后端 RelayService 调用 payWithSig → 交易上链前端判断成功的逻辑与普通路径相同,onchain_confirmed 起即为支付成功。
不要在前端做的事
- 不要轮询区块确认次数:6 次确认是后端内部逻辑,不要在前端显示"3/6 确认"之类进度。
- 不要在前端等待
listener_confirmed才显示成功:onchain_confirmed已经足够。 - 不要在前端重新实现已支付判断:直接信任后端
intent.status字段。