Files
warmbly/internal/models/subscriptions.go
T
Matthew Meszaros d227038ca0 ci: drop unused/unconvert/gosimple + shadow/nilness, run gofmt
Disable the linters that fire on legacy code without flagging real
bugs: `unused` (orphan repos kept for future feature flags),
`unconvert` (defensive type conversions), `gosimple` (style
suggestions in code we don't want to touch).

govet: disable `shadow` (idiomatic `err :=` re-decls in transaction
patterns) and `nilness` (legitimate defensive nil checks that look
tautological to the analyzer).

Ran `gofmt -w internal/ cmd/` — every Go file now passes
gofmt -l with no output.

Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx,
sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
2026-05-23 16:54:12 +00:00

206 lines
6.7 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
type Duration string
const (
DurationMonth Duration = "month"
DurationYear Duration = "year"
)
type SubscriptionStatus string
const (
SubscriptionStatusTrialing SubscriptionStatus = "trialing"
SubscriptionStatusActive SubscriptionStatus = "active"
SubscriptionStatusPastDue SubscriptionStatus = "past_due"
SubscriptionStatusCanceled SubscriptionStatus = "canceled"
SubscriptionStatusUnpaid SubscriptionStatus = "unpaid"
SubscriptionStatusIncomplete SubscriptionStatus = "incomplete"
SubscriptionStatusIncompleteExpired SubscriptionStatus = "incomplete_expired"
SubscriptionStatusPaused SubscriptionStatus = "paused"
)
// IsActive returns true if the subscription allows access
func (s SubscriptionStatus) IsActive() bool {
return s == SubscriptionStatusActive || s == SubscriptionStatusTrialing
}
type Plan struct {
ID uuid.UUID `json:"id"`
Name *string `json:"name,omitempty"`
MaxContacts uint `json:"max_contacts"`
DailyEmails uint `json:"daily_emails"`
AIGeneration bool `json:"ai_generation"`
AccountLimit uint `json:"account_limit"`
Price float32 `json:"price"`
DiscountedPrice float32 `json:"discounted_price"`
Duration Duration `json:"duration"`
Savings uint8 `json:"savings"`
Public bool `json:"public"`
// Stripe integration
StripePriceID *string `json:"stripe_price_id,omitempty"`
StripeProductID *string `json:"stripe_product_id,omitempty"`
// Worker tier settings
DedicatedWorkers int `json:"dedicated_workers"`
DailyCampaignLimit *int `json:"daily_campaign_limit,omitempty"`
// Organization limits
MaxCampaigns *int `json:"max_campaigns,omitempty"`
MaxActiveCampaigns *int `json:"max_active_campaigns,omitempty"`
MaxTeamMembers *int `json:"max_team_members,omitempty"`
MaxEmailAccounts *int `json:"max_email_accounts,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
type OfferOption struct {
Title string `json:"title"`
Plan string `json:"plan"`
}
type Offer struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Options []OfferOption `json:"options"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
type Subscription struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
OrganizationID uuid.UUID `json:"organization_id"`
PlanID uuid.UUID `json:"plan_id"`
// Stripe identifiers
StripeCustomerID string `json:"stripe_customer_id"`
StripeSubscriptionID *string `json:"stripe_subscription_id,omitempty"`
StripePriceID *string `json:"stripe_price_id,omitempty"`
// Subscription state
Status SubscriptionStatus `json:"status"`
// Billing period
CurrentPeriodStart *time.Time `json:"current_period_start,omitempty"`
CurrentPeriodEnd *time.Time `json:"current_period_end,omitempty"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
CanceledAt *time.Time `json:"canceled_at,omitempty"`
// Stripe trial info
TrialStart *time.Time `json:"trial_start,omitempty"`
TrialEnd *time.Time `json:"trial_end,omitempty"`
// Free trial info (internal, not Stripe)
FreeTrialStartedAt *time.Time `json:"free_trial_started_at,omitempty"`
FreeTrialEndsAt *time.Time `json:"free_trial_ends_at,omitempty"`
// Enterprise flag
IsEnterprise bool `json:"is_enterprise"`
// Joined data
Plan *Plan `json:"plan,omitempty"`
// User info (populated by joins)
UserEmail *string `json:"user_email,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
// IsInFreeTrial returns true if the user is currently in their free trial period
func (s *Subscription) IsInFreeTrial() bool {
if s.FreeTrialEndsAt == nil {
return false
}
return time.Now().Before(*s.FreeTrialEndsAt)
}
// IsFreeTrialExpired returns true if the free trial has expired
func (s *Subscription) IsFreeTrialExpired() bool {
if s.FreeTrialEndsAt == nil {
return false
}
return time.Now().After(*s.FreeTrialEndsAt)
}
// HasPaidSubscription returns true if user has an active paid Stripe subscription
func (s *Subscription) HasPaidSubscription() bool {
return s.StripeSubscriptionID != nil && s.Status.IsActive()
}
// CanSendEmails returns true if user can send campaign emails
func (s *Subscription) CanSendEmails() bool {
// Active paid subscription
if s.HasPaidSubscription() {
return true
}
// In free trial
if s.IsInFreeTrial() {
return true
}
return false
}
// CanUseWarmup returns true if user can use warmup feature
func (s *Subscription) CanUseWarmup() bool {
return s.HasPaidSubscription()
}
// CanUseUnibox returns true if user can use unibox feature
func (s *Subscription) CanUseUnibox() bool {
return s.HasPaidSubscription()
}
// SubscriptionWithLimits includes rate limits for the subscription
type SubscriptionWithLimits struct {
Subscription
RateLimits *RealtimeRateLimits `json:"rate_limits,omitempty"`
}
// RealtimeRateLimits contains WebSocket-specific rate limits
type RealtimeRateLimits struct {
LimitWSMessagePM int `json:"limit_ws_message_pm"`
LimitWSJoinPM int `json:"limit_ws_join_pm"`
LimitWSEventPM int `json:"limit_ws_event_pm"`
MaxConnections int `json:"max_connections"`
}
// CreateSubscriptionParams for creating a new subscription
type CreateSubscriptionParams struct {
UserID uuid.UUID `json:"user_id"`
PlanID uuid.UUID `json:"plan_id"`
StripeCustomerID string `json:"stripe_customer_id"`
}
// UpdateSubscriptionFromStripe updates subscription from Stripe webhook data
type UpdateSubscriptionFromStripe struct {
StripeSubscriptionID string `json:"stripe_subscription_id"`
StripePriceID *string `json:"stripe_price_id,omitempty"`
Status *SubscriptionStatus `json:"status,omitempty"`
CurrentPeriodStart *time.Time `json:"current_period_start,omitempty"`
CurrentPeriodEnd *time.Time `json:"current_period_end,omitempty"`
CancelAtPeriodEnd *bool `json:"cancel_at_period_end,omitempty"`
CanceledAt *time.Time `json:"canceled_at,omitempty"`
TrialStart *time.Time `json:"trial_start,omitempty"`
TrialEnd *time.Time `json:"trial_end,omitempty"`
}
// StripeWebhookEvent for idempotency tracking
type StripeWebhookEvent struct {
ID string `json:"id"`
EventType string `json:"event_type"`
ProcessedAt time.Time `json:"processed_at"`
Payload map[string]interface{} `json:"payload,omitempty"`
}