4、核心数据模型设计:账单、支付、退款、对账等核心实体的数据模型设计,包括状态机与字段约束

在BMS(Billing Management System,计费管理系统)中,数据模型是系统的骨架。账单、支付、退款、对账这四个核心实体,不仅承载着资金流转的关键信息,更是财务合规、审计追溯和系统稳定性的基石。一个设计不良的数据模型,往往会导致状态混乱、对账不平、资金差错甚至法律风险。本章将深入剖析这四个核心实体的数据模型设计要点,重点讲解状态机的严谨定义与字段约束的落地实践,帮助学员建立一套高可用、高一致性的数据建模思维。

BMS核心数据模型关系与状态流转总览 账单 (Bill) · bill_id (PK) · user_id / order_id · amount / due_date · status: PENDING → PAID → OVERDUE → CANCELLED → CLOSED 约束: amount > 0, 唯一索引 支付 (Payment) · payment_id (PK) · bill_id (FK) · channel / txn_no · status: INIT → PROCESSING → SUCCESS → FAILED → CLOSED 约束: 金额一致, 幂等键 退款 (Refund) · refund_id (PK) · payment_id (FK) · amount / reason · status: REQUESTED → APPROVED → PROCESSING → SUCCESS → REJECTED 约束: 累计退款 ≤ 支付金额 对账 (Reconciliation) · recon_id (PK) · date / channel · status: MATCHED → MISMATCH → ADJUSTED 约束: 对账批次唯一 1:N 1:N N:1 状态机核心原则 1. 状态单向流转,禁止跳级 2. 每个状态变更记录审计日志 3. 终态不可逆(SUCCESS/CLOSED) 4. 超时/异常需有兜底状态 字段约束要点 · 金额: DECIMAL(12,2), 非负 · 时间: DATETIME(3), UTC存储 · 幂等: 唯一业务键组合 · 版本号: 乐观锁控制并发

4.1 账单实体:业务发生的源头

核心原理:账单(Bill)是BMS中一切资金动作的起点,它记录了用户因使用产品或服务而产生的应付费用。账单模型的设计核心在于“快照”与“可变”的平衡——账单生成后,其金额、周期等核心属性应尽量保持稳定,而状态则随着支付、退款等动作发生流转。账单通常包含账单号、用户ID、订单ID、账单周期、应付金额、已付金额、账单状态、生成时间、到期时间等字段。其中,bill_id作为全局唯一主键,建议使用雪花算法或UUID生成,避免使用自增ID以防止数据迁移和合并时的冲突。

判断方法或操作流程:在设计账单状态机时,需要明确状态的可达矩阵。一个典型的账单状态机包含:PENDING(待支付)、PAID(已支付)、OVERDUE(已逾期)、CANCELLED(已取消)、CLOSED(已关闭)。状态流转规则如下:

  • PENDING → PAID:用户完成全额支付。
  • PENDING → OVERDUE:超过到期日且未支付,由定时任务驱动。
  • PENDING / OVERDUE → CANCELLED:管理员手动取消或系统自动取消(如订单关闭)。
  • PAID → CLOSED:账单完成结算,进入归档状态。

禁止的状态跳转包括:PAID 回退到 PENDING,CANCELLED 重新激活等。这些规则必须在服务层通过状态机引擎强制校验,而非仅依赖数据库约束。

实战场景:某云服务商的账单生成后,用户发起部分支付(如使用优惠券抵扣)。此时账单的paid_amount字段更新,但status仍为PENDING,直到paid_amount >= amount时才触发状态迁移至PAID。这种设计避免了为“部分支付”单独创建状态,简化了状态机复杂度。
字段名类型约束说明
bill_idBIGINTPK, 非空全局唯一账单号
user_idBIGINT索引, 非空所属用户
order_idVARCHAR(64)可空关联订单号
amountDECIMAL(12,2)非空, > 0账单总额
paid_amountDECIMAL(12,2)默认 0已支付金额
statusVARCHAR(20)非空状态机字段
versionINT默认 0乐观锁版本号
风险提示:账单金额字段严禁使用FLOAT/DOUBLE类型,必须使用DECIMAL以避免浮点精度丢失导致的资金差错。同时,amount与paid_amount之间应通过数据库CHECK约束保证paid_amount <= amount。

4.2 支付实体:资金流动的通道

核心原理:支付(Payment)实体记录了用户为清偿账单而发起的每一笔资金操作。支付模型的设计核心在于“幂等性”与“可追溯性”。由于网络超时、重试等原因,同一笔支付请求可能被提交多次,因此必须通过biz_id(业务幂等键)保证唯一。支付状态机通常包含:INIT(初始化)、PROCESSING(处理中)、SUCCESS(成功)、FAILED(失败)、CLOSED(关闭)。其中,PROCESSING是中间态,系统需要配合支付渠道的回调或主动查询来推进状态。

判断方法或操作流程:支付状态流转的关键在于“最终一致性”。当用户发起支付时,系统创建INIT状态的支付单,并调用第三方支付渠道。渠道返回受理成功后,状态置为PROCESSING。随后,系统通过异步通知或定时轮询获取支付结果:

  1. 若渠道明确返回成功,则更新为SUCCESS,并触发账单的PAID动作。
  2. 若渠道明确返回失败或超时关闭,则更新为FAILED,允许用户重新发起支付。
  3. 若长时间处于PROCESSING(如超过30分钟),需启动对账任务与渠道核实,避免资金悬挂。

