支付系统业务

目录

一、支付系统中,前端到底在做什么

前端不是收钱的,而是"控制交易流程的人"

在支付系统里,前端承担的是:

  • 支付流程编排者
  • 支付状态机的执行层
  • 用户行为的约束器
  • 风险与异常的第一防线
  • 用户体验与安全之间的平衡者

这也是为什么:支付系统是前端最难、最不能出错的业务之一

二、支付系统的完整链路(前端视角)

一个标准的企业级支付链路:

typescript 复制代码
确认订单
 ↓
创建支付单
 ↓
选择支付方式
 ↓
发起支付
 ↓
第三方支付
 ↓
支付结果确认
 ↓
订单完成 / 异常处理

三、支付系统的核心业务对象(前端必须理解)

1、订单 ≠ 支付

这是支付系统最重要的认知之一。

概念 含义
订单(Order) 用户买了什么
支付单(PayOrder) 一次支付尝试
交易(Trade) 钱的流转

一个订单可以:

  • 多次支付
  • 更换支付方式
  • 失败后重试

2、前端需要理解的关键字段

typescript 复制代码
interface PayOrder {
  payOrderId: string;
  orderId: string;
  amount: number;        // 单位:分
  status: 'INIT' | 'PAYING' | 'SUCCESS' | 'FAIL';
  expireTime: number;
  channel: 'ALIPAY' | 'WECHAT' | 'BANK';
}

前端展示的所有支付信息,必须来自 payOrder。

四、支付系统的核心:状态机(前端灵魂)

1、为什么支付前端一定要用「状态机」

原因只有一个:支付不是"事件流",而是"状态演进"

不用状态机写支付,迟早出事故

支付系统的天然特性

  • 不可逆
  • 强一致
  • 多异常
  • 多分支
  • 可中断、可恢复

天然适合状态机

2、标准支付状态机(前端必须实现)

前端与状态的关系:

状态 前端行为
INIT 可点击
PAY_CREATING 禁止操作
PAYING loading / 跳转
SUCCESS 成功页
FAIL 错误提示
CAN_RETRY 允许重试

UI = 状态的映射,而不是事件的堆叠

五、前端支付页的模块级拆解

1、金额展示(极其敏感)

核心原则:前端永远不能参与金额计算

正确做法:

  • 金额完全来自后端
  • 前端只展示
  • 金额单位统一为"分"
typescript 复制代码
displayAmount = (amount / 100).toFixed(2);

防攻击点:

  • 禁止前端传金额
  • 禁止浮点数
  • 校验金额变更(价格波动)

2、支付方式选择(复杂度极高)

为什么复杂?

  • 多支付渠道
  • 风控动态可用
  • 限额 / 余额
  • 渠道互斥

前端建模方式:

typescript 复制代码
interface PayChannel {
  code: string;
  name: string;
  enabled: boolean;
  disabledReason?: string;
}

前端职责:

  • 渲染
  • 互斥控制
  • 引导推荐(后端策略)

3、提交支付(最危险操作)

必须防止的事情:

  • 重复提交
  • 多次创建支付单
  • 状态错乱

前端三重防护:

  • 按钮锁
  • 状态校验
  • 幂等 key
typescript 复制代码
if (payStatus !== 'INIT') return;

六、第三方支付对接(前端重点)

1、常见支付方式

场景 前端行为
H5 跳转 URL
JS SDK 调起 JSAPI
App Bridge 调用
小程序 平台 API

示例(H5):

typescript 复制代码
window.location.href = payUrl;

⚠️ 注意:

  • 跳转前必须保存上下文
  • 跳转回来不能信任参数

2、支付完成 ≠ 支付成功

这是 支付系统的铁律。

不可信的返回来源:

  • URL 参数
  • SDK 回调
  • 第三方页面提示

唯一可信方式:

  • 向后端查询支付状态

七、支付结果确认(前端最难)

1、轮询机制(最常用)

typescript 复制代码
const timer = setInterval(async () => {
  const status = await fetchPayStatus();
  if (status === 'SUCCESS') {
    clearInterval(timer);
    gotoSuccess();
  }
}, 2000);

轮询优化:

  • 最大次数
  • 指数退避
  • 页面隐藏暂停

2、异常与中断处理

常见异常:

场景 前端处理
页面关闭 可恢复
网络断开 重试
超时 引导查看订单
状态未知 风控兜底

支付系统必须可恢复。

八、支付安全:前端不是"无关角色"

1、前端安全防护点

  • XSS(支付页零容忍)
  • CSP
  • 防 iframe 嵌套
  • 防调试
  • 防篡改

2、风控协作

前端参与:

  • 设备指纹
  • 行为轨迹
  • 风险提示
  • 二次确认

九、企业级支付系统的前端工程能力

1、架构能力

  • 状态机建模
  • 多端统一抽象
  • 异常兜底设计

2、工程能力

  • 高可用
  • 可恢复
  • 可追踪
  • 可监控

十、为什么支付系统是前端"天花板级业务"

维度 难度
安全 极高
一致性 极高
复杂度 极高
影响面 极大
出错成本 极高

能把支付系统前端做好,基本可以覆盖前端 80% 的高阶能力

十一、支付系统的异常与兜底

1、核心结论(极重要)

支付系统不是"成功路径设计",而是"失败路径设计"

  • 成功路径:1 条
  • 异常路径:几十条

前端的核心价值:把"不可控异常"变成"可控流程"

2、支付异常的「总分类模型」(一眼看清全局)

从前端角度,支付异常可以拆成 5 大类:

  • 用户行为异常
  • 前端环境异常
  • 网络异常
  • 第三方支付异常
  • 系统状态异常(最难)

下面我们 逐类深拆 + 对应兜底策略。

