195 lines
7.4 KiB
Go
195 lines
7.4 KiB
Go
|
|
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
|
||
|
|
}
|