package model import "time" // ---- 状态枚举 ---- type OrderStatusV2 string // 注:v1 model.OrderStatus 已占用 OrderPending/OrderPaid/OrderRefunded 三个标识符, // 为免包内重名冲突,v2 状态枚举统一加 V2 后缀(与 AttemptStatus/RefundStatus 的独立命名空间不冲突,故那两组不加后缀)。 const ( OrderCreatedV2 OrderStatusV2 = "created" OrderPendingV2 OrderStatusV2 = "pending" OrderPaidV2 OrderStatusV2 = "paid" OrderCanceledV2 OrderStatusV2 = "canceled" OrderExpiredV2 OrderStatusV2 = "expired" OrderRefundingV2 OrderStatusV2 = "refunding" OrderPartRefundedV2 OrderStatusV2 = "partially_refunded" OrderRefundedV2 OrderStatusV2 = "refunded" ) type AttemptStatus string const ( AttemptCreated AttemptStatus = "created" AttemptPending AttemptStatus = "pending" AttemptPaid AttemptStatus = "paid" AttemptFailed AttemptStatus = "failed" AttemptExpired AttemptStatus = "expired" ) type RefundStatus string const ( RefundRequested RefundStatus = "requested" RefundProcessing RefundStatus = "processing" RefundSucceeded RefundStatus = "succeeded" RefundFailed RefundStatus = "failed" RefundManualPending RefundStatus = "manual_pending" // crypto 自托管:待运营人工 sweep 退款 ) // Settled 报告订单是否「已真正收到过钱」(paid 及其后的退款态)。webhook 投递门禁用: // 只有已结算订单的 outbox 才放行——payment 事件挡住崩溃窗口里的未付单,refund 事件 // 则因订单已 paid 过而正常放行(不会因订单转入退款态被卡)。 func (s OrderStatusV2) Settled() bool { switch s { case OrderPaidV2, OrderRefundingV2, OrderPartRefundedV2, OrderRefundedV2: return true } return false } // ---- 业务订单(购买账本)---- type OrderV2 struct { Base OutTradeNo string `gorm:"uniqueIndex;size:64;not null"` MerchantID uint64 `gorm:"index"` BizSystem string `gorm:"index;size:32"` // 接入方(pangolin/jiu),空=独立收款 BizRef string `gorm:"size:128"` // 接入方业务单号,回调原样带回 ProductID uint64 `gorm:"index"` BizCode string `gorm:"index;size:64"` // 套餐码副本(下单时从 product 复制),回调带回业务方按码映射权益(设计 §5) Subject string `gorm:"size:128"` // 金额:int64 最小单位 + 币种(presentment/charge/settlement 三态在 charge 落地此处) AmountMinor int64 `gorm:"not null"` Currency string `gorm:"size:16;not null"` Status OrderStatusV2 `gorm:"index;size:24;not null"` // 折扣(业务授权的折后价,记账留痕) BaseAmountMinor int64 `gorm:"default:0"` DiscountMinor int64 `gorm:"default:0"` CampaignID string `gorm:"size:64"` DiscountReason string `gorm:"size:128"` PaidAt *time.Time ExpiresAt *time.Time // 整体购买窗口 } // ---- 支付尝试(收款账本)---- type Attempt struct { Base OutTradeNo string `gorm:"index;size:64;not null"` Channel string `gorm:"index;size:32;not null;uniqueIndex:uq_attempt_provider"` // alipay/wechat/crypto/stripe/... AccountID string `gorm:"index;size:64"` // 选中的收款账户 Provider string `gorm:"size:32"` ProviderRef string `gorm:"size:128;uniqueIndex:uq_attempt_provider"` // 渠道单号 RenderType string `gorm:"size:24"` // redirect/qr/crypto_address/... AmountMinor int64 `gorm:"not null"` Currency string `gorm:"size:16;not null"` Status AttemptStatus `gorm:"index;size:16;not null"` ExpiresAt *time.Time // 本次尝试超时 PaidAt *time.Time } // ---- 收款账户(同一渠道多账户)---- type Account struct { Base AccountID string `gorm:"uniqueIndex;size:64;not null"` // 逻辑账户标识(配置里声明) Channel string `gorm:"index;size:32;not null"` Weight int `gorm:"default:1"` Enabled bool `gorm:"default:true"` DailyLimit int64 `gorm:"default:0"` // 0=不限 Region string `gorm:"size:16"` // cn/global/... Subject string `gorm:"size:128"` // 收款主体 // 凭证不落库明文:仅存 env 变量名引用,真值运行时从 env 取(见 Task 4) CredentialEnvPrefix string `gorm:"size:64"` } // ---- 退款子实体 ---- type Refund struct { Base RefundID string `gorm:"uniqueIndex;size:64;not null"` OutTradeNo string `gorm:"index;size:64;not null"` AttemptProviderRef string `gorm:"size:128"` // 针对哪笔支付,原路退 AmountMinor int64 `gorm:"not null"` Currency string `gorm:"size:16;not null"` Reason string `gorm:"size:256"` Status RefundStatus `gorm:"index;size:16;not null"` ProviderRefundRef string `gorm:"size:128"` InitiatedBy string `gorm:"size:16"` // business/platform CompletedAt *time.Time }