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.
150 lines
6.3 KiB
Go
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
|
|
}
|