package entities import ( "time" "github.com/google/uuid" "gorm.io/gorm" ) // CustomerWallet holds a customer's EnakPoint and EnakCoin balances. The row is also // the lock every wallet operation for the customer takes first, so concurrent // operations on one customer queue up instead of spending the same balance twice. // // Balances are never written directly: they only move together with a ledger row, and // only through the wallet processor. type CustomerWallet struct { CustomerID uuid.UUID `gorm:"type:uuid;primary_key" json:"customer_id"` OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"` PointBalance int64 `gorm:"not null;default:0" json:"point_balance"` CoinBalance int64 `gorm:"not null;default:0" json:"coin_balance"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (CustomerWallet) TableName() string { return "customer_wallets" } // WalletTransaction is one ledger row. The ledger is append-only: a correction is a // new row pointing at the one it corrects, never an update. type WalletTransaction struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"` CustomerID uuid.UUID `gorm:"type:uuid;not null" json:"customer_id"` Currency string `gorm:"not null;size:10" json:"currency"` Type string `gorm:"not null;size:30" json:"type"` // Signed: positive credits the wallet, negative debits it. Amount int64 `gorm:"not null" json:"amount"` BalanceAfter int64 `gorm:"not null" json:"balance_after"` GroupID *uuid.UUID `gorm:"type:uuid" json:"group_id"` // Where the value came from (credit) or went to (debit). ReferenceType string `gorm:"not null;size:30" json:"reference_type"` ReferenceID uuid.UUID `gorm:"type:uuid;not null" json:"reference_id"` CounterpartyCustomerID *uuid.UUID `gorm:"type:uuid" json:"counterparty_customer_id"` ReversesTransactionID *uuid.UUID `gorm:"type:uuid" json:"reverses_transaction_id"` OutletID *uuid.UUID `gorm:"type:uuid" json:"outlet_id"` CreatedByUser *uuid.UUID `gorm:"type:uuid" json:"created_by_user"` Reason *string `gorm:"size:255" json:"reason"` // Frozen at creation, so later renames do not rewrite history. Description string `gorm:"not null;size:255" json:"description"` Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"` IdempotencyKey *string `gorm:"size:100;unique" json:"idempotency_key"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` } func (t *WalletTransaction) BeforeCreate(tx *gorm.DB) error { if t.ID == uuid.Nil { t.ID = uuid.New() } // A nil map would be stored as JSON null rather than an empty object. if t.Metadata == nil { t.Metadata = Metadata{} } return nil } func (WalletTransaction) TableName() string { return "wallet_transactions" } // WalletLot is one credited piece of balance with its own expiry (K9). Debits draw from // the lots that expire soonest. A lot created by a transfer, exchange or refund carries // the expiry of the lot it came from and points back at it through OriginLotID. type WalletLot struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"` CustomerID uuid.UUID `gorm:"type:uuid;not null" json:"customer_id"` Currency string `gorm:"not null;size:10" json:"currency"` SourceTransactionID uuid.UUID `gorm:"type:uuid;not null" json:"source_transaction_id"` OriginLotID *uuid.UUID `gorm:"type:uuid" json:"origin_lot_id"` OriginalAmount int64 `gorm:"not null" json:"original_amount"` // A cache of OriginalAmount minus the lot's allocations, and the only wallet column // that is ever updated. RemainingAmount int64 `gorm:"not null" json:"remaining_amount"` // Nil means the lot never expires. ExpiresAt *time.Time `json:"expires_at"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` } func (l *WalletLot) BeforeCreate(tx *gorm.DB) error { if l.ID == uuid.Nil { l.ID = uuid.New() } return nil } func (WalletLot) TableName() string { return "wallet_lots" } // WalletLotAllocation records how much a debit ledger row drew from one lot. type WalletLotAllocation struct { TransactionID uuid.UUID `gorm:"type:uuid;primary_key" json:"transaction_id"` LotID uuid.UUID `gorm:"type:uuid;primary_key" json:"lot_id"` Amount int64 `gorm:"not null" json:"amount"` } func (WalletLotAllocation) TableName() string { return "wallet_lot_allocations" }