支付模块交易链路梳理与时序图
本文基于某支付模块的代码实现(项目名已替换为 XXX),详细梳理交易链路,分析核心逻辑,并通过时序图直观展示支付流程。以下内容将帮助读者理解从用户发起支付到支付成功的完整过程,并适用于面试或技术分享场景。
一、核心组件概览
1. 核心实体 (PayInfo)
- 作用:记录支付信息,对应数据库表
pay_info。 - 关键字段:
payId:支付流水号( 分布式 ID(Long 类型)。userId:用户 ID(Long 类型)。orderIds:关联订单 ID 列表(逗号分隔的字符串,如 "1,2,3")。bizPayNo:第三方支付交易号(String 类型)。payStatus:支付状态(Integer 类型,0-未支付,1-已支付)。payAmount:支付金额(Long 类型,单位:分)。version:乐观锁版本号(Integer 类型)。
2. 核心控制器
PayController:处理前端支付请求和状态查询。PayNoticeController:处理第三方支付回调。
3. 核心服务 (PayInfoServiceImpl)
- 功能:支付记录创建、状态更新、订单通知。
二、交易链路梳理
以下是基于代码的支付交易完整流程,每一步都明确说明传递的参数。
1. 用户发起支付请求
- 触发点:用户在前端点击“支付”,调用
POST /pay/order。 - 输入参数:
PayInfoDTO(JSON 对象):orderIds:订单 ID 列表(String,如 "1,2,3")。returnUrl:支付成功后前端跳转地址(String,如 "example.com/success")。
- 后端逻辑(
PayController.pay()):- 获取用户 ID:
- 调用
AuthUserContext.getUserId(),返回userId(Long)。
- 调用
- 调用支付服务:
- 参数:
userId(Long)、payParam(PayInfoDTO)。 - 返回:
PayInfoBO(包含payId、payAmount、body等)。
- 参数:
- 设置回调地址和跳转地址:
apiNoticeUrl:后端回调地址(String,如 "api.example.com/notice/pay/…returnUrl:从PayInfoDTO获取。
- 模拟回调(仅测试用):
- 调用
PayNoticeController.submit(payId),参数为payId(Long)。
- 调用
- 返回结果:
- 返回
ServerResponseEntity<PayId>,其中payId是 Long 类型。
- 返回
- 获取用户 ID:
- 输出:
ServerResponseEntity封装的payId(Long)。
注意:apiNoticeUrl 是后端预配置的固定地址,在生成支付记录时会传递给第三方支付平台(见第 3 步),而非仅靠前端传递 payId。
2. 支付记录创建 (PayInfoService.pay())
- 输入参数:
userId:用户 ID(Long)。payParam:PayInfoDTO(包含orderIds和returnUrl)。
- 逻辑:
- 生成支付 ID:
- 调用
SegmentFeignClient.getSegmentId(),返回payId(Long)。
- 调用
- 验证订单:
- 调用
OrderFeignClient.getOrdersAmountAndIfNoCancel(orderIds)。 - 参数:
orderIds(String,如 "1,2,3")。 - 返回:
OrderAmountBO(包含payAmount(Long) 和订单有效性)。
- 调用
- 构建支付记录:
- 创建
PayInfo对象:payId:从步骤 1 获取(Long)。userId:从输入获取(Long)。payAmount:从OrderAmountBO获取(Long)。orderIds:从payParam获取(String)。payStatus:初始化为PayStatus.UNPAY(0)。
- 创建
- 保存记录:
- 调用数据库插入操作,保存
PayInfo。
- 调用数据库插入操作,保存
- 返回支付参数:
- 构造
PayInfoBO:payId(Long)。payAmount(Long)。body(String,支付描述,如 "XXX 商品支付")。
- 构造
- 生成支付 ID:
- 输出:
PayInfoBO。
3. 前端唤起支付
- 前端逻辑(推测实现,未在代码中体现):
- 接收后端返回的
payId(Long)。 - 调用第三方支付 SDK(如微信
wx.requestPayment()或支付宝表单提交)。 - 传递参数(以微信为例):
appId:应用 ID(String,预配置)。timeStamp:时间戳(String,前端生成)。nonceStr:随机字符串(String,前端生成)。package:预支付订单信息(String,如 "prepay_id=xxx",需后端额外生成)。signType:签名类型(String,如 "MD5")。paySign:签名(String,前端根据规则生成)。
- 关键点:
payId本身不足以唤起支付,实际需要后端调用第三方支付接口生成prepay_id(未在代码中体现,需优化)。
- 接收后端返回的
- 用户操作:输入密码完成支付。
解答疑问:前端仅传递 payId 不足以让第三方支付平台知道回调地址。实际生产环境中,后端在生成支付记录后,会调用第三方支付接口(如微信统一下单 API),传入 payId、payAmount 和 notify_url(即 apiNoticeUrl)。第三方支付平台会记录该 notify_url,并在支付成功后回调此地址。
4. 第三方支付平台回调
- 触发点:用户支付成功后,第三方支付平台向
/notice/pay/order发送 POST 请求。 - 输入参数(以微信为例,代码中简化):
out_trade_no:商户订单号,即payId(Long)。transaction_id:第三方交易号(String)。sign:签名(String)。
- 后端逻辑(
PayNoticeController.submit()):- 查询支付记录:
- 调用
PayInfoService.getByPayId(payId)。 - 参数:
payId(Long)。 - 返回:
PayInfo。
- 调用
- 解析订单 ID:
- 从
PayInfo.orderIds(String)解析为List<Long>。
- 从
- 构造支付结果:
- 创建
PayInfoResultBO:payId(Long)。bizPayNo(从回调参数transaction_id获取,String)。
- 创建
- 处理支付成功:
- 调用
PayInfoService.paySuccess(payInfoResult, orderIds)。 - 参数:
payInfoResult(PayInfoResultBO)、orderIds(List<Long>)。
- 调用
- 返回响应:
- 返回空字符串(实际应返回 "SUCCESS",符合第三方要求)。
- 查询支付记录:
- 输出:
ResponseEntity<String>。
5. 支付成功处理 (PayInfoService.paySuccess())
- 输入参数:
payInfoResult:PayInfoResultBO(包含payId、bizPayNo)。orderIds:订单 ID 列表(List<Long>)。
- 逻辑:
- 更新支付状态:
- 更新
PayInfo:payStatus:设置为PayStatus.PAYED(1)。bizPayNo:从payInfoResult获取(String)。callbackContent:回调原始数据(String)。callbackTime:当前时间(Date)。
- 执行数据库更新。
- 更新
- 通知订单服务:
- 通过
RocketMQTemplate发送消息:- 主题:
XXX_ORDER_NOTIFY_TOPIC。 - 消息体:
PayNotifyBO(包含orderIds)。
- 主题:
- 若发送失败,抛出异常。
- 通过
- 更新支付状态:
- 特性:事务性操作,确保状态更新和消息发送一致。
6. 前端查询支付状态(可选)
- 触发点:前端调用
GET /pay/isPay/{orderIds}。 - 输入参数:
orderIds:订单 ID 列表(String,如 "1,2,3")。
- 逻辑(
PayController.isPay()):- 查询状态:
- 调用
PayInfoService.isPay(orderIds, userId)。 - 参数:
orderIds(String)、userId(Long)。 - 返回:
Boolean(true 表示已支付)。
- 调用
- 返回结果:
- 返回
ServerResponseEntity<Boolean>。
- 返回
- 查询状态:
- 输出:
true或false。
三、时序图
以下是支付交易的时序图,使用 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, returnUrl)
P->>S: pay(userId, payParam)
S->>L: getSegmentId()
L-->>S: payId
S->>O: getOrdersAmountAndIfNoCancel(orderIds)
O-->>S: payAmount
S->>S: 保存 PayInfo (payId, userId, payAmount, orderIds)
S-->>P: PayInfoBO (payId, payAmount, body)
P-->>F: payId
F->>T: 唤起支付 (payId, payAmount, notify_url)
U->>T: 输入密码支付
T-->>N: POST /notice/pay/order (payId, transaction_id)
N->>S: getByPayId(payId)
S-->>N: PayInfo
N->>S: paySuccess(payInfoResult, orderIds)
S->>S: 更新 PayInfo (payStatus=1, bizPayNo)
S->>R: 发送消息 (orderIds)
R-->>S: 发送成功
N-->>T: SUCCESS
F->>P: GET /pay/isPay/{orderIds}
P->>S: isPay(orderIds, userId)
S-->>P: true/false
P-->>F: true/false
说明:
notify_url在前端唤起支付时由后端提供给第三方支付平台。- RocketMQ 确保支付成功后订单状态同步。
四、链路特点
- 分布式 ID:
payId通过SegmentFeignClient生成,确保唯一性。 - 状态机:
payStatus驱动流程(0 -> 1)。 - 最终一致性:RocketMQ 保证支付与订单状态同步。
- 安全性:事务控制和消息幂等性。
- 可追溯性:
payId贯穿链路。
五、注意事项与优化建议
- 回调地址来源:
notify_url由后端在调用第三方支付接口时提供,而非仅靠前端传递payId。
- 回调安全性:
- 当前代码未验证签名,需添加(如微信 MD5 或支付宝 RSA2)。
- 测试逻辑:
PayController中调用submit()是测试代码,生产环境应移除。
- 支付参数:
- 返回的
payId需配合prepay_id等参数,前端才能唤起支付。
- 返回的
- 异常处理:
- RocketMQ 发送失败可增加重试或补偿机制。
六、总结
该支付模块链路清晰,从前端发起支付到第三方回调更新状态,再到通知下游服务,体现了微服务架构特点。通过时序图可直观理解交互逻辑,适合技术分享或面试准备。优化方向包括增强安全性、完善支付参数和异常处理。