package subscription import ( "time" "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/tools" ) type SubscriptionStatus int const ( SUBSCRIPTION_PENDING SubscriptionStatus = iota // en attente de premier paiement SUBSCRIPTION_TRIAL // période d'essai SUBSCRIPTION_ACTIVE // actif SUBSCRIPTION_PAUSED // suspendu temporairement SUBSCRIPTION_CANCELLED // résilié par l'utilisateur SUBSCRIPTION_EXPIRED // date de fin dépassée ) func (s SubscriptionStatus) String() string { return [...]string{"pending", "trial", "active", "paused", "cancelled", "expired"}[s] } func SubscriptionStatusList() []SubscriptionStatus { return []SubscriptionStatus{SUBSCRIPTION_PENDING, SUBSCRIPTION_TRIAL, SUBSCRIPTION_ACTIVE, SUBSCRIPTION_PAUSED, SUBSCRIPTION_CANCELLED, SUBSCRIPTION_EXPIRED} } // SubscriptionItem représente un élément d'un abonnement (ressource louée). type SubscriptionItem struct { ResourceID string `json:"resource_id" bson:"resource_id"` ResourceType tools.DataType `json:"resource_type" bson:"resource_type"` Quantity int `json:"quantity" bson:"quantity"` UnitPrice float64 `json:"unit_price" bson:"unit_price"` } // Subscription représente un abonnement mensuel ou annuel à des ressources. type Subscription struct { utils.AbstractObject SubscriberPeerID string `json:"subscriber_peer_id" bson:"subscriber_peer_id" validate:"required"` ProviderPeerID string `json:"provider_peer_id,omitempty" bson:"provider_peer_id,omitempty"` Status SubscriptionStatus `json:"status" bson:"status"` PlanType pricing.PaymentType `json:"plan_type" bson:"plan_type"` // PAY_EVERY_MONTH ou PAY_EVERY_YEAR Items []*SubscriptionItem `json:"items,omitempty" bson:"items,omitempty"` Amount float64 `json:"amount" bson:"amount"` Currency string `json:"currency" bson:"currency" default:"EUR"` StartDate time.Time `json:"start_date" bson:"start_date"` EndDate *time.Time `json:"end_date,omitempty" bson:"end_date,omitempty"` NextBillingDate time.Time `json:"next_billing_date" bson:"next_billing_date"` AutoRenew bool `json:"auto_renew" bson:"auto_renew" default:"true"` TrialEndDate *time.Time `json:"trial_end_date,omitempty" bson:"trial_end_date,omitempty"` DiscountIDs []string `json:"discount_ids,omitempty" bson:"discount_ids,omitempty"` CancelledAt *time.Time `json:"cancelled_at,omitempty" bson:"cancelled_at,omitempty"` CancelReason string `json:"cancel_reason,omitempty" bson:"cancel_reason,omitempty"` } // newSubscription est le constructeur interne commun. func newSubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string, plan pricing.PaymentType, nextBilling time.Time) *Subscription { now := time.Now().UTC() return &Subscription{ SubscriberPeerID: subscriberPeerID, ProviderPeerID: providerPeerID, Status: SUBSCRIPTION_PENDING, PlanType: plan, Items: items, Amount: amount, Currency: currency, StartDate: now, NextBillingDate: nextBilling, AutoRenew: true, } } // NewWeeklySubscription crée un abonnement hebdomadaire. func NewWeeklySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription { now := time.Now().UTC() return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_WEEK, now.AddDate(0, 0, 7)) } // NewMonthlySubscription crée un abonnement mensuel. func NewMonthlySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription { now := time.Now().UTC() return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_MONTH, now.AddDate(0, 1, 0)) } // NewYearlySubscription crée un abonnement annuel. func NewYearlySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription { now := time.Now().UTC() return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_YEAR, now.AddDate(1, 0, 0)) } // NewTrialSubscription crée un abonnement mensuel avec période d'essai. func NewTrialSubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string, trialDays int) *Subscription { now := time.Now().UTC() trialEnd := now.AddDate(0, 0, trialDays) s := NewMonthlySubscription(subscriberPeerID, providerPeerID, items, amount, currency) s.Status = SUBSCRIPTION_TRIAL s.TrialEndDate = &trialEnd s.NextBillingDate = trialEnd return s } // Activate passe l'abonnement au statut actif (après premier paiement). func (s *Subscription) Activate() { s.Status = SUBSCRIPTION_ACTIVE } // Pause suspend l'abonnement. func (s *Subscription) Pause() { if s.Status == SUBSCRIPTION_ACTIVE { s.Status = SUBSCRIPTION_PAUSED } } // Resume réactive un abonnement suspendu. func (s *Subscription) Resume() { if s.Status == SUBSCRIPTION_PAUSED { s.Status = SUBSCRIPTION_ACTIVE } } // Cancel résilie l'abonnement. func (s *Subscription) Cancel(reason string) { now := time.Now().UTC() s.Status = SUBSCRIPTION_CANCELLED s.CancelledAt = &now s.CancelReason = reason s.AutoRenew = false } // Renew avance la prochaine date de facturation d'une période. func (s *Subscription) Renew() { switch s.PlanType { case pricing.PAY_EVERY_WEEK: s.NextBillingDate = s.NextBillingDate.AddDate(0, 0, 7) case pricing.PAY_EVERY_MONTH: s.NextBillingDate = s.NextBillingDate.AddDate(0, 1, 0) case pricing.PAY_EVERY_YEAR: s.NextBillingDate = s.NextBillingDate.AddDate(1, 0, 0) } } // IsExpired vérifie si l'abonnement a dépassé sa date de fin. func (s *Subscription) IsExpired() bool { if s.EndDate == nil { return false } return time.Now().UTC().After(*s.EndDate) } // IsBillingDue vérifie si la prochaine échéance est atteinte. func (s *Subscription) IsBillingDue() bool { return s.Status == SUBSCRIPTION_ACTIVE && !time.Now().UTC().Before(s.NextBillingDate) } // IsInTrial vérifie si l'abonnement est en période d'essai. func (s *Subscription) IsInTrial() bool { return s.Status == SUBSCRIPTION_TRIAL && s.TrialEndDate != nil && time.Now().UTC().Before(*s.TrialEndDate) } // ComputeAmount recalcule le montant total depuis les items. func (s *Subscription) ComputeAmount() float64 { total := 0.0 for _, item := range s.Items { total += item.UnitPrice * float64(item.Quantity) } s.Amount = total return total } func (s *Subscription) GetAccessor(request *tools.APIRequest) utils.Accessor { return NewAccessor(request) } func (s *Subscription) StoreDraftDefault() { s.IsDraft = true } func (s *Subscription) CanUpdate(set utils.DBObject) (bool, utils.DBObject) { incoming := set.(*Subscription) if !s.IsDraft && s.Status != incoming.Status { return true, &Subscription{ Status: incoming.Status, AutoRenew: incoming.AutoRenew, CancelReason: incoming.CancelReason, } } return s.IsDraft, set } func (s *Subscription) CanDelete() bool { return s.IsDraft || s.Status == SUBSCRIPTION_CANCELLED }