mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
refactor: Extract service methods into separate files for better organization
- Move GetUsage, GetUsageHistory, GetUsageAnalytics, GetRevenueAnalytics, and GetPopularPlans to service_analytics.go - Move validateRatePlan, validateSubscribeRequest, calculateNextBillingDate, and calculateCycleEnd to service_methods.go - Move SubscribeToPlan, GetSubscription, UpdateSubscription, CancelSubscription, GetActiveSubscription, and ListSubscriptions to service_subscriptions.go - Move RecordUsage to service_usage.go
This commit is contained in:
@@ -23,7 +23,6 @@ func NewService(repo Repository, logger *logrus.Logger) *Service {
|
||||
}
|
||||
}
|
||||
|
||||
// CreateRatePlan creates a new rate plan with validation
|
||||
func (s *Service) CreateRatePlan(ctx context.Context, plan *RatePlan) (*RatePlan, error) {
|
||||
// Generate ID if not provided
|
||||
if plan.ID == "" {
|
||||
@@ -134,221 +133,6 @@ func (s *Service) SearchRatePlans(ctx context.Context, criteria SearchCriteria)
|
||||
|
||||
return s.repo.ListRatePlans(ctx, filter)
|
||||
}
|
||||
|
||||
// SubscribeToPlan subscribes a profile to a rate plan
|
||||
func (s *Service) SubscribeToPlan(ctx context.Context, req *SubscribeRequest) (*RatePlanSubscription, error) {
|
||||
// Validate request
|
||||
if err := s.validateSubscribeRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Get the rate plan
|
||||
plan, err := s.repo.GetRatePlan(ctx, req.RatePlanID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if plan is active
|
||||
if !plan.IsActive || plan.Status != PlanStatusActive {
|
||||
return nil, fmt.Errorf("rate plan is not available for subscription")
|
||||
}
|
||||
|
||||
// Check if profile already has an active subscription
|
||||
activeSub, err := s.repo.GetActiveSubscription(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if activeSub != nil {
|
||||
return nil, fmt.Errorf("profile already has an active subscription")
|
||||
}
|
||||
|
||||
// Create subscription
|
||||
subscription := &RatePlanSubscription{
|
||||
ID: uuid.New().String(),
|
||||
ProfileID: req.ProfileID,
|
||||
RatePlanID: req.RatePlanID,
|
||||
Status: SubscriptionStatusActive,
|
||||
StartedAt: time.Now(),
|
||||
BillingCycle: plan.BillingCycle,
|
||||
NextBillingDate: s.calculateNextBillingDate(plan.BillingCycle, time.Now()),
|
||||
AutoRenew: req.AutoRenew,
|
||||
CurrentCycle: time.Now(),
|
||||
AppliedDiscounts: req.AppliedDiscounts,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"subscription_id": subscription.ID,
|
||||
"profile_id": req.ProfileID,
|
||||
"rate_plan_id": req.RatePlanID,
|
||||
}).Info("Subscription created successfully")
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// GetSubscription retrieves a subscription by ID
|
||||
func (s *Service) GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error) {
|
||||
subscription, err := s.repo.GetSubscription(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("subscription_id", id).Error("Failed to get subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// UpdateSubscription updates an existing subscription
|
||||
func (s *Service) UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) (*RatePlanSubscription, error) {
|
||||
if err := s.repo.UpdateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.WithField("subscription_id", subscription.ID).Info("Subscription updated successfully")
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// CancelSubscription cancels a subscription
|
||||
func (s *Service) CancelSubscription(ctx context.Context, subscriptionID string, reason string) error {
|
||||
subscription, err := s.repo.GetSubscription(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if subscription.Status != SubscriptionStatusActive {
|
||||
return fmt.Errorf("subscription is not active")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
subscription.Status = SubscriptionStatusCancelled
|
||||
subscription.EndedAt = &now
|
||||
subscription.UpdatedAt = now
|
||||
|
||||
if subscription.Metadata == nil {
|
||||
subscription.Metadata = make(map[string]interface{})
|
||||
}
|
||||
subscription.Metadata["cancellation_reason"] = reason
|
||||
|
||||
if err := s.repo.UpdateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to cancel subscription")
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.WithField("subscription_id", subscriptionID).Info("Subscription cancelled successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetActiveSubscription retrieves the active subscription for a profile
|
||||
func (s *Service) GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error) {
|
||||
subscription, err := s.repo.GetActiveSubscription(ctx, profileID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("profile_id", profileID).Error("Failed to get active subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions retrieves subscriptions for a profile
|
||||
func (s *Service) ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) {
|
||||
subscriptions, err := s.repo.ListSubscriptions(ctx, profileID, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to list subscriptions")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
// RecordUsage records usage for a subscription
|
||||
func (s *Service) RecordUsage(ctx context.Context, req *RecordUsageRequest) (*RatePlanUsage, error) {
|
||||
// Get active subscription
|
||||
subscription, err := s.repo.GetActiveSubscription(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subscription == nil {
|
||||
return nil, fmt.Errorf("no active subscription found")
|
||||
}
|
||||
|
||||
// Get current usage
|
||||
currentUsage, err := s.repo.GetCurrentUsage(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create or update usage record
|
||||
var usage *RatePlanUsage
|
||||
if currentUsage == nil {
|
||||
// Create new usage record
|
||||
usage = &RatePlanUsage{
|
||||
ID: uuid.New().String(),
|
||||
RatePlanID: subscription.RatePlanID,
|
||||
ProfileID: req.ProfileID,
|
||||
CycleStart: subscription.CurrentCycle,
|
||||
CycleEnd: s.calculateCycleEnd(subscription.BillingCycle, subscription.CurrentCycle),
|
||||
DataUsed: req.DataUsed,
|
||||
VoiceUsed: req.VoiceUsed,
|
||||
SMSUsed: req.SMSUsed,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateUsage(ctx, usage); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create usage record")
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Update existing usage record
|
||||
currentUsage.DataUsed += req.DataUsed
|
||||
currentUsage.VoiceUsed += req.VoiceUsed
|
||||
currentUsage.SMSUsed += req.SMSUsed
|
||||
|
||||
if err := s.repo.UpdateUsage(ctx, currentUsage); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update usage record")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usage = currentUsage
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"profile_id": req.ProfileID,
|
||||
"data_used": req.DataUsed,
|
||||
"voice_used": req.VoiceUsed,
|
||||
"sms_used": req.SMSUsed,
|
||||
}).Info("Usage recorded successfully")
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// GetUsage retrieves usage for a profile
|
||||
func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) {
|
||||
usage, err := s.repo.GetCurrentUsage(ctx, profileID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("profile_id", profileID).Error("Failed to get usage")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// GetUsageHistory retrieves usage history for a profile
|
||||
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) {
|
||||
usageHistory, err := s.repo.ListUsageHistory(ctx, profileID, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage history")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usageHistory, nil
|
||||
}
|
||||
|
||||
// CalculateCost calculates the cost for a rate plan based on usage
|
||||
func (s *Service) CalculateCost(ctx context.Context, req *CalculateCostRequest) (*RatePlanCostCalculation, error) {
|
||||
// Get the rate plan
|
||||
@@ -381,9 +165,10 @@ func (s *Service) CalculateCost(ctx context.Context, req *CalculateCostRequest)
|
||||
for _, discountID := range req.AppliedDiscounts {
|
||||
for _, discount := range plan.Discounts {
|
||||
if discount.ID == discountID && discount.IsActive {
|
||||
if discount.Type == DiscountTypePercentage {
|
||||
switch discount.Type {
|
||||
case DiscountTypePercentage:
|
||||
discountCost += baseCost * discount.Value / 100
|
||||
} else if discount.Type == DiscountTypeFixed {
|
||||
case DiscountTypeFixed:
|
||||
discountCost += discount.Value
|
||||
}
|
||||
}
|
||||
@@ -411,144 +196,3 @@ func (s *Service) CalculateCost(ctx context.Context, req *CalculateCostRequest)
|
||||
|
||||
return calculation, nil
|
||||
}
|
||||
|
||||
// GetUsageAnalytics retrieves usage analytics
|
||||
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) {
|
||||
analytics, err := s.repo.GetUsageAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage analytics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetRevenueAnalytics retrieves revenue analytics
|
||||
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) {
|
||||
analytics, err := s.repo.GetRevenueAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get revenue analytics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetPopularPlans retrieves the most popular rate plans
|
||||
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) {
|
||||
plans, err := s.repo.GetPopularPlans(ctx, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get popular plans")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plans, nil
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
func (s *Service) validateRatePlan(plan *RatePlan) error {
|
||||
if plan.Name == "" {
|
||||
return fmt.Errorf("rate plan name is required")
|
||||
}
|
||||
if plan.CarrierID == "" {
|
||||
return fmt.Errorf("carrier ID is required")
|
||||
}
|
||||
if plan.Region == "" {
|
||||
return fmt.Errorf("region is required")
|
||||
}
|
||||
if plan.BasePrice < 0 {
|
||||
return fmt.Errorf("base price cannot be negative")
|
||||
}
|
||||
if plan.Currency == "" {
|
||||
return fmt.Errorf("currency is required")
|
||||
}
|
||||
if plan.BillingCycle == "" {
|
||||
return fmt.Errorf("billing cycle is required")
|
||||
}
|
||||
if plan.ValidFrom.IsZero() {
|
||||
return fmt.Errorf("valid from date is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
|
||||
if req.ProfileID == "" {
|
||||
return fmt.Errorf("profile ID is required")
|
||||
}
|
||||
if req.RatePlanID == "" {
|
||||
return fmt.Errorf("rate plan ID is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) calculateNextBillingDate(cycle BillingCycle, from time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
return from.AddDate(0, 0, 1)
|
||||
case BillingCycleWeekly:
|
||||
return from.AddDate(0, 0, 7)
|
||||
case BillingCycleMonthly:
|
||||
return from.AddDate(0, 1, 0)
|
||||
case BillingCycleQuarterly:
|
||||
return from.AddDate(0, 3, 0)
|
||||
case BillingCycleYearly:
|
||||
return from.AddDate(1, 0, 0)
|
||||
default:
|
||||
return from.AddDate(0, 1, 0) // Default to monthly
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) calculateCycleEnd(cycle BillingCycle, cycleStart time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
return cycleStart.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
||||
case BillingCycleWeekly:
|
||||
return cycleStart.AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
case BillingCycleMonthly:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleQuarterly:
|
||||
return cycleStart.AddDate(0, 3, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleYearly:
|
||||
return cycleStart.AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
default:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond) // Default to monthly
|
||||
}
|
||||
}
|
||||
|
||||
// Request/Response types
|
||||
|
||||
type SearchCriteria struct {
|
||||
CarrierID string `json:"carrier_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
PlanType PlanType `json:"plan_type,omitempty"`
|
||||
MinPrice float64 `json:"min_price,omitempty"`
|
||||
MaxPrice float64 `json:"max_price,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
SortBy string `json:"sort_by,omitempty"`
|
||||
SortOrder string `json:"sort_order,omitempty"`
|
||||
}
|
||||
|
||||
type SubscribeRequest struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
RatePlanID string `json:"rate_plan_id"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type RecordUsageRequest struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
DataUsed int64 `json:"data_used"` // in MB
|
||||
VoiceUsed int64 `json:"voice_used"` // in minutes
|
||||
SMSUsed int64 `json:"sms_used"` // count
|
||||
}
|
||||
|
||||
type CalculateCostRequest struct {
|
||||
RatePlanID string `json:"rate_plan_id"`
|
||||
DataUsed int64 `json:"data_used"` // in MB
|
||||
VoiceUsed int64 `json:"voice_used"` // in minutes
|
||||
SMSUsed int64 `json:"sms_used"` // count
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package rateplan
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// GetUsage retrieves usage for a profile
|
||||
func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) {
|
||||
usage, err := s.repo.GetCurrentUsage(ctx, profileID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("profile_id", profileID).Error("Failed to get usage")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
// GetUsageHistory retrieves usage history for a profile
|
||||
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) {
|
||||
usageHistory, err := s.repo.ListUsageHistory(ctx, profileID, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage history")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usageHistory, nil
|
||||
}
|
||||
|
||||
// GetUsageAnalytics retrieves usage analytics
|
||||
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) {
|
||||
analytics, err := s.repo.GetUsageAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage analytics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetRevenueAnalytics retrieves revenue analytics
|
||||
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) {
|
||||
analytics, err := s.repo.GetRevenueAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get revenue analytics")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return analytics, nil
|
||||
}
|
||||
|
||||
// GetPopularPlans retrieves the most popular rate plans
|
||||
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) {
|
||||
plans, err := s.repo.GetPopularPlans(ctx, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get popular plans")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plans, nil
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package rateplan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *Service) validateRatePlan(plan *RatePlan) error {
|
||||
if plan.Name == "" {
|
||||
return fmt.Errorf("rate plan name is required")
|
||||
}
|
||||
if plan.CarrierID == "" {
|
||||
return fmt.Errorf("carrier ID is required")
|
||||
}
|
||||
if plan.Region == "" {
|
||||
return fmt.Errorf("region is required")
|
||||
}
|
||||
if plan.BasePrice < 0 {
|
||||
return fmt.Errorf("base price cannot be negative")
|
||||
}
|
||||
if plan.Currency == "" {
|
||||
return fmt.Errorf("currency is required")
|
||||
}
|
||||
if plan.BillingCycle == "" {
|
||||
return fmt.Errorf("billing cycle is required")
|
||||
}
|
||||
if plan.ValidFrom.IsZero() {
|
||||
return fmt.Errorf("valid from date is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
|
||||
if req.ProfileID == "" {
|
||||
return fmt.Errorf("profile ID is required")
|
||||
}
|
||||
if req.RatePlanID == "" {
|
||||
return fmt.Errorf("rate plan ID is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) calculateNextBillingDate(cycle BillingCycle, from time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
return from.AddDate(0, 0, 1)
|
||||
case BillingCycleWeekly:
|
||||
return from.AddDate(0, 0, 7)
|
||||
case BillingCycleMonthly:
|
||||
return from.AddDate(0, 1, 0)
|
||||
case BillingCycleQuarterly:
|
||||
return from.AddDate(0, 3, 0)
|
||||
case BillingCycleYearly:
|
||||
return from.AddDate(1, 0, 0)
|
||||
default:
|
||||
return from.AddDate(0, 1, 0) // Default to monthly
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) calculateCycleEnd(cycle BillingCycle, cycleStart time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
return cycleStart.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
||||
case BillingCycleWeekly:
|
||||
return cycleStart.AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
case BillingCycleMonthly:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleQuarterly:
|
||||
return cycleStart.AddDate(0, 3, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleYearly:
|
||||
return cycleStart.AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
default:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond) // Default to monthly
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package rateplan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Service) SubscribeToPlan(ctx context.Context, req *SubscribeRequest) (*RatePlanSubscription, error) {
|
||||
if err := s.validateSubscribeRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
plan, err := s.repo.GetRatePlan(ctx, req.RatePlanID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !plan.IsActive || plan.Status != PlanStatusActive {
|
||||
return nil, fmt.Errorf("rate plan is not available for subscription")
|
||||
}
|
||||
|
||||
activeSub, err := s.repo.GetActiveSubscription(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if activeSub != nil {
|
||||
return nil, fmt.Errorf("profile already has an active subscription")
|
||||
}
|
||||
|
||||
// Create subscription
|
||||
subscription := &RatePlanSubscription{
|
||||
ID: uuid.New().String(),
|
||||
ProfileID: req.ProfileID,
|
||||
RatePlanID: req.RatePlanID,
|
||||
Status: SubscriptionStatusActive,
|
||||
StartedAt: time.Now(),
|
||||
BillingCycle: plan.BillingCycle,
|
||||
NextBillingDate: s.calculateNextBillingDate(plan.BillingCycle, time.Now()),
|
||||
AutoRenew: req.AutoRenew,
|
||||
CurrentCycle: time.Now(),
|
||||
AppliedDiscounts: req.AppliedDiscounts,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"subscription_id": subscription.ID,
|
||||
"profile_id": req.ProfileID,
|
||||
"rate_plan_id": req.RatePlanID,
|
||||
}).Info("Subscription created successfully")
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// GetSubscription retrieves a subscription by ID
|
||||
func (s *Service) GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error) {
|
||||
subscription, err := s.repo.GetSubscription(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("subscription_id", id).Error("Failed to get subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// UpdateSubscription updates an existing subscription
|
||||
func (s *Service) UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) (*RatePlanSubscription, error) {
|
||||
if err := s.repo.UpdateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.WithField("subscription_id", subscription.ID).Info("Subscription updated successfully")
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// CancelSubscription cancels a subscription
|
||||
func (s *Service) CancelSubscription(ctx context.Context, subscriptionID string, reason string) error {
|
||||
subscription, err := s.repo.GetSubscription(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if subscription.Status != SubscriptionStatusActive {
|
||||
return fmt.Errorf("subscription is not active")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
subscription.Status = SubscriptionStatusCancelled
|
||||
subscription.EndedAt = &now
|
||||
subscription.UpdatedAt = now
|
||||
|
||||
if subscription.Metadata == nil {
|
||||
subscription.Metadata = make(map[string]interface{})
|
||||
}
|
||||
subscription.Metadata["cancellation_reason"] = reason
|
||||
|
||||
if err := s.repo.UpdateSubscription(ctx, subscription); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to cancel subscription")
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.WithField("subscription_id", subscriptionID).Info("Subscription cancelled successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetActiveSubscription retrieves the active subscription for a profile
|
||||
func (s *Service) GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error) {
|
||||
subscription, err := s.repo.GetActiveSubscription(ctx, profileID)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).WithField("profile_id", profileID).Error("Failed to get active subscription")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions retrieves subscriptions for a profile
|
||||
func (s *Service) ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) {
|
||||
subscriptions, err := s.repo.ListSubscriptions(ctx, profileID, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to list subscriptions")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (s *Service) RecordUsage(ctx context.Context, req *RecordUsageRequest) (*RatePlanUsage, error) {
|
||||
// Get active subscription
|
||||
subscription, err := s.repo.GetActiveSubscription(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if subscription == nil {
|
||||
return nil, fmt.Errorf("no active subscription found")
|
||||
}
|
||||
|
||||
// Get current usage
|
||||
currentUsage, err := s.repo.GetCurrentUsage(ctx, req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create or update usage record
|
||||
var usage *RatePlanUsage
|
||||
if currentUsage == nil {
|
||||
// Create new usage record
|
||||
usage = &RatePlanUsage{
|
||||
ID: uuid.New().String(),
|
||||
RatePlanID: subscription.RatePlanID,
|
||||
ProfileID: req.ProfileID,
|
||||
CycleStart: subscription.CurrentCycle,
|
||||
CycleEnd: s.calculateCycleEnd(subscription.BillingCycle, subscription.CurrentCycle),
|
||||
DataUsed: req.DataUsed,
|
||||
VoiceUsed: req.VoiceUsed,
|
||||
SMSUsed: req.SMSUsed,
|
||||
}
|
||||
|
||||
if err := s.repo.CreateUsage(ctx, usage); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create usage record")
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Update existing usage record
|
||||
currentUsage.DataUsed += req.DataUsed
|
||||
currentUsage.VoiceUsed += req.VoiceUsed
|
||||
currentUsage.SMSUsed += req.SMSUsed
|
||||
|
||||
if err := s.repo.UpdateUsage(ctx, currentUsage); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update usage record")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usage = currentUsage
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"profile_id": req.ProfileID,
|
||||
"data_used": req.DataUsed,
|
||||
"voice_used": req.VoiceUsed,
|
||||
"sms_used": req.SMSUsed,
|
||||
}).Info("Usage recorded successfully")
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
Reference in New Issue
Block a user