电商项目-支付模块交易链路梳理与时序图

466 阅读6分钟

支付模块交易链路梳理与时序图

本文基于某支付模块的代码实现(项目名已替换为 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()):
    1. 获取用户 ID:
      • 调用 AuthUserContext.getUserId(),返回 userId(Long)。
    2. 调用支付服务:
      • 参数:userId(Long)、payParamPayInfoDTO)。
      • 返回:PayInfoBO(包含 payIdpayAmountbody 等)。
    3. 设置回调地址和跳转地址:
    4. 模拟回调(仅测试用):
      • 调用 PayNoticeController.submit(payId),参数为 payId(Long)。
    5. 返回结果:
      • 返回 ServerResponseEntity<PayId>,其中 payId 是 Long 类型。
  • 输出ServerResponseEntity 封装的 payId(Long)。

注意apiNoticeUrl 是后端预配置的固定地址,在生成支付记录时会传递给第三方支付平台(见第 3 步),而非仅靠前端传递 payId

2. 支付记录创建 (PayInfoService.pay())

  • 输入参数
    • userId:用户 ID(Long)。
    • payParamPayInfoDTO(包含 orderIdsreturnUrl)。
  • 逻辑
    1. 生成支付 ID
      • 调用 SegmentFeignClient.getSegmentId(),返回 payId(Long)。
    2. 验证订单
      • 调用 OrderFeignClient.getOrdersAmountAndIfNoCancel(orderIds)
      • 参数:orderIds(String,如 "1,2,3")。
      • 返回:OrderAmountBO(包含 payAmount(Long) 和订单有效性)。
    3. 构建支付记录
      • 创建 PayInfo 对象:
        • payId:从步骤 1 获取(Long)。
        • userId:从输入获取(Long)。
        • payAmount:从 OrderAmountBO 获取(Long)。
        • orderIds:从 payParam 获取(String)。
        • payStatus:初始化为 PayStatus.UNPAY(0)。
    4. 保存记录
      • 调用数据库插入操作,保存 PayInfo
    5. 返回支付参数
      • 构造 PayInfoBO
        • payId(Long)。
        • payAmount(Long)。
        • body(String,支付描述,如 "XXX 商品支付")。
  • 输出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),传入 payIdpayAmountnotify_url(即 apiNoticeUrl)。第三方支付平台会记录该 notify_url,并在支付成功后回调此地址。

4. 第三方支付平台回调

  • 触发点:用户支付成功后,第三方支付平台向 /notice/pay/order 发送 POST 请求。
  • 输入参数(以微信为例,代码中简化):
    • out_trade_no:商户订单号,即 payId(Long)。
    • transaction_id:第三方交易号(String)。
    • sign:签名(String)。
  • 后端逻辑PayNoticeController.submit()):
    1. 查询支付记录:
      • 调用 PayInfoService.getByPayId(payId)
      • 参数:payId(Long)。
      • 返回:PayInfo
    2. 解析订单 ID:
      • PayInfo.orderIds(String)解析为 List<Long>
    3. 构造支付结果:
      • 创建 PayInfoResultBO
        • payId(Long)。
        • bizPayNo(从回调参数 transaction_id 获取,String)。
    4. 处理支付成功:
      • 调用 PayInfoService.paySuccess(payInfoResult, orderIds)
      • 参数:payInfoResultPayInfoResultBO)、orderIdsList<Long>)。
    5. 返回响应:
      • 返回空字符串(实际应返回 "SUCCESS",符合第三方要求)。
  • 输出ResponseEntity<String>

5. 支付成功处理 (PayInfoService.paySuccess())

  • 输入参数
    • payInfoResultPayInfoResultBO(包含 payIdbizPayNo)。
    • orderIds:订单 ID 列表(List<Long>)。
  • 逻辑
    1. 更新支付状态
      • 更新 PayInfo
        • payStatus:设置为 PayStatus.PAYED(1)。
        • bizPayNo:从 payInfoResult 获取(String)。
        • callbackContent:回调原始数据(String)。
        • callbackTime:当前时间(Date)。
      • 执行数据库更新。
    2. 通知订单服务
      • 通过 RocketMQTemplate 发送消息:
        • 主题:XXX_ORDER_NOTIFY_TOPIC
        • 消息体:PayNotifyBO(包含 orderIds)。
      • 若发送失败,抛出异常。
  • 特性:事务性操作,确保状态更新和消息发送一致。

6. 前端查询支付状态(可选)

  • 触发点:前端调用 GET /pay/isPay/{orderIds}
  • 输入参数
    • orderIds:订单 ID 列表(String,如 "1,2,3")。
  • 逻辑PayController.isPay()):
    1. 查询状态:
      • 调用 PayInfoService.isPay(orderIds, userId)
      • 参数:orderIds(String)、userId(Long)。
      • 返回:Boolean(true 表示已支付)。
    2. 返回结果:
      • 返回 ServerResponseEntity<Boolean>
  • 输出truefalse

三、时序图

以下是支付交易的时序图,使用 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 确保支付成功后订单状态同步。

四、链路特点

  1. 分布式 IDpayId 通过 SegmentFeignClient 生成,确保唯一性。
  2. 状态机payStatus 驱动流程(0 -> 1)。
  3. 最终一致性:RocketMQ 保证支付与订单状态同步。
  4. 安全性:事务控制和消息幂等性。
  5. 可追溯性payId 贯穿链路。

五、注意事项与优化建议

  1. 回调地址来源
    • notify_url 由后端在调用第三方支付接口时提供,而非仅靠前端传递 payId
  2. 回调安全性
    • 当前代码未验证签名,需添加(如微信 MD5 或支付宝 RSA2)。
  3. 测试逻辑
    • PayController 中调用 submit() 是测试代码,生产环境应移除。
  4. 支付参数
    • 返回的 payId 需配合 prepay_id 等参数,前端才能唤起支付。
  5. 异常处理
    • RocketMQ 发送失败可增加重试或补偿机制。

六、总结

该支付模块链路清晰,从前端发起支付到第三方回调更新状态,再到通知下游服务,体现了微服务架构特点。通过时序图可直观理解交互逻辑,适合技术分享或面试准备。优化方向包括增强安全性、完善支付参数和异常处理。