支付领域模型设计:核心字段与关系
在支付中间件中,领域模型是业务逻辑的骨架。订单、支付单、退款单、账户四个模型构成了支付系统的核心数据实体。理解它们的字段定义与关联关系,是设计高内聚、低耦合支付系统的前提。
1. 订单模型(Order)
订单是交易发起的源头,代表用户购买商品或服务的业务凭证。在支付中间件中,订单模型主要承载业务上下文,不直接处理资金流转。
| 字段 | 类型 | 说明 |
|---|---|---|
| order_id | string | 全局唯一订单号,业务方生成 |
| merchant_id | string | 商户标识,区分不同业务线 |
| subject | string | 订单标题,用于展示 |
| total_amount | int64 | 订单总金额,单位:分 |
| currency | string | 币种,如 CNY、USD |
| status | int | 订单状态:待支付、已支付、已关闭、已退款 |
| description | string | 订单描述,可选 |
| created_at | int64 | 创建时间戳 |
| updated_at | int64 | 最后更新时间戳 |
设计要点:订单模型应保持轻量,不存储支付渠道、支付凭证等支付细节。订单与支付单通过
order_id 关联,实现业务与支付的解耦。
2. 支付单模型(Payment)
支付单是支付中间件的核心实体,记录一次支付请求的完整生命周期。它包含支付渠道、支付方式、支付金额、支付状态等关键信息。
| 字段 | 类型 | 说明 |
|---|---|---|
| payment_id | string | 支付单号,中间件生成 |
| order_id | string | 关联的订单号 |
| channel | string | 支付渠道:wechat、alipay、unionpay |
| pay_method | string | 支付方式:扫码、JSAPI、APP、H5 |
| pay_amount | int64 | 实际支付金额,单位:分 |
| currency | string | 币种 |
| status | int | 支付状态:待支付、支付中、成功、失败、关闭 |
| channel_trade_no | string | 渠道交易号,渠道返回 |
| channel_notify_time | int64 | 渠道异步通知时间 |
| expire_at | int64 | 支付过期时间 |
| created_at | int64 | 创建时间 |
| updated_at | int64 | 最后更新时间 |
注意:支付单的
pay_amount 可能与订单的 total_amount 不一致,例如部分支付、优惠减免场景。支付中间件应支持金额校验与容差处理。
3. 退款单模型(Refund)
退款单记录一次退款操作的完整信息,包括退款金额、退款原因、退款状态等。退款单与支付单通过 payment_id 关联,支持多次部分退款。
| 字段 | 类型 | 说明 |
|---|---|---|
| refund_id | string | 退款单号,中间件生成 |
| payment_id | string | 关联的支付单号 |
| order_id | string | 关联的订单号,冗余字段 |
| refund_amount | int64 | 退款金额,单位:分 |
| currency | string | 币种 |
| reason | string | 退款原因 |
| status | int | 退款状态:待处理、处理中、成功、失败、驳回 |
| channel_refund_no | string | 渠道退款单号 |
| channel_notify_time | int64 | 渠道退款通知时间 |
| created_at | int64 | 创建时间 |
| updated_at | int64 | 最后更新时间 |
关键约束:退款金额不能超过支付单的剩余可退金额。中间件需维护支付单的
refunded_amount 累计字段,用于实时校验。
4. 账户模型(Account)
账户模型记录商户或用户在支付平台中的资金余额与流水。它是支付清结算的基础,通常包含账户余额、冻结金额、可用余额等字段。
| 字段 | 类型 | 说明 |
|---|---|---|
| account_id | string | 账户号,全局唯一 |
| account_type | int | 账户类型:商户账户、用户账户、平台账户 |
| currency | string | 币种 |
| balance | int64 | 账户总余额,单位:分 |
| frozen_amount | int64 | 冻结金额,不可用 |
| available_balance | int64 | 可用余额 = balance - frozen_amount |
| status | int | 账户状态:正常、冻结、注销 |
| version | int | 乐观锁版本号,用于并发控制 |
| created_at | int64 | 创建时间 |
| updated_at | int64 | 最后更新时间 |
并发安全:账户余额更新必须使用乐观锁(
version 字段)或数据库行锁,防止并发扣款导致超扣。
5. 模型关系总览
四个模型通过外键形成清晰的层级关系:订单 → 支付单 → 退款单,账户独立管理资金。下图展示了核心实体间的关联与数据流向。
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 - ?而不加锁,高并发下出现超扣。 - 状态枚举不完整:支付单缺少“支付中”状态,导致渠道异步通知期间系统状态不一致。