- DELIVERY_LINK_TTL_MINUTES 设为 0 或负数时链接永久有效(expires_at 存 NULL), 默认值由 120 改为 0,本地 .env 同步改为永久 - 新增 delivery_link_generated_at 列区分从未生成与永久链接,兼容历史数据 - 永久链接授权仅校验 HMAC 签名、不校验过期(exp=0),作废/恢复语义不变 - 前端链接有效期列对永久链接显示"永久有效"
321 lines
16 KiB
Go
321 lines
16 KiB
Go
package model
|
||
|
||
import (
|
||
"time"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
const (
|
||
MerchantStatusActive = "active"
|
||
MerchantStatusDisabled = "disabled"
|
||
|
||
// MerchantCodeSelfOperated 是平台自营商户编码,作为默认商品目录的模板来源。
|
||
MerchantCodeSelfOperated = "self-operated"
|
||
|
||
MerchantFeatureProducts = "products"
|
||
MerchantFeatureOrders = "orders"
|
||
MerchantFeatureWallet = "wallet"
|
||
MerchantFeatureAPI = "api"
|
||
MerchantFeatureCallbacks = "callbacks"
|
||
DefaultMerchantFeatures = "products,orders,wallet,api,callbacks"
|
||
|
||
MemberRoleOwner = "owner"
|
||
MemberRoleOperator = "operator"
|
||
MemberRoleFinance = "finance"
|
||
MemberRoleViewer = "viewer"
|
||
|
||
PermissionMembersManage = "members:manage"
|
||
PermissionProductsManage = "products:manage"
|
||
PermissionOrdersManage = "orders:manage"
|
||
PermissionWalletView = "wallet:view"
|
||
PermissionWalletLedger = "wallet:ledger"
|
||
PermissionRechargeManage = "recharge:manage"
|
||
PermissionAPIManage = "api:manage"
|
||
PermissionCallbacksManage = "callbacks:manage"
|
||
|
||
FeeTypeRate = "rate" // 手续费按百分比
|
||
FeeTypeFixed = "fixed" // 手续费按固定金额
|
||
|
||
APIClientStatusActive = "active"
|
||
APIClientStatusDisabled = "disabled"
|
||
|
||
ProductStatusActive = "active"
|
||
ProductStatusInactive = "inactive"
|
||
|
||
OrderStatusPaid = "paid" // 已扣款,待发货
|
||
OrderStatusDelivering = "delivering" // 发货中
|
||
OrderStatusDelivered = "delivered" // 已交付
|
||
OrderStatusShipFailed = "ship_failed" // 发货失败(可重试)
|
||
OrderStatusCancelled = "cancelled" // 已取消并退款
|
||
|
||
OrderSourceAPI = "api" // 商户开放接口创建
|
||
OrderSourceManual = "manual" // 商户后台人工创建
|
||
OrderSourceTest = "test" // 后台联调测试订单
|
||
|
||
WalletLedgerCredit = "credit"
|
||
WalletLedgerDebit = "debit"
|
||
WalletLedgerRefund = "refund"
|
||
WalletLedgerAdjust = "adjust"
|
||
|
||
CallbackStatusActive = "active"
|
||
CallbackStatusDisabled = "disabled"
|
||
|
||
CallbackDeliveryPending = "pending"
|
||
CallbackDeliverySending = "sending"
|
||
CallbackDeliveryDelivered = "delivered"
|
||
CallbackDeliveryFailed = "failed"
|
||
)
|
||
|
||
// Merchant 商户租户,是所有新业务数据的隔离边界。
|
||
type Merchant struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
Code string `gorm:"uniqueIndex;size:64;not null" json:"code"`
|
||
Name string `gorm:"size:128;not null" json:"name"`
|
||
Status string `gorm:"size:16;not null;default:active;index" json:"status"`
|
||
ContactName string `gorm:"size:64" json:"contact_name"`
|
||
ContactInfo string `gorm:"size:128" json:"contact_info"`
|
||
Features string `gorm:"type:text;not null;default:'products,orders,wallet,api,callbacks'" json:"features"`
|
||
|
||
// 手续费按"百分比或固定"二选一:fee_type=rate 用 fee_rate_bp,fee_type=fixed 用 fee_fixed_amount。
|
||
FeeType string `gorm:"size:16;not null;default:rate" json:"fee_type"`
|
||
FeeRateBP int64 `gorm:"not null;default:0" json:"fee_rate_bp"`
|
||
FeeFixedAmount int64 `gorm:"not null;default:0" json:"fee_fixed_amount"`
|
||
}
|
||
|
||
func (m *Merchant) BeforeCreate(tx *gorm.DB) error {
|
||
if m.Features == "" {
|
||
m.Features = DefaultMerchantFeatures
|
||
}
|
||
if m.FeeType == "" {
|
||
m.FeeType = FeeTypeRate
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// MerchantMember 将系统账号和商户权限分离,账号可以属于多个商户。
|
||
type MerchantMember struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
|
||
MerchantID uint `gorm:"not null;uniqueIndex:idx_merchant_member;index" json:"merchant_id"`
|
||
UserID uint `gorm:"not null;uniqueIndex:idx_merchant_member;index" json:"user_id"`
|
||
Role string `gorm:"size:32;not null;default:operator" json:"role"`
|
||
Status int `gorm:"not null;default:1" json:"status"`
|
||
IsDefault bool `gorm:"not null;default:false" json:"is_default"`
|
||
|
||
Merchant *Merchant `gorm:"foreignKey:MerchantID" json:"merchant,omitempty"`
|
||
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||
}
|
||
|
||
// MerchantRole 是商户内可配置的员工角色;负责人仍是系统保留角色。
|
||
type MerchantRole struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
MerchantID uint `gorm:"not null;uniqueIndex:idx_merchant_role_code;index" json:"merchant_id"`
|
||
Code string `gorm:"size:64;not null;uniqueIndex:idx_merchant_role_code" json:"code"`
|
||
Name string `gorm:"size:64;not null" json:"name"`
|
||
Permissions string `gorm:"type:text;not null;default:''" json:"permissions"`
|
||
Status int `gorm:"not null;default:1" json:"status"`
|
||
}
|
||
|
||
// APIClient 为一个商户的外部系统集成凭证。SecretCiphertext 仅保存加密后的密钥。
|
||
type APIClient struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
MerchantID uint `gorm:"not null;index" json:"merchant_id"`
|
||
Name string `gorm:"size:128;not null" json:"name"`
|
||
AppKey string `gorm:"uniqueIndex;size:96;not null" json:"app_key"`
|
||
SecretCiphertext string `gorm:"type:text;not null" json:"-"`
|
||
SignatureVersion string `gorm:"size:16;not null;default:v1" json:"signature_version"`
|
||
Scopes string `gorm:"type:text;not null" json:"scopes"`
|
||
Status string `gorm:"size:16;not null;default:active;index" json:"status"`
|
||
ExpiresAt *time.Time `json:"expires_at"`
|
||
LastUsedAt *time.Time `json:"last_used_at"`
|
||
|
||
Merchant *Merchant `gorm:"foreignKey:MerchantID" json:"merchant,omitempty"`
|
||
}
|
||
|
||
// Product 是平台通用商品目录;商品特性通过 Attributes 保存,避免把游戏、角色等字段写死。
|
||
type Product struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
Code string `gorm:"uniqueIndex;size:96;not null" json:"code"`
|
||
Name string `gorm:"size:160;not null" json:"name"`
|
||
Category string `gorm:"size:64;index" json:"category"`
|
||
Description string `gorm:"type:text" json:"description"`
|
||
Attributes string `gorm:"type:text" json:"attributes"`
|
||
Status string `gorm:"size:16;not null;default:active;index" json:"status"`
|
||
}
|
||
|
||
// MerchantProduct 是商户可售商品及其价格、库存和发货配置。
|
||
type MerchantProduct struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
MerchantID uint `gorm:"not null;uniqueIndex:idx_merchant_product_sku;index" json:"merchant_id"`
|
||
ProductID uint `gorm:"not null;index" json:"product_id"`
|
||
SKU string `gorm:"size:96;not null;uniqueIndex:idx_merchant_product_sku" json:"sku"`
|
||
DisplayName string `gorm:"size:160" json:"display_name"`
|
||
PriceAmount int64 `gorm:"not null;default:0" json:"price_amount"`
|
||
CostAmount int64 `gorm:"not null;default:0" json:"cost_amount"`
|
||
Currency string `gorm:"size:12;not null;default:POINT" json:"currency"`
|
||
Stock int64 `gorm:"not null;default:-1" json:"stock"`
|
||
Status string `gorm:"size:16;not null;default:active;index" json:"status"`
|
||
FulfillmentConfig string `gorm:"type:text" json:"fulfillment_config"`
|
||
|
||
Product *Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||
Merchant *Merchant `gorm:"foreignKey:MerchantID" json:"merchant,omitempty"`
|
||
}
|
||
|
||
// WalletAccount 以最小货币单位记录商户余额,绝不使用 float64 做账。
|
||
type WalletAccount struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
MerchantID uint `gorm:"not null;uniqueIndex" json:"merchant_id"`
|
||
Currency string `gorm:"size:12;not null;default:POINT" json:"currency"`
|
||
AvailableBalance int64 `gorm:"not null;default:0" json:"available_balance"`
|
||
FrozenBalance int64 `gorm:"not null;default:0" json:"frozen_balance"`
|
||
}
|
||
|
||
// WalletLedgerEntry 记录每一笔余额变化,BalanceAfter 方便审计与对账。
|
||
type WalletLedgerEntry struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
MerchantID uint `gorm:"not null;index;uniqueIndex:idx_wallet_ledger_idempotency" json:"merchant_id"`
|
||
WalletAccountID uint `gorm:"not null;index" json:"wallet_account_id"`
|
||
EntryNo string `gorm:"uniqueIndex;size:64;not null" json:"entry_no"`
|
||
Type string `gorm:"size:24;not null;index" json:"type"`
|
||
Amount int64 `gorm:"not null" json:"amount"`
|
||
BalanceAfter int64 `gorm:"not null" json:"balance_after"`
|
||
ReferenceType string `gorm:"size:32;not null" json:"reference_type"`
|
||
ReferenceNo string `gorm:"size:96;not null;index" json:"reference_no"`
|
||
IdempotencyKey *string `gorm:"uniqueIndex:idx_wallet_ledger_idempotency;size:128" json:"-"`
|
||
Note string `gorm:"size:255" json:"note"`
|
||
}
|
||
|
||
// FulfillmentOrder 保留订单状态、商品及请求快照。
|
||
type FulfillmentOrder struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
MerchantID uint `gorm:"not null;index;uniqueIndex:idx_order_client_no" json:"merchant_id"`
|
||
OrderNo string `gorm:"uniqueIndex;size:64;not null" json:"order_no"`
|
||
ClientOrderNo string `gorm:"size:96;not null;uniqueIndex:idx_order_client_no" json:"client_order_no"`
|
||
MerchantProductID uint `gorm:"not null;index" json:"merchant_product_id"`
|
||
ProductSKU string `gorm:"size:96;not null" json:"product_sku"`
|
||
ProductName string `gorm:"size:160;not null" json:"product_name"`
|
||
Quantity int64 `gorm:"not null;default:1" json:"quantity"`
|
||
BaseAmount int64 `gorm:"not null;default:0" json:"base_amount"`
|
||
FeeType string `gorm:"size:16;not null;default:rate" json:"fee_type"`
|
||
FeeRateBP int64 `gorm:"not null;default:0" json:"fee_rate_bp"`
|
||
FeeFixedAmount int64 `gorm:"not null;default:0" json:"fee_fixed_amount"`
|
||
ServiceFeeAmount int64 `gorm:"not null;default:0" json:"service_fee_amount"`
|
||
Amount int64 `gorm:"not null" json:"amount"`
|
||
Currency string `gorm:"size:12;not null;default:POINT" json:"currency"`
|
||
OrderStatus string `gorm:"size:16;not null;default:paid;index" json:"order_status"`
|
||
OrderSource string `gorm:"size:16;not null;default:api;index" json:"order_source"`
|
||
BuyerReference string `gorm:"size:128" json:"buyer_reference"`
|
||
RequestFingerprint string `gorm:"size:64;not null;default:'';index" json:"-"`
|
||
RequestData string `gorm:"type:text" json:"request_data"`
|
||
ResultData string `gorm:"type:text" json:"result_data"`
|
||
ProviderOrderNo string `gorm:"size:96;index" json:"provider_order_no"`
|
||
FailureReason string `gorm:"size:512" json:"failure_reason"`
|
||
CancelledAt *time.Time `json:"cancelled_at"`
|
||
DeliveredAt *time.Time `json:"delivered_at"`
|
||
DeliveryLinkExpiresAt *time.Time `json:"delivery_link_expires_at"`
|
||
DeliveryLinkRevokedAt *time.Time `json:"delivery_link_revoked_at"`
|
||
// DeliveryLinkGeneratedAt 记录发货链接的生成/恢复时间;expires_at 为 NULL 时表示永久有效。
|
||
DeliveryLinkGeneratedAt *time.Time `json:"delivery_link_generated_at"`
|
||
|
||
MerchantProduct *MerchantProduct `gorm:"foreignKey:MerchantProductID" json:"merchant_product,omitempty"`
|
||
}
|
||
|
||
// CallbackSubscription 为商户提供独立、可禁用的事件订阅。
|
||
type CallbackSubscription struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||
|
||
MerchantID uint `gorm:"not null;index" json:"merchant_id"`
|
||
Name string `gorm:"size:128;not null" json:"name"`
|
||
URL string `gorm:"size:1024;not null" json:"url"`
|
||
Events string `gorm:"type:text;not null" json:"events"`
|
||
SecretCiphertext string `gorm:"type:text;not null" json:"-"`
|
||
Status string `gorm:"size:16;not null;default:active;index" json:"status"`
|
||
}
|
||
|
||
// CallbackDelivery 是事务性 outbox 记录;事件 ID 可供接收方做幂等。
|
||
type CallbackDelivery struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
MerchantID uint `gorm:"not null;index" json:"merchant_id"`
|
||
CallbackSubscriptionID uint `gorm:"not null;index" json:"callback_subscription_id"`
|
||
EventID string `gorm:"uniqueIndex;size:64;not null" json:"event_id"`
|
||
Event string `gorm:"size:64;not null;index" json:"event"`
|
||
Payload string `gorm:"type:text;not null" json:"payload"`
|
||
Status string `gorm:"size:16;not null;default:pending;index" json:"status"`
|
||
Attempts int `gorm:"not null;default:0" json:"attempts"`
|
||
NextAttemptAt time.Time `gorm:"not null;index" json:"next_attempt_at"`
|
||
LastStatusCode int `gorm:"not null;default:0" json:"last_status_code"`
|
||
LastResponse string `gorm:"type:text" json:"last_response"`
|
||
DeliveredAt *time.Time `json:"delivered_at"`
|
||
|
||
CallbackSubscription *CallbackSubscription `gorm:"foreignKey:CallbackSubscriptionID" json:"callback_subscription,omitempty"`
|
||
}
|
||
|
||
// APIRequestNonce 使 API 重放保护跨进程、跨重启生效。
|
||
type APIRequestNonce struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
APIClientID uint `gorm:"not null;uniqueIndex:idx_api_nonce;index" json:"api_client_id"`
|
||
Nonce string `gorm:"size:96;not null;uniqueIndex:idx_api_nonce" json:"nonce"`
|
||
ExpiresAt time.Time `gorm:"not null;index" json:"expires_at"`
|
||
}
|
||
|
||
// SourceAPINonce 记录源头接口的请求 nonce,保证重放保护跨进程、跨重启生效。
|
||
type SourceAPINonce struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
APIKey string `gorm:"size:96;not null;uniqueIndex:idx_source_api_nonce" json:"-"`
|
||
Nonce string `gorm:"size:64;not null;uniqueIndex:idx_source_api_nonce" json:"-"`
|
||
ExpiresAt time.Time `gorm:"not null;index" json:"-"`
|
||
}
|
||
|
||
func (SourceAPINonce) TableName() string {
|
||
return "source_api_nonces"
|
||
}
|
||
|
||
// AuditLog 留存后台和开放接口的重要业务操作。
|
||
type AuditLog struct {
|
||
ID uint `gorm:"primarykey" json:"id"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
MerchantID *uint `gorm:"index" json:"merchant_id"`
|
||
ActorUserID *uint `gorm:"index" json:"actor_user_id"`
|
||
APIClientID *uint `gorm:"index" json:"api_client_id"`
|
||
RequestID string `gorm:"size:64;index" json:"request_id"`
|
||
Action string `gorm:"size:96;not null;index" json:"action"`
|
||
EntityType string `gorm:"size:64;not null;index" json:"entity_type"`
|
||
EntityID string `gorm:"size:96;not null;index" json:"entity_id"`
|
||
Metadata string `gorm:"type:text" json:"metadata"`
|
||
}
|