本手册围绕 DGJ2 的采购在线支付、预订单支付、销售收款、退款、待退款账户、返利、授信/白条、还款、账户调整、提现和对账核销,建立“支付中心消息 -> 业务单 -> 收付款单 -> 账户流水 -> 业务状态”的完整排查路径。

适用于支付成功业务单没推进、付款单重复/缺失、退款不到账、账户余额异常、授信还款重复、金额相差 100 倍、回调重复投递和迟到支付等问题。所有生产修复都必须先确认金额单位、业务唯一键和外部最终状态。

1. 业务目标

  • 从支付单号、采购单号、预订单号、退款/售后号任一标识找到完整资金链。
  • 正确计算 sid%32 收付款分片和 sid%16 账户分片。
  • 区分外部支付金额“分”和本地财务金额“元”。
  • 解释 Payment 主单、PaymentInfo 明细、Account 流水和 PoOrderPayInfo 快照各自职责。
  • 判断回调在消息校验、业务定位、财务落账、状态推进还是后置副作用处失败。
  • 让支付成功、退款、余额调整和授信还款在重复投递下保持幂等。
  • 修复后同时核对外部支付、业务单、财务单、账户、报表和特殊来源业务。

2. 六层资金模型

flowchart LR
    EXT["支付中心订单/回调"] --> BIZ["采购/预订单/销售/售后"]
    EXT --> PAY["Payment收付款主单"]
    PAY --> PI["PaymentInfo结算明细"]
    PI --> ACC["Account账户流水"]
    EXT --> SNAP["PoOrderPayInfo支付构成快照"]
    BIZ --> REPORT["往来账/报表"]
    PAY --> REPORT
    ACC --> REPORT
层回答的问题不能替代
支付中心外部最终支付/退款结果DGJ 本地是否完整落账
业务单这笔钱为哪张订单服务账户实际增减
Payment生成了哪张收款/付款/收入/支出单每个账户分摊
PaymentInfo每个账户、结算对象和业务单的金额账户余额汇总
Account账户余额如何变化原业务履约状态
PoOrderPayInfo采购支付方式、折扣、返利、退款账户构成Payment/Account 的元级财务流水

3. 适用现象与首查方向

现象首查常见原因
支付中心成功,采购仍待支付PayCenterNotify、采购状态回调未到、状态前置不符、事务回滚
支付成功没有付款单32 分片 Payment/Info查错分片、零元订单、回调中途失败
有付款单但账户流水没有AccountService、16 分片账户saveAccounts 失败、事务/连接问题
账户流水有但采购状态没变同一事务和状态更新业务状态更新失败或外部脚本直写财务
金额相差 100 倍分/元边界外部字段未除 100 或本地字段重复除 100
付款总额比支付金额大服务费Payment 总额包含 serviceAmount
重复支付回调 NACK采购状态已推进代码把重复状态识别为错误并要求重试
支付超时后又成功迟到支付采购状态 12 仍允许成功回调
退款回调后普通退款没财务单回调职责paycenterRefundResult 当前主要处理专属授信返还
退款账户余额不对账户类型和退款事件退款/提现/支付抵扣不同事件混淆
授信还款重复生成支出单还款幂等键bill_no/state 未正确判断
调整单重复adjustNoPaymentInfo 未保存或查错站点分片
销售收全款未核销出库金额和 PaymentInfostlId 错、有效状态过滤、差额口径
逾期锁站状态错误逾期消息lockStatus、时间门槛、菜单规则失败

4. 排查前事实卡

环境:
回调事件/routing key:
回调时间:
sourceOrderType:
sourceOrderNo / afterSaleNo:
payOrderNo / refundNo / adjustNo:
sid:
业务单 id / billNo / 当前状态:
mainPayTypeCode:
外部总金额(分):
外部明细金额(分):
本地 Payment 金额(元):
本地 PaymentInfo 汇总(元):
Account 汇总(元):
PoOrderPayInfo 金额(分):
是否包含服务费/折扣/返利/待退款/授信:
是否重复回调或迟到支付:

5. 分表和核心表

5.1 收付款:32 分片

t_scm_payment_{sid % 32}
t_scm_payment_info_{sid % 32}

PaymentModel、PaymentInfoModel 的 setSid() 已确认使用该规则。

5.2 账户:16 分片

t_scm_account_{sid % 16}
t_scm_account_info_{sid % 16}

ScmAccountSubModel、ScmAccountInfoSubModel 使用该规则。

5.3 非同规则表

表分片用途
t_scm_po_order_payinfo当前 Model 单表采购支付构成快照
t_scm_repayment_order当前 Model 单表授信还款单
t_scm_payment_log以线上 DDL/Model 为准收付款操作日志
th_scm_account历史表往月账户金额/流水
采购主单 t_scm_po_order不按 sid 分片在线支付业务单
销售出库 t_scm_sa_invoice_0_{sid%64}64 分片销售应收和核销对象
flowchart TB
    SID["sid"] --> M32["sid % 32"]
    SID --> M16["sid % 16"]
    SID --> COL["单表sid字段"]
    M32 --> P["payment_n"]
    M32 --> PI["payment_info_n"]
    M16 --> A["account_n"]
    M16 --> AI["account_info_n"]
    COL --> POI["po_order_payinfo"]
    COL --> REPAY["repayment_order"]

5.4 常见误查

  • 查 t_scm_payment 基表,漏新 32 分片。
  • Payment 用 32 后缀,Account 也机械使用 32,实际应为 16。
  • 把 PoOrderPayInfo.iid 当 Payment 主单 ID,实际主要关联采购/预订单业务 ID。
  • 用 Payment billNo 反查业务单,而业务关联稳定字段是 PaymentInfo stlId/stlNo。
  • 跨月只查当前账户分片,不考虑 th_scm_account。

6. 入口与代码地图

