Add Stripe billing, free trials, and cross-platform subscription guards
- Stripe integration: add StripeService with checkout sessions, customer portal, and webhook handling for subscription lifecycle events. - Free trials: auto-start configurable trial on first subscription check, with admin-controllable duration and enable/disable toggle. - Cross-platform guard: prevent duplicate subscriptions across iOS, Android, and Stripe by checking existing platform before allowing purchase. - Subscription model: add Stripe fields (customer_id, subscription_id, price_id), trial fields (trial_start, trial_end, trial_used), and SubscriptionSource/IsTrialActive helpers. - API: add trial and source fields to status response, update OpenAPI spec. - Clean up stale migration and audit docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,20 @@ const (
|
||||
TierPro SubscriptionTier = "pro"
|
||||
)
|
||||
|
||||
// SubscriptionPlatform constants
|
||||
const (
|
||||
PlatformIOS = "ios"
|
||||
PlatformAndroid = "android"
|
||||
PlatformStripe = "stripe"
|
||||
)
|
||||
|
||||
// SubscriptionSettings represents the subscription_subscriptionsettings table (singleton)
|
||||
type SubscriptionSettings struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
EnableLimitations bool `gorm:"column:enable_limitations;default:false" json:"enable_limitations"`
|
||||
EnableMonitoring bool `gorm:"column:enable_monitoring;default:true" json:"enable_monitoring"`
|
||||
TrialEnabled bool `gorm:"column:trial_enabled;default:true" json:"trial_enabled"`
|
||||
TrialDurationDays int `gorm:"column:trial_duration_days;default:14" json:"trial_duration_days"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for GORM
|
||||
@@ -31,18 +40,28 @@ type UserSubscription struct {
|
||||
User User `gorm:"foreignKey:UserID" json:"-"`
|
||||
Tier SubscriptionTier `gorm:"column:tier;size:10;default:'free'" json:"tier"`
|
||||
|
||||
// In-App Purchase data
|
||||
AppleReceiptData *string `gorm:"column:apple_receipt_data;type:text" json:"-"`
|
||||
// In-App Purchase data (Apple / Google)
|
||||
AppleReceiptData *string `gorm:"column:apple_receipt_data;type:text" json:"-"`
|
||||
GooglePurchaseToken *string `gorm:"column:google_purchase_token;type:text" json:"-"`
|
||||
|
||||
// Stripe data (web subscriptions)
|
||||
StripeCustomerID *string `gorm:"column:stripe_customer_id;size:255" json:"-"`
|
||||
StripeSubscriptionID *string `gorm:"column:stripe_subscription_id;size:255" json:"-"`
|
||||
StripePriceID *string `gorm:"column:stripe_price_id;size:255" json:"-"`
|
||||
|
||||
// Subscription dates
|
||||
SubscribedAt *time.Time `gorm:"column:subscribed_at" json:"subscribed_at"`
|
||||
ExpiresAt *time.Time `gorm:"column:expires_at" json:"expires_at"`
|
||||
AutoRenew bool `gorm:"column:auto_renew;default:true" json:"auto_renew"`
|
||||
|
||||
// Trial
|
||||
TrialStart *time.Time `gorm:"column:trial_start" json:"trial_start"`
|
||||
TrialEnd *time.Time `gorm:"column:trial_end" json:"trial_end"`
|
||||
TrialUsed bool `gorm:"column:trial_used;default:false" json:"trial_used"`
|
||||
|
||||
// Tracking
|
||||
CancelledAt *time.Time `gorm:"column:cancelled_at" json:"cancelled_at"`
|
||||
Platform string `gorm:"column:platform;size:10" json:"platform"` // ios, android
|
||||
Platform string `gorm:"column:platform;size:10" json:"platform"` // ios, android, stripe
|
||||
|
||||
// Admin override - bypasses all limitations regardless of global settings
|
||||
IsFree bool `gorm:"column:is_free;default:false" json:"is_free"`
|
||||
@@ -53,8 +72,11 @@ func (UserSubscription) TableName() string {
|
||||
return "subscription_usersubscription"
|
||||
}
|
||||
|
||||
// IsActive returns true if the subscription is active (pro tier and not expired)
|
||||
// IsActive returns true if the subscription is active (pro tier and not expired, or trial active)
|
||||
func (s *UserSubscription) IsActive() bool {
|
||||
if s.IsTrialActive() {
|
||||
return true
|
||||
}
|
||||
if s.Tier != TierPro {
|
||||
return false
|
||||
}
|
||||
@@ -64,9 +86,37 @@ func (s *UserSubscription) IsActive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsPro returns true if the user has a pro subscription
|
||||
// IsPro returns true if the user has a pro subscription or active trial
|
||||
func (s *UserSubscription) IsPro() bool {
|
||||
return s.Tier == TierPro && s.IsActive()
|
||||
return s.IsActive()
|
||||
}
|
||||
|
||||
// IsTrialActive returns true if the user has an active, unexpired trial
|
||||
func (s *UserSubscription) IsTrialActive() bool {
|
||||
if s.TrialEnd == nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().UTC().Before(*s.TrialEnd)
|
||||
}
|
||||
|
||||
// HasStripeSubscription returns true if the user has Stripe subscription data
|
||||
func (s *UserSubscription) HasStripeSubscription() bool {
|
||||
return s.StripeSubscriptionID != nil && *s.StripeSubscriptionID != ""
|
||||
}
|
||||
|
||||
// HasAppleSubscription returns true if the user has Apple receipt data
|
||||
func (s *UserSubscription) HasAppleSubscription() bool {
|
||||
return s.AppleReceiptData != nil && *s.AppleReceiptData != ""
|
||||
}
|
||||
|
||||
// HasGoogleSubscription returns true if the user has Google purchase token
|
||||
func (s *UserSubscription) HasGoogleSubscription() bool {
|
||||
return s.GooglePurchaseToken != nil && *s.GooglePurchaseToken != ""
|
||||
}
|
||||
|
||||
// SubscriptionSource returns the platform that the active subscription came from
|
||||
func (s *UserSubscription) SubscriptionSource() string {
|
||||
return s.Platform
|
||||
}
|
||||
|
||||
// UpgradeTrigger represents the subscription_upgradetrigger table
|
||||
|
||||
Reference in New Issue
Block a user