支付领域模型设计:核心字段与关系

在支付中间件中,领域模型是业务逻辑的骨架。订单、支付单、退款单、账户四个模型构成了支付系统的核心数据实体。理解它们的字段定义与关联关系,是设计高内聚、低耦合支付系统的前提。

1. 订单模型(Order)

订单是交易发起的源头,代表用户购买商品或服务的业务凭证。在支付中间件中,订单模型主要承载业务上下文,不直接处理资金流转。

字段类型说明
order_idstring全局唯一订单号,业务方生成
merchant_idstring商户标识,区分不同业务线
subjectstring订单标题,用于展示
total_amountint64订单总金额,单位:分
currencystring币种,如 CNY、USD
statusint订单状态:待支付、已支付、已关闭、已退款
descriptionstring订单描述,可选
created_atint64创建时间戳
updated_atint64最后更新时间戳
设计要点:订单模型应保持轻量,不存储支付渠道、支付凭证等支付细节。订单与支付单通过 order_id 关联,实现业务与支付的解耦。

2. 支付单模型(Payment)

支付单是支付中间件的核心实体,记录一次支付请求的完整生命周期。它包含支付渠道、支付方式、支付金额、支付状态等关键信息。

字段类型说明
payment_idstring支付单号,中间件生成
order_idstring关联的订单号
channelstring支付渠道:wechat、alipay、unionpay
pay_methodstring支付方式:扫码、JSAPI、APP、H5
pay_amountint64实际支付金额,单位:分
currencystring币种
statusint支付状态:待支付、支付中、成功、失败、关闭
channel_trade_nostring渠道交易号,渠道返回
channel_notify_timeint64渠道异步通知时间
expire_atint64支付过期时间
created_atint64创建时间
updated_atint64最后更新时间
注意:支付单的 pay_amount 可能与订单的 total_amount 不一致,例如部分支付、优惠减免场景。支付中间件应支持金额校验与容差处理。

3. 退款单模型(Refund)

退款单记录一次退款操作的完整信息,包括退款金额、退款原因、退款状态等。退款单与支付单通过 payment_id 关联,支持多次部分退款。

字段类型说明
refund_idstring退款单号,中间件生成
payment_idstring关联的支付单号
order_idstring关联的订单号,冗余字段
refund_amountint64退款金额,单位:分
currencystring币种
reasonstring退款原因
statusint退款状态:待处理、处理中、成功、失败、驳回
channel_refund_nostring渠道退款单号
channel_notify_timeint64渠道退款通知时间
created_atint64创建时间
updated_atint64最后更新时间
关键约束:退款金额不能超过支付单的剩余可退金额。中间件需维护支付单的 refunded_amount 累计字段,用于实时校验。

4. 账户模型(Account)

账户模型记录商户或用户在支付平台中的资金余额与流水。它是支付清结算的基础,通常包含账户余额、冻结金额、可用余额等字段。

字段类型说明
account_idstring账户号,全局唯一
account_typeint账户类型:商户账户、用户账户、平台账户
currencystring币种
balanceint64账户总余额,单位:分
frozen_amountint64冻结金额,不可用
available_balanceint64可用余额 = balance - frozen_amount
statusint账户状态:正常、冻结、注销
versionint乐观锁版本号,用于并发控制
created_atint64创建时间
updated_atint64最后更新时间
并发安全:账户余额更新必须使用乐观锁(version 字段)或数据库行锁,防止并发扣款导致超扣。

5. 模型关系总览

四个模型通过外键形成清晰的层级关系:订单 → 支付单 → 退款单,账户独立管理资金。下图展示了核心实体间的关联与数据流向。

支付领域模型关系图 支付领域模型核心关系 订单模型 order_id (PK) merchant_id, total_amount 支付单模型 payment_id (PK) order_id (FK), channel 退款单模型 refund_id (PK) payment_id (FK), order_id 账户模型 account_id (PK) balance, frozen_amount 1:N 1:N 资金变动 关系说明 • 订单与支付单:1:N,一个订单可对应多个支付单(如分次支付、支付失败重试) • 支付单与退款单:1:N,一个支付单可多次部分退款 • 支付单与账户:支付成功时增加商户账户余额,退款成功时扣减