6.1 PC/业务入口

业务Controller/Service
付款application/controllers/scm/Payment.php
收款application/controllers/scm/Receipt.php
对账application/controllers/scm/AccCheck.php
采购支付application/controllers/scm/InvPo.php
销售对账/核销application/service/scm/InvSaService.php、ReceiptService.php
授信还款Repayment 相关 Controller/Service

6.2 回调入口

application/controllers/tasks/PayCenterNotify.php 消费:

destination = DEST_PAYCENTER_NOTIFY

6.3 核心 Model/Service

模块文件
Payment 主单application/models/payment/PaymentModel.php
PaymentInfoapplication/models/payment/PaymentInfoModel.php
采购支付快照application/models/orders/PoOrderPayInfoModel.php
Accountapplication/models/bs/AccountModel.php、application/models/scm/ScmAccount*Model.php
账户写入application/service/scm/AccountService.php
还款单application/models/payment/RepaymentOrderModel.php
支付回调application/controllers/tasks/PayCenterNotify.php
全车件支付application/controllers/tasks/QPayCenterNotify.php

7. Payment 状态和交易类型

7.1 Payment 状态

值枚举含义
0BILLSTATUS_CONFIRMED已确认
1BILLSTATUS_SUBMITED已提交
2BILLSTATUS_CONFIRMED1已确认兼容值
6BILLSTATUS_CANCELED已撤销

报表通常排除 billStatus=6 和 isDelete=1。

7.2 常见资金交易类型

transType含义常见方向
153001收款账户增加/应收减少,按业务定义
153101付款账户支出
153401其他收入账户增加
153402其他支出账户减少
155001预收款客户资金
155002退款账户退款
155003余额支出账户扣减
154001调拨收款收入
154002调拨付款支出
150601销售收款/销售出库结算应收
150602销售退款/销退结算应退
150501采购付款明细应付
170401采购订单支付快照主类型采购
170001预订单支付快照主类型预订单

交易类型命名有历史复用,必须联合 billType + transType + stlId/stlNo 判断。

8. 支付中心十类事件

事件Handler主要副作用
支付结果paycenterPayResultPayment/Info/Account/PayInfo、业务状态
额度调整paycenterBalanceAdjust收入/支出单和账户
提现退款结果paycenterRefundWithdrawResult提现状态、支出单、待退款账户
授信线上还款paycenterBaitiaoReturnPayResult还款单、支出明细、账户
授信线下还款paycenterBaitiaoOfflineReturnPayResult新建已还款单、支出和账户
授信到期paycenterBaitiaoExpire站内信/短信提醒
支付异常paycenterPayException取消待支付采购单
分期到期paycenterInstallmentExpire到期提醒
退款结果paycenterRefundResult当前主要返还专属授信活动额度
服务站逾期变化paycenterBaitiaoServiceOverdueChange锁定/解锁菜单
flowchart LR
    Q["支付中心MQ"] --> D{"routing key"}
    D --> PAY["支付结果"]
    D --> EX["支付异常"]
    D --> REF["退款/提现"]
    D --> BAL["额度调整"]
    D --> BT["授信还款/到期"]
    PAY --> DB["财务+业务事务"]
    REF --> DB
    BAL --> DB
    BT --> DB

9. 金额单位矩阵

这是财务排查最重要的口径。

字段/表典型单位代码证据
回调 detailTotalAmount分Payment 总额写入时 /100
回调 details[].payAmount分PaymentInfo 写入时 /100
回调 serviceAmount分PaymentInfo 合并后 /100
回调折扣/返利/退款明细分PoOrderPayInfo 直接保留
Payment.totalAmount元detailTotalAmount/100,再加服务费
PaymentInfo.amount元(payAmount+serviceAmount)/100
Account 流水金额元由 PaymentInfo 传入,方向另处理
PoOrderPayInfo.amount/rebate/refund/...分支付构成原始值直接写入
PoOrderPayInfo.service_amount分累加原始服务费
RepaymentOrder.amount/interest/late_fee多数按外部分保存展示/生成 PaymentInfo 时 /100

9.1 正确金额关系

Payment.totalAmount(元)
= (detailTotalAmount(分) + sum(serviceAmount分)) / 100

sum(PaymentInfo.amount元)
= sum(payAmount分 + serviceAmount分) / 100

PoOrderPayInfo 不应直接与 Payment 元字段数值相等,应先统一单位。

flowchart LR
    CENT["支付中心金额 分"] --> DIV["除以100"]
    DIV --> PAY["Payment 元"]
    DIV --> INFO["PaymentInfo 元"]
    INFO --> ACC["Account 元"]
    CENT --> SNAP["PoOrderPayInfo 分"]

10. 支付成功回调定位

10.1 脱敏消息示例

{
  "sourceOrderNo": "PO-DEMO-20260715-001",
  "sourceOrderType": "01",
  "payOrderNo": "PAY-DEMO-001",
  "mainPayTypeCode": "02",
  "payStatus": "02",
  "payStatusMsg": "支付成功",
  "existsCashDiscount": "0",
  "cashDiscountRate": "100",
  "cashDiscountAmount": "0",
  "detailTotalAmount": "12345",
  "payTime": "2026-07-15 10:00:00",
  "details": [
    {
      "payAccountTypeCode": "02",
      "payAccountTypeDesc": "示例付款账户",
      "payAmount": "12345",
      "serviceAmount": "0"
    }
  ]
}

10.2 外部成功判断

payStatus 必须等于 '02'

非 02 当前返回 NACK;需要结合支付中心重试协议确认失败状态是否应该持续重投。

10.3 业务类型

sourceOrderType本地业务
01采购单
04预订单

11. 采购支付成功完整流程

