支付页面需防误操作、保状态、抗重放、可访问:提交时禁用整个表单并设aria-disabled;服务端必须幂等校验;确认页须reset表单、移除监听器、replacestate;成功页订单号应入url path并服务端渲染,缓存头设为no-store。

支付页面不是“做个表单加个按钮”就完事的,关键在防误操作、保状态、抗重放、可访问。前端渲染的支付页尤其容易漏掉幂等校验和表单清理,导致用户刷新后重复提交或看到旧数据。
form 提交前必须禁用整个表单,不只是 submit 按钮
只给 <button type="submit"></button> 加 disabled 不够——键盘用户仍可用 Tab 切到其他输入框并回车;屏幕阅读器可能忽略按钮禁用状态,继续播报其他控件。真正有效的做法是:
- 点击提交时,对整个
<form></form>调用form.setAttribute('aria-disabled', 'true') - 同时给所有可交互子元素(
<input>、<select></select>、<button></button>)批量设disabled属性 - 服务端收到请求后,必须校验
Idempotency-Key或订单号去重,不能依赖前端“只点一次”
支付确认页不能保留原表单 DOM
SPA 场景下,路由跳转到确认页时若只是 display: none 原支付表单,后退会直接复原脏数据(比如用户改过金额但没提交)。正确做法是:
- 进入确认页时,调用
form.reset()清空所有字段值 - 移除所有事件监听器(
form.removeEventListener('submit', handler)) - 用
history.replaceState()替换当前 history 条目,避免用户后退回到未清空的表单页 - 确认页 URL 必须是独立路径(如
/payment/confirm/abc123),不能是哈希或 query 参数驱动
支付成功页要显式声明订单上下文,不能靠 JS 临时拼接
很多实现把订单号存在 JS 变量里再插入 DOM,一旦页面意外刷新就丢失。用户需要能复制、分享、截图的确定性信息:
- 订单号必须作为 URL path 参数传入(如
/payment/success/ord_789xyz),服务端渲染时写死进 HTML - 页面内所有订单引用(如“您的订单号:
ord_789xyz”)都从 URL 或<meta name="order-id" content="ord_789xyz">读取,不依赖 JS 状态 - 避免用
document.title = '支付成功 - ' + orderId动态改标题,搜索引擎和分享卡片可能抓不到
最常被忽略的是:确认页和成功页的 HTTP 缓存头必须设为 no-store,否则代理或浏览器可能缓存带敏感信息的响应,下次打开直接显示他人订单。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











