package subscriptions import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/ports" "context" "errors" "time" ) var ( ErrPlanNotFound = errors.New("subscription plan not found") ErrSubscriptionNotFound = errors.New("subscription not found") ErrAlreadySubscribed = errors.New("user already has an active subscription") ErrInvalidPlan = errors.New("invalid subscription plan") ) type Service struct { store ports.SubscriptionStore } func NewService(store ports.SubscriptionStore) *Service { return &Service{store: store} } // ===================== // Subscription Plans // ===================== func (s *Service) CreatePlan(ctx context.Context, input domain.CreateSubscriptionPlanInput) (*domain.SubscriptionPlan, error) { return s.store.CreateSubscriptionPlan(ctx, input) } func (s *Service) GetPlanByID(ctx context.Context, id int64) (*domain.SubscriptionPlan, error) { return s.store.GetSubscriptionPlanByID(ctx, id) } func (s *Service) ListPlans(ctx context.Context, activeOnly bool) ([]domain.SubscriptionPlan, error) { return s.store.ListSubscriptionPlans(ctx, activeOnly) } func (s *Service) UpdatePlan(ctx context.Context, id int64, input domain.UpdateSubscriptionPlanInput) error { return s.store.UpdateSubscriptionPlan(ctx, id, input) } func (s *Service) DeletePlan(ctx context.Context, id int64) error { return s.store.DeleteSubscriptionPlan(ctx, id) } // ===================== // User Subscriptions // ===================== // Subscribe creates a new subscription for a user func (s *Service) Subscribe(ctx context.Context, userID, planID int64, paymentRef, paymentMethod *string) (*domain.UserSubscription, error) { // Check if user already has an active subscription hasActive, err := s.store.HasActiveSubscription(ctx, userID) if err != nil { return nil, err } if hasActive { return nil, ErrAlreadySubscribed } // Get the plan to calculate expiry plan, err := s.store.GetSubscriptionPlanByID(ctx, planID) if err != nil { return nil, ErrPlanNotFound } if !plan.IsActive { return nil, ErrInvalidPlan } // Calculate expiry date startsAt := time.Now() expiresAt := domain.CalculateExpiryDate(startsAt, plan.DurationValue, plan.DurationUnit) input := domain.CreateUserSubscriptionInput{ UserID: userID, PlanID: planID, StartsAt: &startsAt, ExpiresAt: expiresAt, Status: strPtr(string(domain.SubscriptionStatusActive)), PaymentReference: paymentRef, PaymentMethod: paymentMethod, AutoRenew: boolPtr(false), } return s.store.CreateUserSubscription(ctx, input) } func (s *Service) GetSubscriptionByID(ctx context.Context, id int64) (*domain.UserSubscription, error) { return s.store.GetUserSubscriptionByID(ctx, id) } func (s *Service) GetActiveSubscription(ctx context.Context, userID int64) (*domain.UserSubscription, error) { return s.store.GetActiveSubscriptionByUserID(ctx, userID) } func (s *Service) GetSubscriptionHistory(ctx context.Context, userID int64, limit, offset int32) ([]domain.UserSubscription, error) { return s.store.GetUserSubscriptionHistory(ctx, userID, limit, offset) } func (s *Service) HasActiveSubscription(ctx context.Context, userID int64) (bool, error) { return s.store.HasActiveSubscription(ctx, userID) } func (s *Service) CancelSubscription(ctx context.Context, subscriptionID int64) error { return s.store.CancelUserSubscription(ctx, subscriptionID) } func (s *Service) SetAutoRenew(ctx context.Context, subscriptionID int64, autoRenew bool) error { return s.store.UpdateAutoRenew(ctx, subscriptionID, autoRenew) } // RenewSubscription extends an existing subscription func (s *Service) RenewSubscription(ctx context.Context, subscriptionID int64) (*domain.UserSubscription, error) { sub, err := s.store.GetUserSubscriptionByID(ctx, subscriptionID) if err != nil { return nil, ErrSubscriptionNotFound } plan, err := s.store.GetSubscriptionPlanByID(ctx, sub.PlanID) if err != nil { return nil, ErrPlanNotFound } // Calculate new expiry from current expiry (or now if expired) baseTime := sub.ExpiresAt if baseTime.Before(time.Now()) { baseTime = time.Now() } newExpiry := domain.CalculateExpiryDate(baseTime, plan.DurationValue, plan.DurationUnit) err = s.store.ExtendSubscription(ctx, subscriptionID, newExpiry) if err != nil { return nil, err } // Also reactivate if expired if sub.Status == string(domain.SubscriptionStatusExpired) { err = s.store.UpdateSubscriptionStatus(ctx, subscriptionID, string(domain.SubscriptionStatusActive)) if err != nil { return nil, err } } return s.store.GetUserSubscriptionByID(ctx, subscriptionID) } // Helper functions func strPtr(s string) *string { return &s } func boolPtr(b bool) *bool { return &b }