sequenceDiagram
    participant PC as 支付中心
    participant N as PayCenterNotify
    participant PO as 采购主单
    participant P as Payment32分片
    participant PI as PaymentInfo32分片
    participant A as Account16分片
    participant S as PoOrderPayInfo
    PC->>N: pay_result payStatus=02
    N->>PO: 按sourceOrderNo查采购
    N->>N: 校验状态10或12
    N->>P: 写付款主单
    N->>PI: 按支付账户写明细
    N->>A: 写账户流水
    N->>S: 写支付构成快照(分)
    N->>PO: 状态改为1并记录paymentTime
    N->>N: 特殊来源后置处理
    N-->>PC: ACK

11.1 可接受采购状态

支付成功回调只接受:

  • 10 待支付。
  • 12 支付超时。

其他状态被视为状态错误或重复消息,当前代码抛异常并 NACK。

11.2 落账字段

Payment 主单:

  • billType=PAYMENT/FKD 路径。
  • transType=153101 付款。
  • totalAmount 为元,包含服务费。
  • sid/uid/buId 来自采购单。

PaymentInfo:

  • 一种支付账户一条明细。
  • transType=150501 采购付款明细。
  • paymentType=1 标识在线支付。
  • stlId=采购单.id。
  • stlNo=采购单.billNo。
  • amount=(payAmount+serviceAmount)/100 元。

Account:由 AccountService::saveAccounts() 根据 PaymentInfo 生成。

PoOrderPayInfo:保存主支付类型、折扣、返利、待退款账户、服务费等原始分值。

11.3 业务状态

回调末尾:

采购 billStatus -> 1 已提交
paymentTime -> payTime
auditOpinion -> payStatusMsg

秒杀来源 13 还会调用 FlashSaleSer::markPaidByPoOrder() 转活动数量。

12. 事务与后置副作用

支付主链在采购 Model 数据库事务中完成:

  • Payment。
  • PaymentInfo。
  • Account。
  • PoOrderPayInfo。
  • 采购状态。
  • 秒杀支付转量。

事务提交后才执行部分工单同步。因此:

失败点财务/采购后置工单处理
事务内失败回滚未执行消息 NACK 重试
提交成功、工单失败已成功失败只补工单,不重放支付主链
ACK 丢失已成功可能成功重复消息需幂等识别

13. 幂等与重复回调

13.1 当前状态门槛

采购支付成功后状态从 10/12 改成 1。重复回调再次进入时,状态不在允许集合,会抛“状态错误或者消息重复”并 NACK。

这能阻止重复入账,但不一定是最佳 MQ 语义:已完成的重复消息更适合识别为幂等成功并 ACK,否则可能形成无意义重试/死信。线上策略需要确认。

13.2 数据库幂等键建议

数据业务唯一键候选
外部支付payOrderNo
采购支付sourceOrderType + sourceOrderNo
PaymentInfostlId + accId + 外部支付号
PoOrderPayInfosid + iid + billNo + transType,需结合多次支付规则
余额调整sid + adjustNo
授信还款bill_no
提现退款提现单业务号

13.3 重复测试

同一成功消息连续投递两次,预期:

  • Payment 不新增第二张。
  • PaymentInfo 不新增第二组。
  • Account 不重复变化。
  • PoOrderPayInfo 不重复。
  • 采购状态保持正确。
  • 秒杀 used_qty 不重复增加。
  • 第二次应有明确幂等日志和稳定 ACK 策略。

14. 迟到支付

采购状态 12 支付超时仍被允许处理成功回调。这是为支付中心迟到成功保留的恢复路径,但风险更高:

  • 超时任务可能已释放活动锁量。
  • 采购可能已经触发关闭/取消。
  • 专属授信额度可能已释放。
  • 页面已允许用户重新下单。
flowchart TD
    A["状态12收到成功回调"] --> B["确认支付中心最终成功"]
    B --> C["确认采购关闭副作用"]
    C --> D["确认活动/库存/额度是否已释放"]
    D --> E["事务内恢复采购和财务"]
    E --> F["补回特殊来源占用"]
    F --> G["检查是否重复新订单"]

专属授信超时后迟到支付代码会重新扣活动额度,注释已提示可能导致额度为负,这是已知高风险场景。

15. 零元订单

当主支付类型为零元订单:

  • 回调会短暂等待以降低订单未落库竞态。
  • 采购路径可跳过常规 Payment/PaymentInfo 构建,直接推进业务状态。
  • 排查时“没有付款单”可能是符合设计,不应直接补财务单。

必须用 mainPayTypeCode 和订单金额共同证明零元语义。

16. 混合支付

16.1 支付账户构成

一笔支付可包含:

  • 对公/对私账户。
  • 授信/白条。
  • 融资或银行账户。
  • 待退款账户。
  • 普通返利、专属返利。
  • 现金折扣。

16.2 主支付类型修正

  • 明细含多个主支付账户时,支付快照类型标记为混合支付。
  • 授信与返利/待退款混合时,展示类型可能强制按授信。
  • 主类型为待退款账户时,快照类型可转换为兼容值。

16.3 对账关系

外部 detailTotalAmount
= sum(details.payAmount)

本地 PaymentInfo 总额
= sum((payAmount + serviceAmount)/100)

Payment.totalAmount
= (detailTotalAmount + sum(serviceAmount))/100

折扣、返利和待退款账户要按支付中心合同判断是否包含在 detailTotalAmount,不能重复相加。

17. 预订单支付

17.1 与采购的差异

维度采购预订单
sourceOrderType0104
业务定位采购主单PreOrderService
主交易类型快照170401170001
财务单一张付款单多账户明细每个支付账户可生成一张其他支出单
状态推进采购状态 1payedPreOrder()

预订单可支付状态包含待支付、取消、自动取消,说明迟到支付同样需要特别核对释放副作用。

17.2 预订单账户方向

预订单其他支出写 Account 前会把 PaymentInfo 金额转为负数,表示账户支出;PaymentInfo 本身仍按正金额展示业务支出。

