veza/veza-backend-api/internal/core/subscription/models.go
senke a1000ce7fb style(backend): gofmt -w on 85 files (whitespace only)
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.

The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
2026-04-14 12:22:14 +02:00

150 lines
6.3 KiB
Go

package subscription
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// PlanName represents a subscription plan type
type PlanName string
const (
PlanFree PlanName = "free"
PlanCreator PlanName = "creator"
PlanPremium PlanName = "premium"
)
// SubscriptionStatus represents the status of a user subscription
type SubscriptionStatus string
const (
StatusActive SubscriptionStatus = "active"
StatusTrialing SubscriptionStatus = "trialing"
StatusCanceled SubscriptionStatus = "canceled"
StatusPastDue SubscriptionStatus = "past_due"
StatusExpired SubscriptionStatus = "expired"
)
// BillingCycle represents the billing frequency
type BillingCycle string
const (
BillingMonthly BillingCycle = "monthly"
BillingYearly BillingCycle = "yearly"
)
// InvoiceStatus represents the status of an invoice
type InvoiceStatus string
const (
InvoicePending InvoiceStatus = "pending"
InvoicePaid InvoiceStatus = "paid"
InvoiceFailed InvoiceStatus = "failed"
InvoiceRefunded InvoiceStatus = "refunded"
)
// Plan represents a subscription plan definition
type Plan struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
Name PlanName `gorm:"not null;uniqueIndex;size:50" json:"name"`
DisplayName string `gorm:"not null;size:100" json:"display_name"`
Description string `gorm:"type:text" json:"description"`
PriceMonthly int `gorm:"column:price_monthly_cents;not null;default:0" json:"price_monthly_cents"`
PriceYearly int `gorm:"column:price_yearly_cents;not null;default:0" json:"price_yearly_cents"`
Currency string `gorm:"size:3;default:'USD'" json:"currency"`
UploadLimitMonthly *int `gorm:"column:upload_limit_monthly" json:"upload_limit_monthly"`
StorageLimitBytes int64 `gorm:"not null;default:0" json:"storage_limit_bytes"`
MarketplaceCommission float64 `gorm:"column:marketplace_commission_rate;type:numeric(5,4);default:0" json:"marketplace_commission_rate"`
CanSellOnMarketplace bool `gorm:"not null;default:false" json:"can_sell_on_marketplace"`
HasPrioritySupport bool `gorm:"not null;default:false" json:"has_priority_support"`
HasCollaborationTools bool `gorm:"not null;default:false" json:"has_collaboration_tools"`
HasDistribution bool `gorm:"not null;default:false" json:"has_distribution"`
TrialDays int `gorm:"not null;default:0" json:"trial_days"`
IsActive bool `gorm:"not null;default:true" json:"is_active"`
SortOrder int `gorm:"not null;default:0" json:"sort_order"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (Plan) TableName() string {
return "subscription_plans"
}
func (p *Plan) BeforeCreate(tx *gorm.DB) error {
if p.ID == uuid.Nil {
p.ID = uuid.New()
}
return nil
}
// UserSubscription represents a user's active or past subscription
type UserSubscription struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
PlanID uuid.UUID `gorm:"type:uuid;not null" json:"plan_id"`
Status SubscriptionStatus `gorm:"not null;size:30;default:'active'" json:"status"`
BillingCycle BillingCycle `gorm:"not null;size:10;default:'monthly'" json:"billing_cycle"`
CurrentPeriodStart time.Time `gorm:"not null" json:"current_period_start"`
CurrentPeriodEnd time.Time `gorm:"not null" json:"current_period_end"`
TrialStart *time.Time `json:"trial_start,omitempty"`
TrialEnd *time.Time `json:"trial_end,omitempty"`
CanceledAt *time.Time `json:"canceled_at,omitempty"`
CancelAtPeriodEnd bool `gorm:"not null;default:false" json:"cancel_at_period_end"`
HyperswitchSubscriptionID string `gorm:"size:255" json:"hyperswitch_subscription_id,omitempty"`
HyperswitchCustomerID string `gorm:"size:255" json:"hyperswitch_customer_id,omitempty"`
PaymentMethodID string `gorm:"size:255" json:"payment_method_id,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
// Relations (loaded via Preload)
Plan Plan `gorm:"foreignKey:PlanID" json:"plan,omitempty"`
}
func (UserSubscription) TableName() string {
return "user_subscriptions"
}
func (s *UserSubscription) BeforeCreate(tx *gorm.DB) error {
if s.ID == uuid.Nil {
s.ID = uuid.New()
}
return nil
}
// IsTrialing returns true if the subscription is in trial period
func (s *UserSubscription) IsTrialing() bool {
return s.Status == StatusTrialing && s.TrialEnd != nil && time.Now().Before(*s.TrialEnd)
}
// IsActiveOrTrialing returns true if the subscription grants access
func (s *UserSubscription) IsActiveOrTrialing() bool {
return s.Status == StatusActive || s.Status == StatusTrialing
}
// Invoice represents a subscription billing invoice
type Invoice struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
SubscriptionID uuid.UUID `gorm:"type:uuid;not null" json:"subscription_id"`
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
AmountCents int `gorm:"not null" json:"amount_cents"`
Currency string `gorm:"size:3;default:'USD'" json:"currency"`
Status InvoiceStatus `gorm:"not null;size:30;default:'pending'" json:"status"`
BillingPeriodStart time.Time `gorm:"not null" json:"billing_period_start"`
BillingPeriodEnd time.Time `gorm:"not null" json:"billing_period_end"`
HyperswitchPaymentID string `gorm:"size:255" json:"hyperswitch_payment_id,omitempty"`
PaidAt *time.Time `json:"paid_at,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
}
func (Invoice) TableName() string {
return "subscription_invoices"
}
func (i *Invoice) BeforeCreate(tx *gorm.DB) error {
if i.ID == uuid.Nil {
i.ID = uuid.New()
}
return nil
}