Files
affiliate_dash/backend/internal/model/merchant.go
T
yml2213 0de0ad9e9d 货币体系: 全平台统一使用积分(POINT)替代人民币(CNY)
后端:
- 模型 currency 默认值 CNY→POINT (MerchantProduct/WalletAccount/FulfillmentOrder)
- DashboardStats TotalSales/TotalFees 从 float64 改为 int64,去掉 /100.0 转换
- 上游 OpenOrderQuery.Amount 从 float64 改为 int64,直接返回积分整数
- 钱包/商品创建时 currency 默认 POINT

前端:
- 去掉 centsToYuan/yuanToCents 转换函数,金额直接用整数积分
- money() 显示从 ¥X.XX 改为 X 积分
- 商品售价/成本表单字段名改回 price_amount/cost_amount,precision 改 0
- 钱包调整表单 amount_yuan→amount,precision 改 0
- 手续费固定金额表单 precision 改 0,label 改积分
- 币种选项 CNY→POINT
- Dashboard 成交金额/手续费 suffix 元→积分,去掉 precision

数据库:
- 新增迁移 004: currency 默认值 CNY→POINT,存量数据更新
2026-07-30 14:30:34 +08:00

302 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import (
"time"
"gorm.io/gorm"
)
const (
MerchantStatusActive = "active"
MerchantStatusDisabled = "disabled"
MerchantFeatureProducts = "products"
MerchantFeatureOrders = "orders"
MerchantFeatureWallet = "wallet"
MerchantFeatureAPI = "api"
MerchantFeatureCallbacks = "callbacks"
DefaultMerchantFeatures = "products,orders,wallet,api,callbacks"
MemberRoleOwner = "owner"
MemberRoleOperator = "operator"
MemberRoleFinance = "finance"
MemberRoleViewer = "viewer"
FeeTypeRate = "rate" // 手续费按百分比
FeeTypeFixed = "fixed" // 手续费按固定金额
APIClientStatusActive = "active"
APIClientStatusDisabled = "disabled"
ProductStatusActive = "active"
ProductStatusInactive = "inactive"
PaymentStatusPending = "pending"
PaymentStatusPaid = "paid"
PaymentStatusRefunded = "refunded"
PaymentStatusCancelled = "cancelled"
FulfillmentStatusPending = "pending"
FulfillmentStatusProcessing = "processing"
FulfillmentStatusSucceeded = "succeeded"
FulfillmentStatusFailed = "failed"
FulfillmentStatusCancelled = "cancelled"
FulfillmentJobStatusPending = "pending"
FulfillmentJobStatusProcessing = "processing"
FulfillmentJobStatusSucceeded = "succeeded"
FulfillmentJobStatusFailed = "failed"
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_bpfee_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"`
}
// 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"`
PaymentStatus string `gorm:"size:16;not null;default:pending;index" json:"payment_status"`
FulfillmentStatus string `gorm:"size:16;not null;default:pending;index" json:"fulfillment_status"`
BuyerReference string `gorm:"size:128" json:"buyer_reference"`
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"`
MerchantProduct *MerchantProduct `gorm:"foreignKey:MerchantProductID" json:"merchant_product,omitempty"`
}
// FulfillmentJob 为后续履约器保留可重试的持久化任务边界。
type FulfillmentJob 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"`
OrderID uint `gorm:"not null;uniqueIndex" json:"order_id"`
Status string `gorm:"size:16;not null;default:pending;index" json:"status"`
Attempts int `gorm:"not null;default:0" json:"attempts"`
NextRunAt time.Time `gorm:"not null;index" json:"next_run_at"`
ProviderOrderNo string `gorm:"size:96;index" json:"provider_order_no"`
RequestPayload string `gorm:"type:text" json:"request_payload"`
ResultPayload string `gorm:"type:text" json:"result_payload"`
LastError string `gorm:"size:512" json:"last_error"`
}
// 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"`
}
// 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"`
}