18. 支付异常

paycenterPayException:

  1. 用 sourceOrderNo 查采购单。
  2. 调用 PoOrderSer::cancelWaitPayOrder(orderId, true, exceptionDesc)。
  3. 成功 ACK,异常 NACK。
sequenceDiagram
    participant PC as 支付中心
    participant N as PayCenterNotify
    participant PO as PoOrderSer
    participant SRC as 活动/预订单来源
    PC->>N: pay_exception
    N->>PO: cancelWaitPayOrder
    PO->>PO: 关闭待支付采购
    PO->>SRC: 释放来源占用
    N-->>PC: ACK/NACK

排查时确认异常取消是否同时释放秒杀锁量、预订单占用和专属额度。

19. 退款结果

19.1 当前回调职责

paycenterRefundResult 遍历退款明细;当前静态代码重点处理:

  • 退款账户类型为授信。
  • 退款金额大于 0。
  • 售后单是未履约退货类型。
  • 找到原采购和专属授信活动。
  • 把金额返还到专属授信活动额度。

它不是一套完整通用退款财务入账实现。看到普通退款回调 ACK,不代表 Payment/Account 已自动生成退款流水,需追踪发起退款的业务 Service。

19.2 退款排查

  1. 固定 afterSaleNo 和退款业务号。
  2. 查售后/退货采购单和原采购单。
  3. 查支付中心最终退款状态和明细账户。
  4. 查退款财务单由哪个入口创建。
  5. 查 Account 方向和待退款账户。
  6. 授信退款再查活动额度。
  7. 重复回调不应重复返还额度。

20. 额度调整

20.1 幂等键

paycenterBalanceAdjust 先用 sid + adjustNo 查 PaymentInfo。已有记录时视为已处理。

20.2 方向

调整类型本地业务交易类型
调增其他收入153401
调减其他支出153402

每个子账户明细会创建/复用系统账户,授信额度调整不一定直接生成普通收入单,需按代码分支理解。

20.3 异常

  • adjustNo 未落 PaymentInfo,重复回调无法识别。
  • 系统账户不存在时自动创建失败。
  • 供应商映射不到时使用兼容客户,需评估报表口径。
  • 多明细中途失败依赖事务整体回滚。

21. 提现退款结果

该事件连接:

提现申请 -> 审核结果 -> Payment/PaymentInfo -> 待退款账户 -> Account -> 通知

成功路径通常:

  • 更新提现状态。
  • 生成其他支出单。
  • PaymentInfo 金额由分转元。
  • 写 Account 前金额转负。
  • 发送站内信。

注意:通知发送失败是否影响事务/重试,需要按具体位置判断;补偿通知时不要重放已落账主链。

22. 授信/白条还款

22.1 还款单状态

state含义
0未还款
1已还款
2还款失败/取消

状态 1 设置 pay_time;状态 2 只允许从 0 更新并记录取消人/时间。

22.2 线上/自动还款

paycenterBaitiaoReturnPayResult:

  • 用 sourceNo 查还款单。
  • 自动还款可在本地不存在时创建还款单。
  • 已 state=1 视为重复。
  • 本金、利息、滞纳金按 details 拆 PaymentInfo。
  • 外部分金额 /100 写元。
  • 写 Account 前转负数。
  • 更新还款单金额和状态。

22.3 线下还款

paycenterBaitiaoOfflineReturnPayResult:

  • 用 orderNo 幂等。
  • 创建已还款状态 RepaymentOrder。
  • 生成其他支出 Payment/Info。
  • 写负向账户流水。

22.4 幂等风险

当前重复还款被抛异常并可能 NACK。与采购支付相同,业务已完成的重复消息更适合稳定幂等 ACK,需确认生产重试协议。

sequenceDiagram
    participant PC as 支付中心
    participant N as PayCenterNotify
    participant R as RepaymentOrder
    participant P as Payment/Info
    participant A as Account
    PC->>N: 授信还款结果
    N->>R: 按bill_no查状态
    alt 已还款
        N-->>PC: 幂等结果
    else 未处理
        N->>P: 本金/利息/滞纳金明细
        N->>A: 负向账户流水
        N->>R: state=1/pay_time
        N-->>PC: ACK
    end

23. 到期提醒与逾期锁站

23.1 到期提醒

  • 授信到期消息查服务站和待还列表。
  • 发送站内信和短信。
  • 任一通知失败可能 NACK,导致重复提醒;需确认消费者重试和通知幂等。
  • 分期到期金额字段可能直接是元,不能套用所有回调都按分的假设。

23.2 逾期菜单锁定

锁定状态:

外部状态动作
01 未锁定解锁菜单
02 锁定中10:00 后锁定菜单
03 延期锁定解锁菜单

单个站点异常会记录并继续处理列表中其他站点,整体消息可能仍 ACK,因此必须监控每站处理结果。

24. 销售收款、对账和核销

24.1 关联键

PaymentInfo.stlId = 销售出库单.id
PaymentInfo.stlNo = 销售出库单.billNo
PaymentInfo.sid = 出库单.sid

销售出库表按 _0_{sid%64},PaymentInfo 按 sid%32。

24.2 核销公式

出库 amount == 有效收款 payAmount + diffAmount
或出库 amount == 0
    -> 出库状态5、hxStateCode=2
否则
    -> 出库状态4 已对账

24.3 有效收款过滤

  • isDelete=0。
  • billStatus<>6。
  • transType 属于销售收款/退款口径。
  • stlId 指向正确出库分片记录。
  • 日期区间与报表截止时间一致。

25. 一笔采购支付全景 SQL

仅在只读连接执行。字段和唯一键以目标环境 DDL 为准。

25.1 采购和支付快照

