支付模块交易链路梳理与时序图
本文基于某支付模块的代码实现(项目名已替换为 XXX),详细梳理交易链路,分析核心逻辑,并通过时序图直观展示支付流程。以下内容将帮助读者理解从用户发起支付到支付成功的完整过程,并适用于面试或技术分享场景。
一、核心组件概览
1. 核心实体 (PayInfo)
- 作用 :记录支付信息,对应数据库表
pay_info。 - 关键字段 :
payId:支付流水号(分布式 ID)。userId:用户 ID。orderIds:关联订单 ID 列表(逗号分隔)。bizPayNo:第三方支付交易号。payStatus:支付状态(0-未支付,1-已支付)。payAmount:支付金额(单位:分)。version:乐观锁版本号。
2. 核心控制器
PayController:处理前端支付请求和状态查询。PayNoticeController:处理第三方支付回调。
3. 核心服务 (PayInfoServiceImpl)
- 功能:支付记录创建、状态更新、订单通知。
二、交易链路梳理
以下是基于代码的支付交易完整流程:
1. 用户发起支付请求
- 触发点 :用户在前端点击"支付",调用
POST /pay/order。 - 输入 :
PayInfoDTO(包含orderIds和returnUrl)。 - 后端逻辑 (
PayController.pay()):- 获取用户 ID(通过
AuthUserContext)。 - 调用
PayInfoService.pay()生成支付记录并返回PayInfoBO。 - 设置回调地址(
apiNoticeUrl)和前端跳转地址(returnUrl)。 - 模拟回调(调用
PayNoticeController.submit(),仅用于测试)。 - 返回
payId给前端。
- 获取用户 ID(通过
- 输出 :
ServerResponseEntity封装的payId。
注意 :代码中直接调用 payNoticeController.submit() 是测试逻辑,实际生产环境中应由第三方支付平台发起回调。
2. 支付记录创建 (PayInfoService.pay())
- 输入 :
userId和PayInfoDTO。 - 逻辑 :
- 生成支付 ID :通过
SegmentFeignClient获取分布式 ID(payId)。 - 验证订单 :调用
OrderFeignClient.getOrdersAmountAndIfNoCancel(),获取订单金额并检查订单是否有效。 - 构建支付记录 :
- 设置
payId、userId、payAmount、orderIds等。 - 初始状态为
PayStatus.UNPAY(未支付)。
- 设置
- 保存记录 :插入
PayInfo到数据库。 - 返回支付参数 :构造
PayInfoBO(包含payId、payAmount、body)。
- 生成支付 ID :通过
- 输出 :
PayInfoBO。
3. 前端唤起支付
- 前端逻辑 (未在代码中体现,推测实现):
- 接收后端返回的
payId。 - 使用
payId和其他参数(如金额)调用第三方支付 SDK(如微信wx.requestPayment()或支付宝表单提交)。
- 接收后端返回的
- 用户操作:输入密码完成支付。
4. 第三方支付平台回调
- 触发点 :用户支付成功后,第三方支付平台(如微信、支付宝)向后端回调地址(
/notice/pay/order)发送通知。 - 输入 :
payId(实际应包含第三方交易号和签名,代码中简化)。 - 后端逻辑 (
PayNoticeController.submit()):- 根据
payId查询PayInfo。 - 解析
orderIds为List<Long>。 - 构造
PayInfoResultBO(包含payId、bizPayNo等)。 - 调用
PayInfoService.paySuccess()处理支付成功逻辑。 - 返回空字符串(实际应返回第三方要求的响应,如
SUCCESS)。
- 根据
- 输出 :
ResponseEntity<String>。
5. 支付成功处理 (PayInfoService.paySuccess())
- 输入 :
PayInfoResultBO和orderIds。 - 逻辑 :
- 更新支付状态 :
- 设置
payStatus为PayStatus.PAYED。 - 更新
bizPayNo、callbackContent、callbackTime。 - 执行数据库更新。
- 设置
- 通知订单服务 :
- 通过
RocketMQTemplate发送消息到XXX_ORDER_NOTIFY_TOPIC,携带PayNotifyBO(包含orderIds)。 - 若发送失败,抛出异常(依赖 RocketMQ 重试)。
- 通过
- 更新支付状态 :
- 特性:事务性操作,确保状态更新和消息发送一致。
6. 前端查询支付状态(可选)
- 触发点 :前端调用
GET /pay/isPay/{orderIds}检查支付结果。 - 逻辑 (
PayController.isPay()):- 调用
PayInfoService.isPay()查询状态。 - 返回布尔值(
true表示已支付)。
- 调用
- 输出 :
ResponseEntity<Boolean>。
三、时序图
以下是支付交易的时序图,使用 Mermaid 语法生成:
sequenceDiagram
participant U as 用户
participant F as 前端
participant P as PayController
participant S as PayInfoService
participant O as OrderFeignClient
participant L as SegmentFeignClient
participant T as 第三方支付平台
participant N as PayNoticeController
participant R as RocketMQ
U->>F: 点击支付
F->>P: POST /pay/order (orderIds)
P->>S: pay(userId, payParam)
S->>L: getSegmentId()
L-->>S: payId
S->>O: getOrdersAmountAndIfNoCancel(orderIds)
O-->>S: payAmount
S->>S: 保存 PayInfo
S-->>P: PayInfoBO (payId)
P-->>F: payId
F->>T: 唤起支付 (payId, payAmount)
U->>T: 输入密码支付
T-->>N: POST /notice/pay/order (payId)
N->>S: getByPayId(payId)
S-->>N: PayInfo
N->>S: paySuccess(payInfoResult, orderIds)
S->>S: 更新 PayInfo (payStatus=1)
S->>R: 发送消息 (ORDER_NOTIFY_TOPIC)
R-->>S: 发送成功
N-->>T: SUCCESS
F->>P: GET /pay/isPay/{orderIds}
P->>S: isPay(orderIds, userId)
S-->>P: isPay (1/0)
P-->>F: true/false
说明:
- 图中展示了从用户支付到状态更新的完整链路。
PayNoticeController.submit()被视为第三方回调的入口。- RocketMQ 确保支付成功后订单状态同步。
四、链路特点
- 分布式 ID :通过
SegmentFeignClient生成payId,确保唯一性。 - 状态机 :
payStatus驱动支付流程(未支付 -> 已支付)。 - 最终一致性:RocketMQ 保证支付状态与订单状态同步。
- 安全性:事务控制和消息发送的幂等性设计。
- 可追溯性 :
payId贯穿整个链路。
五、注意事项与优化建议
- 回调安全性 :
- 当前代码未验证第三方签名,生产环境需添加签名校验(如微信的 MD5 或支付宝的 RSA2)。
- 测试逻辑 :
PayController中直接调用submit()是测试代码,实际应移除,由第三方触发。
- 异常处理 :
- RocketMQ 发送失败时抛异常合理,但可增加重试机制或异步补偿。
- 前端交互 :
- 返回的
payId需配合第三方支付参数(如prepay_id),否则前端无法唤起支付。
- 返回的
六、总结
该支付模块设计清晰,链路完整,从前端发起支付到第三方回调更新状态,再到通知下游服务,体现了微服务架构的典型特点。通过时序图可以直观理解各组件的交互逻辑,适合用于技术分享或面试准备。优化方向包括增强安全性、完善异常处理和规范化第三方支付集成。