2. 支付订单基础

支付订单是支付系统的核心业务实体,它承载了“谁在什么时间、用什么方式、为哪笔交易、支付了多少钱”这一完整语义。理解支付订单,不能只把它看作一张数据库表,而应把它视为连接交易意图与资金流转的枢纽对象。本节将从定义、业务属性、数据结构、单据关系以及典型场景五个维度展开,帮助大家建立对支付订单的结构化认知。

一、支付订单的定义与本质

支付订单(Payment Order)是支付系统为一次支付请求创建的、具有唯一标识的业务单据。它记录的是“支付动作”本身,而非交易内容或资金结算结果。其本质是一个状态可迁移、金额可校验、渠道可路由的持久化对象。

从系统视角看,支付订单承担三项核心职责:

  • 幂等控制:通过商户订单号与支付单号建立唯一约束,防止重复支付。
  • 状态追踪:记录从创建、支付中、成功、失败到关闭的完整生命周期。
  • 对账依据:作为与渠道流水、账单、退款单勾兑的基准单据。
支付订单不等于“支付成功记录”。它从创建那一刻就存在,即使最终失败或关闭,也仍然是有效业务数据,用于审计与排障。

二、核心业务属性与数据结构

一个典型的支付订单包含以下属性分组:

属性分组 关键字段 说明
标识信息 payment_id、merchant_order_no、channel_order_no 内部支付单号、商户订单号、渠道流水号,三者构成幂等与对账基础
金额信息 total_amount、pay_amount、currency、fee 订单金额、实付金额、币种、手续费,需区分标价与实收
主体信息 payer_id、payee_id、merchant_id 付款方、收款方、商户主体,支撑风控与分账
渠道信息 channel_code、pay_method、bank_card_id 支付渠道、支付方式、银行卡标识,决定路由与费率
状态信息 status、sub_status、expire_time 主状态、子状态、过期时间,驱动生命周期流转
时间信息 create_time、pay_time、finish_time 创建、支付、完成时间,用于时效分析与对账窗口

在代码层面,支付订单通常以聚合根形式存在,状态变更必须通过领域方法而非直接赋值:

public class PaymentOrder {
    private String paymentId;
    private String merchantOrderNo;
    private Long totalAmount;
    private Long payAmount;
    private String currency;
    private PaymentStatus status;
    private String channelCode;
    private LocalDateTime expireTime;

    public void markPaid(String channelOrderNo, LocalDateTime payTime) {
        if (this.status != PaymentStatus.PAYING) {
            throw new IllegalStateException("当前状态不允许支付成功");
        }
        this.status = PaymentStatus.SUCCESS;
        this.channelOrderNo = channelOrderNo;
        this.payTime = payTime;
    }
}

三、支付订单与交易、账单、退款单的关系

这四类单据容易混淆,必须从“业务意图—支付动作—资金结算—资金退回”四个层次区分:

  • 交易单(Trade Order):描述“买卖什么”,是业务意图的载体,一个交易单可对应多次支付尝试。
  • 支付订单(Payment Order):描述“怎么付”,一次支付请求对应一张支付单,是交易单的支付执行结果。
  • 账单(Bill/Settlement):描述“钱怎么结”,按周期或批次汇总支付成功流水,用于与渠道、商户结算。
  • 退款单(Refund Order):描述“钱怎么退”,必须关联原支付订单,金额不得超过原支付金额。

它们之间的数量关系通常是:一个交易单对应零到多个支付订单;一个支付订单最多对应一个成功账单明细;一个支付订单可对应零到多个退款单,但累计退款金额受原单约束。

支付订单与周边单据关系图 交易单 业务意图 / 商品 支付订单 支付动作 / 状态机 账单 结算 / 对账 退款单 资金退回 1 : N N : 1 1 : N

四、典型应用场景

支付订单在不同业务场景下呈现不同形态,理解场景有助于设计合理的字段与状态:

  1. 电商下单支付:交易单创建后生成支付订单,用户选择渠道完成支付,支付成功后触发发货。支付订单需支持超时关闭与库存释放。
  2. 充值场景:支付订单直接关联用户账户,支付成功后增加余额,通常无实物交易单,但需记录充值来源。
  3. 分账场景:一笔支付订单成功后,按规则拆分给多个收款方,支付订单需保留分账明细引用。
  4. 跨境支付:涉及多币种、汇率锁定、合规校验,支付订单需记录原始币种与结算币种。
  5. 退款场景:基于原支付订单发起退款,系统校验可退金额,生成退款单并驱动资金退回。
在分账与跨境场景中,支付订单的金额字段必须区分“订单金额”“实付金额”“可退金额”“已退金额”,否则极易在对账时出现差额。

五、常见误区与复盘要点

在实际项目中,支付订单设计常出现以下误区:

  • 误区一:用交易单代替支付订单。交易单关注业务,支付订单关注资金动作,混用会导致重复支付与对账困难。
  • 误区二:状态字段过于简单。只有“成功/失败”无法表达支付中、已关闭、部分退款等中间态,必须设计主状态与子状态。
  • 误区三:金额字段不区分维度。将订单金额与实付金额合并,导致优惠、手续费、退款计算错误。
  • 误区四:忽略幂等键设计。商户订单号与支付单号未建立唯一索引,重复请求会生成多张支付单。
复盘时重点检查:支付订单是否可独立追溯?状态迁移是否有日志?金额字段是否可支撑退款与对账?这三点决定了支付系统的健壮性。

综上,支付订单是支付系统的“心脏”,它向上承接交易意图,向下驱动资金流转,横向关联账单与退款。只有把定义、属性、关系与场景都梳理清楚,后续的状态机设计与生命周期管理才有坚实基础。