3、用户行为异常(出现频率最高)

(1)、重复点击 / 连续提交

真实场景:

  • 用户狂点"立即支付"
  • 双击 / 三击
  • 手滑刷新

风险:

  • 多次创建支付单
  • 状态错乱
  • 重复扣款风险

前端兜底策略(必须多层):

  • 第一层:按钮锁(UI级)
typescript 复制代码
if (isPaying) return;
isPaying = true;
  • 第二层:状态锁(业务级)
typescript 复制代码
if (payStatus !== 'INIT') return;
  • 第三层:幂等 Key(接口级)
typescript 复制代码
POST /createPayOrder
Headers:
  Idempotent-Key: uuid

❗️ 结论:前端永远不能只靠按钮 disabled

(2)、用户中途退出 / 关闭页面

场景:

  • 跳转支付宝后关闭浏览器
  • 支付中刷新页面
  • App 切后台

核心原则:

  • 支付流程必须"可恢复"

前端兜底方案:
①、本地保存支付上下文

typescript 复制代码
localStorage.setItem('pendingPay', JSON.stringify({
  orderId,
  payOrderId,
  timestamp
}));

②、页面重新进入时检测

typescript 复制代码
if (hasPendingPay()) {
  queryPayStatus();
}

③、引导策略

  • 已支付 → 成功页
  • 未支付 → 继续支付 / 更换方式

4、前端环境异常(最容易被忽略)

(1)、页面崩溃 / JS 异常

场景:

  • JS runtime error
  • 白屏
  • 资源加载失败

前端兜底策略:
①、支付页必须是"最小依赖页面"

  • 禁止复杂组件
  • 禁止不必要动画
  • 禁止重型第三方库

②、全局异常捕获

typescript 复制代码
window.onerror = (err) => {
  reportPayError(err);
  showFallbackUI();
};

③、降级 UI

  • 简化支付页
  • 引导跳转订单中心

(2)、浏览器 / WebView 限制

场景:

  • iframe 被禁
  • WebView 不支持某 API
  • Cookie 丢失

兜底策略:

  • 能跳转就跳转(location.href)
  • 不依赖 localStorage 单点
  • 重要信息后端可查

5、网络异常(最典型)

(1)、创建支付单超时

场景:

  • 请求已到后端
  • 前端超时未收到响应

‼️ 这是高危场景

❌ 错误做法:

  • 直接提示失败
  • 允许再次点击

✅ 正确兜底:

typescript 复制代码
try {
  await createPayOrder();
} catch {
  queryPayOrderByOrderId();
}

永远先查状态,再决定 UI

(2)、支付过程中网络断开

场景:

  • 已跳第三方
  • 回跳失败

兜底策略:

  • 回到业务系统后:
    • 不显示"失败"
    • 显示"支付结果确认中"
    • 自动轮询

6、第三方支付异常(前端必须克制)

(1)、第三方返回"成功",但不可信

铁律(再强调一次):

  • 任何前端拿到的支付成功都不可信

正确兜底流程:

typescript 复制代码
第三方回跳
 ↓
前端展示"处理中"
 ↓
查询后端支付状态
 ↓
SUCCESS / FAIL

(2)、第三方无回调 / 卡死

场景:

  • SDK 没返回
  • 页面卡在 loading

兜底设计:

  • 超时兜底(如 30s)
  • 显示:"支付可能已完成,请前往订单中心确认"

7、系统状态异常(最难、最值钱)

(1)、支付状态 = UNKNOWN(真实存在)

场景:

  • 银行处理中
  • 风控审核中
  • 支付网关延迟

前端策略(非常重要):

❌ 错误

  • 直接失败
  • 直接成功

✅ 正确

  • 状态透明化
typescript 复制代码
支付处理中
资金到账可能有延迟
请稍后在订单中查看
  • 自动轮询
  • 手动刷新入口

(2)、成功 / 失败状态"延迟到达"

场景:

  • 用户看到失败
  • 实际已成功

前端兜底:

  • 成功状态优先级最高
  • 成功可覆盖失败
  • 失败不可覆盖成功

8、支付异常兜底「设计原则总结」(⭐️⭐️⭐️⭐️⭐️)

(1)、前端兜底五大铁律

  • 先查状态,再做判断
  • 成功必须来自后端
  • 失败可以被推翻
  • 流程必须可恢复
  • 永远给用户退路

(2)、前端兜底 UI 标准文案(实战)

场景 推荐文案
不确定 支付结果确认中
网络异常 网络异常,未确认支付结果
超时 请前往订单中心查看
可重试 可重新发起支付
风控 为保障安全,请稍后再试

9、你如果在面试中这样讲(直接高分)(⭐️)

面试官:

支付失败你们怎么处理?

高级前端答案(模板):

我们不直接认为失败,而是进入一个"支付结果不确定态"。

前端会优先向后端查询支付单状态,如果是 UNKNOWN,会进入轮询;

同时整个支付流程是可恢复的,用户再次进入订单页时可以继续确认状态。

成功态只能来自服务端确认,失败态允许被成功覆盖。

基本稳过。

相关推荐
龙哥·三年风水1 年前
微信支付开发-前端api实现
微信·php·支付系统
阿里巴啦1 年前
“设计模式双剑合璧:工厂模式与策略模式在支付系统中的完美结合”
设计模式·策略模式·工厂模式·支付系统·设计模式的使用
隐墨_SC2 年前
图解支付-金融级密钥管理系统:构建支付系统的安全基石
安全·金融·支付系统·密钥管理系统设计与实现·密钥管理
隐墨_SC2 年前
图解报文网关:一种低代码报文网关的创新设计
低代码·渠道网关·报文网关·渠道接入·支付系统