select id, sid, billNo, billStatus, paymentType, paymentTime,
       srcOrderType, srcOrderId, srcOrderNo, amount, totalQty
from t_scm_po_order
where billNo = :po_bill_no;

select id, sid, iid, billNo, transType, type,
       amount, discount, rebate, refund, exclusive_rebate,
       service_amount
from t_scm_po_order_payinfo
where sid = :sid and iid = :po_order_id
order by id;

25.2 Payment/PaymentInfo

-- :pay_suffix = sid % 32
select id, sid, billNo, billType, transType, totalAmount,
       buId, billStatus, isDisable, createTime, isDelete
from t_scm_payment_:pay_suffix
where sid = :sid
  and id in (
      select iid from t_scm_payment_info_:pay_suffix
      where sid = :sid and stlId = :po_order_id and isDelete = 0
  );

select id, iid, sid, billNo, billType, transType,
       amount, diffAmount, accId, wayId, paymentType,
       stlId, stlNo, stlDate, billStatus, createTime, isDelete
from t_scm_payment_info_:pay_suffix
where sid = :sid and (stlId = :po_order_id or stlNo = :po_bill_no)
order by iid, id;

25.3 Account

-- :account_suffix = sid % 16
select *
from t_scm_account_:account_suffix
where sid = :sid and iid in (:payment_ids)
order by createTime, id;

select *
from t_scm_account_info_:account_suffix
where sid = :sid and iid in (:account_ids)
order by createTime, id;

账户表实际关联字段需按线上 DDL 和 AccountService 写入结构确认。

26. 金额对账 SQL

26.1 Payment 与明细

select p.id, p.billNo, p.totalAmount,
       coalesce(sum(case when i.isDelete=0 and i.billStatus<>6 then i.amount else 0 end),0) as info_amount
from t_scm_payment_:pay_suffix p
left join t_scm_payment_info_:pay_suffix i on i.iid = p.id and i.sid = p.sid
where p.sid = :sid and p.id in (:payment_ids)
group by p.id, p.billNo, p.totalAmount;

含服务费、多账户和特殊收入/支出拆单时,需要按同一业务单汇总多张 Payment,不能强制一张主单等于全部明细。

26.2 重复候选

select stlId, stlNo, transType, accId, amount, count(*) as cnt
from t_scm_payment_info_:pay_suffix
where sid = :sid and isDelete = 0 and billStatus<>6
group by stlId, stlNo, transType, accId, amount
having count(*) > 1;

混合支付、分次收付款可能合法,必须结合外部支付号和创建时间判断。

27. 支付成功故障树

flowchart TD
    A["支付成功业务未推进"] --> B{"消息到达?"}
    B -->|否| B1["查支付中心/routing/binding"]
    B -->|是| C{"payStatus=02?"}
    C -->|否| C1["失败消息NACK策略"]
    C -->|是| D{"业务单存在?"}
    D -->|否| D1["sourceOrderType/No错误或竞态"]
    D -->|是| E{"状态为10或12?"}
    E -->|否| E1["重复回调或非法状态"]
    E -->|是| F{"Payment/Info写入?"}
    F -->|否| F1["事务内异常"]
    F -->|是| G{"Account/PayInfo完整?"}
    G -->|否| G1["账户/支付构成断点"]
    G -->|是| H{"业务状态已更新?"}
    H -->|否| H1["事务回滚/跨连接"]
    H -->|是| I["查特殊来源后置状态"]

28. 按现象执行 SOP

28.1 支付成功仍待支付

  1. 查回调日志和消息体摘要。
  2. 确认 payStatus='02'。
  3. 用 sourceOrderType/sourceOrderNo 查业务单。
  4. 查回调前状态是否 10/12。
  5. 计算 32 分片查 Payment/Info。
  6. 计算 16 分片查 Account。
  7. 查 PoOrderPayInfo。
  8. 查事务异常和 NACK 重试。
  9. 特殊来源查秒杀/预订单状态。

28.2 本地有两张付款单

  1. 按 stlId/stlNo 找所有 PaymentInfo。
  2. 反查 Payment 主单和创建时间。
  3. 区分正常混合支付附加收入单与重复主付款单。
  4. 查同一外部 payOrderNo 的回调次数。
  5. 查采购状态在第一次回调后是否及时改变。
  6. 查唯一键和事务提交/ACK 顺序。

28.3 金额相差 100 倍

  1. 标注每个字段单位。
  2. 外部原始值统一为分。
  3. Payment/Info/Account 统一换算成分比较,或全部换成元。
  4. PoOrderPayInfo 不先除 100 就不要与 Payment 比。
  5. 单独加入 serviceAmount。
  6. 折扣/返利/待退款确认是否包含在总额。

28.4 退款不到账

  1. 查发起退款的售后/关闭业务。
  2. 查支付中心退款最终状态。
  3. 查 afterSaleNo 和原支付单关系。
  4. 查退款账户类型。
  5. 查实际创建退款 Payment/Account 的 Service,不只看 paycenterRefundResult。
  6. 授信退款查活动额度返还。
  7. 查重复回调和幂等结果。

28.5 授信还款重复

  1. 查 repayment_order.bill_no/state。
  2. 查自动、线上还是线下还款。
  3. 查 32 分片 PaymentInfo 的 stlId/stlNo。
  4. 查 16 分片 Account。
  5. 查本金、利息、滞纳金拆分。
  6. 查重复消息是 ACK 还是 NACK,是否持续重试。

28.6 销售已收款未核销

  1. 查 64 分片出库 amount/hxStateCode/billStatus。
  2. 查 32 分片 PaymentInfo。
  3. 排除状态 6 和软删。
  4. 计算有效 amount+diffAmount。
  5. 确认 stlId 指向出库 ID。
  6. 查对账/核销回写日志。

29. 补偿与修复决策

