Yimaru-BackEnd/internal/services/subscriptions/service.go

159 lines
4.7 KiB
Go

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
}