支付单必须记录渠道流水号(channel_txn_no)、支付渠道、支付金额、支付时间等关键字段,用于后续对账与审计。

-- 支付表核心DDL示例
CREATE TABLE payment (
    payment_id      BIGINT PRIMARY KEY,
    bill_id         BIGINT NOT NULL,
    biz_id          VARCHAR(64) NOT NULL UNIQUE, -- 幂等键
    channel         VARCHAR(32) NOT NULL,
    channel_txn_no  VARCHAR(64),
    amount          DECIMAL(12,2) NOT NULL,
    status          VARCHAR(20) NOT NULL,
    created_at      DATETIME(3) NOT NULL,
    updated_at      DATETIME(3) NOT NULL,
    version         INT DEFAULT 0,
    CONSTRAINT fk_payment_bill FOREIGN KEY (bill_id) REFERENCES bill(bill_id),
    CONSTRAINT chk_payment_amount CHECK (amount > 0)
);
经验提醒:支付回调处理必须实现幂等。即使收到渠道的重复回调,也只能成功更新一次状态。建议在更新SUCCESS时使用UPDATE payment SET status='SUCCESS', version=version+1 WHERE payment_id=? AND status IN ('INIT','PROCESSING'),通过受影响行数判断是否重复处理。

4.3 退款实体:逆向资金流的控制

核心原理:退款(Refund)是支付的逆操作,其数据模型设计比支付更为严格,因为涉及资金安全与风控。退款单必须关联原始的支付单,且退款总额不能超过支付净额(支付金额减去已退款金额)。退款状态机包含:REQUESTED(已申请)、APPROVED(已审批)、PROCESSING(处理中)、SUCCESS(成功)、REJECTED(已拒绝)。引入APPROVED中间态是为了满足财务人工审核或风控自动审核的场景。

判断方法或操作流程:退款流程通常由用户发起或系统触发(如订单取消)。操作流程如下:

  • 创建退款单,状态为REQUESTED,记录退款原因、退款金额。
  • 系统校验退款金额合法性(累计退款 ≤ 支付金额 - 已退款金额)。
  • 风控审核通过后,状态置为APPROVED,调用支付渠道退款接口。
  • 渠道返回受理后,状态置为PROCESSING,等待渠道最终结果。
  • 退款成功则置为SUCCESS,并更新原支付单的已退款金额;失败则置为REJECTED,允许人工介入。

退款单需要额外记录refund_reason、operator_id(操作人)、approval_note(审批备注)等字段,以满足合规审计要求。

案例拆解:某电商平台发生一笔100元的支付,用户随后申请全额退款。系统创建退款单(金额100元),状态REQUESTED。风控规则引擎检测到该用户历史退款率异常,自动将状态置为REJECTED,并通知客服人工审核。客服核实后手动将状态改为APPROVED并执行退款。此案例展示了状态机中人工干预节点的必要性,以及REJECTED状态并非终态,可通过人工操作重新进入流程。
状态允许操作目标状态触发条件
REQUESTED风控审核APPROVED / REJECTED自动规则或人工
APPROVED调用渠道PROCESSING退款接口受理
PROCESSING渠道回调SUCCESS / REJECTED渠道明确结果
REJECTED人工重试APPROVED管理员操作

4.4 对账实体:系统自愈的保障

核心原理:对账(Reconciliation)是BMS中保障资金一致性的最后一道防线。对账实体记录系统账单与支付渠道流水之间的核对结果。对账模型的核心在于“批次”与“差异处理”。对账批次通常按日期和渠道维度生成,包含对账日期、渠道、系统总金额、渠道总金额、差异金额、对账状态等字段。对账状态机相对简单:IN_PROGRESS(对账中)、MATCHED(一致)、MISMATCH(不一致)、ADJUSTED(已调整)。

判断方法或操作流程:对账流程通常由定时任务触发,核心步骤如下:

  1. 拉取渠道对账单文件,解析为渠道流水列表。
  2. 从本地数据库查询同一时间范围内的支付与退款流水。
  3. 以channel_txn_no为关联键进行匹配,找出差异记录(本地有渠道无、渠道有本地无、金额不一致)。
  4. 生成差异报告,状态置为MISMATCH。
  5. 财务人员根据差异报告进行调账处理(如补单、冲正),完成后状态置为ADJUSTED。

对账表需要存储原始对账文件的MD5值,防止重复导入。同时,差异记录需要关联到具体的支付单或退款单,便于追踪。

风险说明:对账差异处理必须谨慎。对于“渠道有而本地无”的流水,不能直接补单,需要先确认是否为测试数据或渠道故障。对于“本地有而渠道无”的流水,需要检查支付状态是否为SUCCESS,若渠道最终未结算,应启动自动冲正流程。
总结复盘:核心数据模型的设计应遵循“高内聚、低耦合”原则。账单、支付、退款、对账四个实体通过外键关联,但状态机相互独立。字段约束是数据质量的基石,建议在数据库层面同时使用NOT NULL、CHECK、UNIQUE和FOREIGN KEY四种约束。此外,所有金额字段使用DECIMAL(12,2),所有时间字段使用DATETIME(3)并统一存储UTC时间,展示层再转换为本地时区。通过严谨的状态机定义和字段约束,BMS才能在高并发、高资金流动的场景下保持数据的一致性与可审计性。