flowchart TD
    A["财务链不一致"] --> B["冻结业务单并保存六层快照"]
    B --> C{"支付中心最终状态?"}
    C -->|失败/未支付| D["不得补成功财务"]
    C -->|成功| E{"本地业务状态?"}
    E --> F{"财务单完整?"}
    F -->|否| G["单支付号幂等补账"]
    F -->|是| H{"账户流水完整?"}
    H -->|否| I["通过AccountService补账户"]
    H -->|是| J["只补业务/特殊来源状态"]
    G --> K["金额单位和影响行数复核"]
    I --> K
    J --> K

29.1 修复优先级

  1. 原支付/退款回调的幂等补偿入口。
  2. 业务 Service 的状态恢复/取消接口。
  3. Payment/Account 领域 Service。
  4. 单支付号、带断言的修复脚本。
  5. 最后才直接 SQL,必须同步修正所有关联层。

29.2 禁止操作

  • 只改采购 billStatus。
  • 只插 Payment,不写 PaymentInfo/Account。
  • 把 PoOrderPayInfo 分值当元写入 Payment。
  • 删除重复财务单前不判断账户流水。
  • 未确认支付中心最终状态就重放成功回调。
  • 用随机 MQ 消息 ID 当支付幂等键。

30. 回归测试矩阵

30.1 采购支付

  • [ ] 普通在线支付成功。
  • [ ] 零元订单。
  • [ ] 多账户混合支付。
  • [ ] 待退款账户/返利/专属返利。
  • [ ] 服务费支付。
  • [ ] 重复成功回调。
  • [ ] 状态 12 迟到支付。
  • [ ] 支付异常取消。
  • [ ] 秒杀来源支付转量。

30.2 预订单

  • [ ] 正常待支付成功。
  • [ ] 取消/自动取消后迟到支付。
  • [ ] 多账户拆其他支出单。
  • [ ] 重复回调不重复入账。

30.3 退款和调整

  • [ ] 采购关闭退款。
  • [ ] 采购退货退款。
  • [ ] 普通账户退款和授信退款。
  • [ ] 专属额度返还一次。
  • [ ] 余额调增/调减。
  • [ ] 同 adjustNo 重复回调。
  • [ ] 提现成功/失败通知。

30.4 授信

  • [ ] 线上还款、自动还款、线下还款。
  • [ ] 本金/利息/滞纳金拆分。
  • [ ] 重复还款消息。
  • [ ] 到期/分期提醒。
  • [ ] 逾期锁站、延期解锁、10点门槛。

30.5 销售财务

  • [ ] 未收、部分收、全部收。
  • [ ] 零金额核销。
  • [ ] 对账和撤销对账。
  • [ ] 销退退款。
  • [ ] 64/32 分片关联。

31. 监控建议

指标维度告警
支付成功回调 NACKsourceOrderType/原因连续/重复状态突增
成功支付无 Paymentsid/业务单超短延迟仍缺失
Payment 无 Accountsid/paymentId任意新增
业务状态与财务不一致业务类型任意新增
金额单位异常业务单比例接近100倍
迟到支付sourceType状态12成功回调
重复财务明细stlId/accId超合法账户数
退款未完成afterSaleNo超 SLA
还款回调 NACKbillNo重复积压
逾期锁站部分失败sid单站失败也告警

32. 已确认与待确认

32.1 静态代码已确认

  • Payment/PaymentInfo 按 sid%32。
  • Account/AccountInfo 按 sid%16。
  • PoOrderPayInfo 和 RepaymentOrder 当前 Model 使用单表。
  • Payment 状态为 0/1/2/6。
  • 支付中心注册十类事件。
  • 成功支付要求 payStatus='02'。
  • 采购成功回调接受状态 10 和 12。
  • 外部支付金额主要为分,Payment/Info/Account 主要按元。
  • PoOrderPayInfo 主要保存原始分值。
  • Payment 总额和明细包含服务费。
  • 成功回调在一个事务中写财务、账户、支付快照和业务状态。
  • 采购支付后状态改为 1,秒杀来源转 locked 到 used。
  • 重复支付/还款当前多以异常/NACK 处理。
  • 普通退款财务不由 paycenterRefundResult 单独完整负责。
  • 余额调整用 adjustNo 幂等。
  • 授信还款单状态为 0/1/2。
  • 逾期锁站消息对列表内单站失败会继续处理。

32.2 环境待确认

  • 支付中心各事件投递保证、重试和死信策略。
  • 业务已完成的重复消息是否允许 ACK。
  • Payment/Account 线上唯一键和历史双写边界。
  • th_scm_account 归档策略。
  • 外部每个金额字段的正式单位合同,尤其分期到期元字段。
  • 普通退款财务单的当前生产创建入口。
  • 支付中心与 DGJ 对迟到支付的最终仲裁规则。
  • 混合支付、服务费和折扣的报表口径。
  • 全车件 QPayCenterNotify 与主回调的业务边界。
  • 财务报表 DWD/Hologres 同步 SLA。

33. 证据来源

主题文件
支付事件和主流程application/controllers/tasks/PayCenterNotify.php
支付状态application/KzData/Enums/PaymentEnums.php
账户类型application/KzData/Enums/AccountEnums.php
交易类型application/KzData/Enums/TransTypeEnums.php
采购状态application/KzData/Enums/PoOrderEnums.php
Payment Modelapplication/models/payment/PaymentModel.php
PaymentInfo Modelapplication/models/payment/PaymentInfoModel.php
采购支付快照application/models/orders/PoOrderPayInfoModel.php
Account 分片application/models/scm/ScmAccountSubModel.php
AccountInfo 分片application/models/scm/ScmAccountInfoSubModel.php
账户写入application/service/scm/AccountService.php
授信还款单application/models/payment/RepaymentOrderModel.php
销售核销application/service/scm/InvSaService.php、ReceiptService.php
秒杀支付application/Services/Activity/FlashSaleSer.php