6. 模型间关键约束

  • 金额一致性:支付单的 pay_amount 不能超过订单的 total_amount;退款单的 refund_amount 不能超过支付单的剩余可退金额。
  • 状态流转约束:订单状态为“已支付”时才能发起退款;支付单状态为“成功”时才能创建退款单。
  • 幂等性:同一 order_id 重复创建支付单应返回已有支付单,避免重复支付。
  • 账户余额非负:账户的 available_balance 在任何时刻不得小于 0,否则触发风控告警。

7. 模型关系在 Go 中的实现示例

// Order 订单模型
type Order struct {
    OrderID     string `gorm:"primaryKey;size:32"`
    MerchantID  string `gorm:"size:16;not null;index"`
    Subject     string `gorm:"size:128;not null"`
    TotalAmount int64  `gorm:"not null"`
    Currency    string `gorm:"size:3;default:CNY"`
    Status      int    `gorm:"default:0"`
    CreatedAt   int64  `gorm:"autoCreateTime"`
    UpdatedAt   int64  `gorm:"autoUpdateTime"`
}

// Payment 支付单模型
type Payment struct {
    PaymentID        string `gorm:"primaryKey;size:32"`
    OrderID          string `gorm:"size:32;not null;index"`
    Channel          string `gorm:"size:16;not null"`
    PayMethod        string `gorm:"size:16"`
    PayAmount        int64  `gorm:"not null"`
    Currency         string `gorm:"size:3;default:CNY"`
    Status           int    `gorm:"default:0"`
    ChannelTradeNo   string `gorm:"size:64"`
    ChannelNotifyTime int64
    ExpireAt         int64
    CreatedAt        int64  `gorm:"autoCreateTime"`
    UpdatedAt        int64  `gorm:"autoUpdateTime"`
}

// Refund 退款单模型
type Refund struct {
    RefundID         string `gorm:"primaryKey;size:32"`
    PaymentID        string `gorm:"size:32;not null;index"`
    OrderID          string `gorm:"size:32;index"`
    RefundAmount     int64  `gorm:"not null"`
    Currency         string `gorm:"size:3;default:CNY"`
    Reason           string `gorm:"size:256"`
    Status           int    `gorm:"default:0"`
    ChannelRefundNo  string `gorm:"size:64"`
    CreatedAt        int64  `gorm:"autoCreateTime"`
    UpdatedAt        int64  `gorm:"autoUpdateTime"`
}

// Account 账户模型
type Account struct {
    AccountID       string `gorm:"primaryKey;size:32"`
    AccountType     int    `gorm:"not null"`
    Currency        string `gorm:"size:3;default:CNY"`
    Balance         int64  `gorm:"not null;default:0"`
    FrozenAmount    int64  `gorm:"not null;default:0"`
    AvailableBalance int64 `gorm:"-:all"` // 计算字段,不持久化
    Status          int    `gorm:"default:0"`
    Version         int    `gorm:"default:0"`
    CreatedAt       int64  `gorm:"autoCreateTime"`
    UpdatedAt       int64  `gorm:"autoUpdateTime"`
}
设计建议:在 Go 中,建议使用 GORM 的 Preload 或 Joins 进行关联查询,避免 N+1 问题。账户模型的 Version 字段用于乐观锁,更新时需校验 WHERE version = ?。

8. 模型设计的常见误区

  • 订单与支付单混为一谈:将支付渠道、支付凭证直接放在订单表中,导致订单表字段膨胀,业务与支付耦合。
  • 退款单缺少支付单关联:退款单只关联订单号,不关联支付单号,导致多次支付场景下无法确定退款来源。
  • 账户余额无并发控制:直接使用 UPDATE account SET balance = balance - ? 而不加锁,高并发下出现超扣。
  • 状态枚举不完整:支付单缺少“支付中”状态,导致渠道异步通知期间系统状态不一致。