34. 一页式执行结论

先固定外部支付号、sourceOrderType/sourceOrderNo 和 sid。
Payment/Info 查 sid%32,Account/Info 查 sid%16,采购支付快照和还款单另查单表。
支付中心和PoOrderPayInfo主要是分,Payment/Info/Account主要是元,比较前先统一单位。
支付成功主链要同时核对业务单、Payment、PaymentInfo、Account、PoOrderPayInfo和特殊来源。
采购状态10/12可收成功回调;状态12迟到支付必须检查此前释放副作用。
重复回调不能重复落账;已完成重复消息的ACK/NACK策略需要环境确认。
普通退款不要只看paycenterRefundResult,它当前重点处理专属授信额度返还。
修复必须以支付中心最终状态为仲裁,优先用幂等补偿和领域Service。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
付款/收款制单PC Payment/Receipt/采购销售入口来源单、账户、金额、支付方式财务/采购/销售 ControllerPayment/Receipt/Account Service财务单号 + 来源单号付款、收款、应收应付分录
支付中心成功DEST_PAYCENTER_NOTIFYpayOrderNo、来源单、状态、金额PayCenterNotifyPayment/业务 Service支付单号支付落账并推进采购/活动
快钱/其他通道QPayCenterNotify 等通道单号、状态、金额对应 Consumer通道适配/资金 Service外部通道单 + 本地支付单通道结果映射本地财务状态
退款/还款退款回调、RepaymentOrder 流程原支付单、退款/还款单、金额Consumer/财务入口Refund/Repayment Service原支付关系退款或授信额度返还一次完成

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 发起支付 | Controller/Provider | request_id、来源单、payOrderNo、金额 | 支付中心受理且本地关系保存 | 调用超时、金额单位/账户错误 | payOrderNo 串联请求与回调 | | 支付回调 | PayCenterNotify | message ID、payOrderNo、来源单、payStatus=02 | ACK 且本地支付只记一次 | 非 02、重复落账、迟到支付 | 支付关系查来源订单状态 | | 账户分录 | Account Service/Model | 财务单、账户 ID、前后余额 | 分录和余额同事务一致 | 余额不足、借贷不平、部分提交 | 分录号与主单/账户关联 | | 退款还款 | Refund/Repayment Consumer/Service | 原支付单、退款单、授信单 | 原支付与返还金额闭合 | 重复返还、找不到原支付 | 原支付单贯穿正反两笔资金 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
创建财务单Payment.php/Receipt.php + Service财务事务来源应付/应收、账户、金额SCM_PAYMENT*/收款及业务关系表status 初始化;金额和分摊明细 insert财务单、来源单、明细合计
外部受理支付 ProviderDB 外部边界待支付单和金额支付中心、本地通道关系本地待支付 -> 外部处理中;超时不等于失败payOrderNo、Provider 返回码
成功落账PayCenterNotify::paycenterPayResult回调事务回调成功、当前支付/业务状态SCM_PAYMENT、SCM_PAYMENT_INFO、SCM_PO_ORDER_PAYINFO 等首次 insert/confirmed;业务 10/12 -> 后续态;重复回调 0 增量payOrderNo、金额、状态、流水条数
账户核销AccountService账户事务余额、未核销金额SCM_ACCOUNT*、分录/关系balance old +/- n;已核销 +n前后余额 + 分录净额
退款/还款refund/repayment Service反向资金事务原支付成功事实、已退金额refund/repayment 表、账户/授信关系已退 +n;额度/余额按规则返还,最大不超过原额原支付、退款单、通道最终状态

子模块追踪:finance-pay-request 支付发起与受理

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
发起支付采购/财务入口创建支付请求request ID、来源单、sid、账户/金额application/controllers/scm/Payment.php -> application/service/scm/AccountService.php来源应付、支付方式、账户可用额、已有支付关系本地财务事务创建待支付主明细 none -> pending;支付中心调用在事务外request ID + sourceOrderNo + local/payOrderNo外部超时不能直接判失败;按 payOrderNo 查受理结果再重试
受理回查支付中心返回或查询结果payOrderNo、外部 code、金额application/controllers/tasks/PayCenterNotify.php本地待支付关系和外部订单状态查询阶段 DB 不变;受理仅表示处理中,不做成功落账payOrderNo + sourceOrderNo + provider code本地无关系但外部有单时先补关系,避免再次发起扣款

子模块追踪:finance-pay-success 支付成功回调

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
成功消费DEST_PAYCENTER_NOTIFY pay resultmessage ID、payOrderNo、payStatus=02、来源单application/controllers/tasks/PayCenterNotify.php本地支付状态、来源业务 10/12 等允许态、已有明细回调本地事务首次写 SCM_PAYMENT*、采购支付关系并推进业务态message ID + payOrderNo + sourceOrderNo非 02 不走成功;重复消息不得再次插入明细或改变余额
全景回查支付成功仍待支付payOrderNo、采购/秒杀/预订单号application/models/payment/PaymentModel.phpPayment/Info、Account、PoOrderPayInfo、来源状态查询只读 不写;金额统一单位后闭环payOrderNo + all source billNos外部成功本地缺失用幂等回调补偿,禁止单独改来源状态

子模块追踪:finance-repeat-late 重复回调与迟到支付

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
幂等判断同 payOrderNo 重复成功消息payOrderNo、sourceOrderNo、message IDapplication/controllers/tasks/PayCenterNotify.php已有 Payment/Info/业务状态和金额单消息本地事务命中已完成则 new -> new、新增明细 0multiple message IDs + same payOrderNo当前 ACK/NACK 生产策略需环境确认;业务幂等不依赖随机消息 ID
迟到支付采购已超时/关闭后收到成功payOrderNo、采购单、关闭/释放记录application/KzData/Enums/PoOrderEnums.php状态 12/关闭态、外部最终成功、库存/活动释放事实不直接落账或按代码受理后进入补偿决策;跨域变化分事务payOrderNo + billNo + close/release IDs先冻结自动反向动作,核实资金、采购、秒杀数量账后按规则补偿

子模块追踪:finance-zero-mixed 零元与混合支付

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
零元支付应付金额为 0 的订单确认sourceOrderNo、应付/优惠金额application/controllers/scm/InvPo.php -> application/service/scm/AccountService.php原价、优惠、应付和来源状态本地事务直接完成零元资金事实 pending -> paid,不应产生实际扣款request ID + sourceOrderNo + amounts金额不为零不得走零元分支;重复提交不新增 PaymentInfo
混合支付余额/授信/在线等账户组合payOrderNo、账户明细、各渠道金额application/KzData/Enums/AccountEnums.php各账户可用额、主支付类型和分摊合计财务本地事务写多条分摊,账户 balance/credit old -> new,合计等于应付payOrderNo + account IDs/types + amounts任一渠道失败按合同回滚/退款已成功部分;主类型不替代明细事实

子模块追踪:finance-preorder 预订单支付

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
预订单支付预订单发起并收到支付成功preorderNo、payOrderNo、sidapplication/controllers/tasks/PayCenterNotify.php预订单当前态、应付额、账户方向、关联采购回调本地事务写预订单支付关系并 status: wait_pay -> paid/convertedmessage ID + preorder/pay/po orderNos不套用普通采购账户方向;重复成功不得重复转采购
售后回查预订单取消或退款preorderNo、原支付/退款单application/models/payment/PaymentInfoModel.php原支付、已退额、售后状态和采购关系反向资金本地事务 refunded +n;查询关系只读preorderNo + original/refund payOrderNo已转采购需按责任边界处理;不同时退预订单和采购两次

子模块追踪:finance-exception 支付异常

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
异常消费支付中心 exception 事件message ID、payOrderNo、异常码、来源单application/controllers/tasks/PayCenterNotify.php当前支付/业务态、外部异常类型、是否已成功单消息本地事务记录异常 normal -> exception;已成功单不被异常消息回退message ID + payOrderNo + exception code未知异常保留原态并告警;乱序消息先以支付中心最终态仲裁
人工排查支付卡住、金额或状态不一致payOrderNo、sourceOrderNo、时间线application/models/payment/PaymentModel.php请求、受理、回调、Payment/Info/Account 六层事实查询只读 不写;定位缺失层后只补该层request/message IDs + provider/local timestamps禁止凭错误文案直接重扣;生产补偿需授权和前后快照

子模块追踪:finance-refund 退款与提现退款

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
退款结果普通退款/支付中心退款回调原 payOrderNo、refundNo、金额、message IDapplication/controllers/tasks/PayCenterNotify.php原成功支付、已退款额、业务关闭/退货事实退款本地事务 refunded: old -> old+n 并写关系;不超过原支付额message ID + original/refundNo + amount当前专属授信职责与普通退款覆盖需按事件代码确认;重复返还 0 增量
提现退款提现失败或退回结果withdrawNo、account ID、金额application/controllers/tasks/QPayCenterNotify.php原提现单、通道终态、账户分录反向账户本地事务 balance: old -> old+n、提现态到 refundedwithdraw/refundNo + account entry通道超时先查最终态;余额已返只补状态/关系,不再加款

子模块追踪:finance-credit 额度调整、授信与白条还款

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
额度调整支付中心额度调整或专属授信回调adjustmentNo、account ID、方向、金额application/controllers/tasks/PayCenterNotify.php -> application/service/scm/AccountService.php账户类型、当前额度、原业务和幂等键账户本地事务 credit: old +/- n 并写分录,单键一次message ID + adjustmentNo + account/source ID方向错或结果重复不得二次变化;原支付关系缺失进入人工核对
白条还款线上/自动/线下还款确认repaymentNo、授信单、金额、渠道application/models/payment/RepaymentOrderModel.php应还、已还、逾期状态和账户额度还款本地事务 repaid +n、订单 pending -> partial/paid、额度按规则恢复repaymentNo + credit/source orderNo线上回调与线下确认必须共享幂等事实,防止同一金额双还

子模块追踪:finance-overdue 到期提醒与逾期锁站

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
到期扫描定时任务扫描授信/白条到期记录task batch、sid、repaymentNo、dueAtapplication/models/payment/RepaymentOrderModel.php未还金额、到期时间、上次提醒和站点状态每单本地事务更新提醒/逾期标记 normal -> due/overdue;通知异步task + batch + sid/repaymentNo时区和调度配置需环境确认;已还清记录扫描零变化
锁站恢复逾期锁菜单;还清后恢复sid、逾期单集合、菜单规则application/service/scm/AccountService.php站点所有逾期事实和当前白名单/菜单状态权限本地事务 unlocked -> locked 或满足条件 locked -> unlockedsid + repaymentNos + rule/action不因单笔还款恢复仍有其他逾期的站点;权限发布失败只补配置同步

子模块追踪:finance-receipt 销售收款、对账与核销

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
收款核销财务对销售单制收款并核销sale/receipt billNo、账户、金额application/controllers/scm/Receipt.php -> application/service/scm/InvSaService.php销售应收、有效收款、已核/退款额和账户财务本地事务写收款分录,received/verified: old -> old+nrequest ID + sale/receipt billNo + transType超额、重复来源或账户非法零写入;金额单位先统一
对账回查对账单金额与销售核销不一致statement ID、销售/收款单、时间窗application/controllers/scm/AccCheck.php有效未软删交易、退款反向、分片和报表口径查询只读 不写;验证应收、已收、已核、未核等式statement ID + source/finance billNos报表延迟与业务不平分开处理;主账正确时仅补报表/对账任务