mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Add repository types package with rate plan, subscription, usage, and analytics domain models
- Add Repository interface with rate plan CRUD, subscription management, usage tracking, and analytics operations - Add RatePlan struct with pricing, allowances, overage rates, discounts, and metadata fields - Add RatePlanSubscription struct with billing cycle, auto-renewal, and applied discounts support - Add RatePlanUsage struct with cycle tracking and data/voice/SMS usage metrics - Add filter types for rate plans, subscriptions, usage analytics, and revenue analytics
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Repository defines the interface for rate plan data operations
|
||||
type Repository interface {
|
||||
// Rate Plan operations
|
||||
CreateRatePlan(ctx context.Context, plan *RatePlan) error
|
||||
GetRatePlan(ctx context.Context, id string) (*RatePlan, error)
|
||||
UpdateRatePlan(ctx context.Context, plan *RatePlan) error
|
||||
DeleteRatePlan(ctx context.Context, id string) error
|
||||
ListRatePlans(ctx context.Context, filter *RatePlanFilter) ([]*RatePlan, error)
|
||||
|
||||
// Subscription operations
|
||||
CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error
|
||||
GetSubscription(ctx context.Context, id string) (*RatePlanSubscription, error)
|
||||
UpdateSubscription(ctx context.Context, subscription *RatePlanSubscription) error
|
||||
GetActiveSubscription(ctx context.Context, profileID string) (*RatePlanSubscription, error)
|
||||
ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error)
|
||||
|
||||
// Usage operations
|
||||
CreateUsage(ctx context.Context, usage *RatePlanUsage) error
|
||||
GetUsage(ctx context.Context, id string) (*RatePlanUsage, error)
|
||||
UpdateUsage(ctx context.Context, usage *RatePlanUsage) error
|
||||
GetCurrentUsage(ctx context.Context, profileID string) (*RatePlanUsage, error)
|
||||
ListUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error)
|
||||
|
||||
// Analytics operations
|
||||
GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error)
|
||||
GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error)
|
||||
GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error)
|
||||
}
|
||||
|
||||
// RatePlanUsage represents usage data for a rate plan
|
||||
type RatePlanUsage struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
RatePlanID string `json:"rate_plan_id" gorm:"index"`
|
||||
ProfileID string `json:"profile_id" gorm:"index"`
|
||||
CycleStart time.Time `json:"cycle_start"`
|
||||
CycleEnd time.Time `json:"cycle_end"`
|
||||
DataUsed int64 `json:"data_used"`
|
||||
VoiceUsed int64 `json:"voice_used"`
|
||||
SMSUsed int64 `json:"sms_used"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for RatePlanUsage
|
||||
func (RatePlanUsage) TableName() string {
|
||||
return "rate_plan_usage"
|
||||
}
|
||||
|
||||
// RatePlanSubscription represents a subscription to a rate plan
|
||||
type RatePlanSubscription struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
ProfileID string `json:"profile_id" gorm:"index"`
|
||||
RatePlanID string `json:"rate_plan_id" gorm:"index"`
|
||||
Status SubscriptionStatus `json:"status" gorm:"index"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
EndedAt *time.Time `json:"ended_at,omitempty"`
|
||||
BillingCycle BillingCycle `json:"billing_cycle"`
|
||||
NextBillingDate time.Time `json:"next_billing_date"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
CurrentCycle time.Time `json:"current_cycle"`
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty" gorm:"serializer:json"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty" gorm:"serializer:json"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for RatePlanSubscription
|
||||
func (RatePlanSubscription) TableName() string {
|
||||
return "rate_plan_subscriptions"
|
||||
}
|
||||
|
||||
// SubscriptionStatus represents the status of a subscription
|
||||
type SubscriptionStatus string
|
||||
|
||||
const (
|
||||
SubscriptionStatusActive SubscriptionStatus = "active"
|
||||
SubscriptionStatusCancelled SubscriptionStatus = "cancelled"
|
||||
SubscriptionStatusExpired SubscriptionStatus = "expired"
|
||||
SubscriptionStatusSuspended SubscriptionStatus = "suspended"
|
||||
)
|
||||
|
||||
// BillingCycle represents the billing cycle type
|
||||
type BillingCycle string
|
||||
|
||||
const (
|
||||
BillingCycleDaily BillingCycle = "daily"
|
||||
BillingCycleWeekly BillingCycle = "weekly"
|
||||
BillingCycleMonthly BillingCycle = "monthly"
|
||||
BillingCycleQuarterly BillingCycle = "quarterly"
|
||||
BillingCycleYearly BillingCycle = "yearly"
|
||||
)
|
||||
|
||||
// SubscriptionFilter defines filtering options for subscription queries
|
||||
type SubscriptionFilter struct {
|
||||
Status SubscriptionStatus `json:"status,omitempty"`
|
||||
RatePlanID string `json:"rate_plan_id,omitempty"`
|
||||
StartedAfter *time.Time `json:"started_after,omitempty"`
|
||||
StartedBefore *time.Time `json:"started_before,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
}
|
||||
|
||||
// RatePlan represents a rate plan
|
||||
type RatePlan struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CarrierID string `json:"carrier_id" gorm:"index"`
|
||||
Region string `json:"region" gorm:"index"`
|
||||
PlanType PlanType `json:"plan_type"`
|
||||
BasePrice float64 `json:"base_price"`
|
||||
Currency string `json:"currency"`
|
||||
BillingCycle BillingCycle `json:"billing_cycle"`
|
||||
DataAllowance *DataAllowance `json:"data_allowance,omitempty" gorm:"serializer:json"`
|
||||
VoiceAllowance *VoiceAllowance `json:"voice_allowance,omitempty" gorm:"serializer:json"`
|
||||
SMSAllowance *SMSAllowance `json:"sms_allowance,omitempty" gorm:"serializer:json"`
|
||||
OverageRates *OverageRates `json:"overage_rates,omitempty" gorm:"serializer:json"`
|
||||
Discounts []*Discount `json:"discounts,omitempty" gorm:"serializer:json"`
|
||||
ValidFrom time.Time `json:"valid_from"`
|
||||
ValidTo *time.Time `json:"valid_to,omitempty"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Status PlanStatus `json:"status"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty" gorm:"serializer:json"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for RatePlan
|
||||
func (RatePlan) TableName() string {
|
||||
return "rate_plans"
|
||||
}
|
||||
|
||||
// PlanType represents the type of rate plan
|
||||
type PlanType string
|
||||
|
||||
const (
|
||||
PlanTypeData PlanType = "data"
|
||||
PlanTypeVoice PlanType = "voice"
|
||||
PlanTypeSMS PlanType = "sms"
|
||||
PlanTypeBundle PlanType = "bundle"
|
||||
)
|
||||
|
||||
// PlanStatus represents the status of a rate plan
|
||||
type PlanStatus string
|
||||
|
||||
const (
|
||||
PlanStatusActive PlanStatus = "active"
|
||||
PlanStatusInactive PlanStatus = "inactive"
|
||||
PlanStatusDraft PlanStatus = "draft"
|
||||
)
|
||||
|
||||
// DataAllowance represents data allowance configuration
|
||||
type DataAllowance struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Unit string `json:"unit"`
|
||||
Unlimited bool `json:"unlimited"`
|
||||
}
|
||||
|
||||
// VoiceAllowance represents voice allowance configuration
|
||||
type VoiceAllowance struct {
|
||||
Minutes int64 `json:"minutes"`
|
||||
Unlimited bool `json:"unlimited"`
|
||||
}
|
||||
|
||||
// SMSAllowance represents SMS allowance configuration
|
||||
type SMSAllowance struct {
|
||||
Messages int64 `json:"messages"`
|
||||
Unlimited bool `json:"unlimited"`
|
||||
}
|
||||
|
||||
// OverageRates represents overage rate configuration
|
||||
type OverageRates struct {
|
||||
DataRate float64 `json:"data_rate"`
|
||||
VoiceRate float64 `json:"voice_rate"`
|
||||
SMSRate float64 `json:"sms_rate"`
|
||||
}
|
||||
|
||||
// Discount represents a discount configuration
|
||||
type Discount struct {
|
||||
ID string `json:"id"`
|
||||
Type DiscountType `json:"type"`
|
||||
Value float64 `json:"value"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// DiscountType represents the type of discount
|
||||
type DiscountType string
|
||||
|
||||
const (
|
||||
DiscountTypePercentage DiscountType = "percentage"
|
||||
DiscountTypeFixed DiscountType = "fixed"
|
||||
)
|
||||
|
||||
// RatePlanFilter defines filtering options for rate plan queries
|
||||
type RatePlanFilter struct {
|
||||
CarrierID string `json:"carrier_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
PlanType PlanType `json:"plan_type,omitempty"`
|
||||
Status PlanStatus `json:"status,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
MinPrice float64 `json:"min_price,omitempty"`
|
||||
MaxPrice float64 `json:"max_price,omitempty"`
|
||||
ValidFrom *time.Time `json:"valid_from,omitempty"`
|
||||
ValidTo *time.Time `json:"valid_to,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Offset int `json:"offset,omitempty"`
|
||||
SortBy string `json:"sort_by,omitempty"`
|
||||
SortOrder string `json:"sort_order,omitempty"`
|
||||
}
|
||||
|
||||
// UsageAnalyticsFilter defines filtering options for usage analytics
|
||||
type UsageAnalyticsFilter struct {
|
||||
RatePlanID string `json:"rate_plan_id,omitempty"`
|
||||
CarrierID string `json:"carrier_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
GroupBy string `json:"group_by,omitempty"`
|
||||
}
|
||||
|
||||
// UsageAnalytics contains usage statistics
|
||||
type UsageAnalytics struct {
|
||||
TotalDataUsed int64 `json:"total_data_used"`
|
||||
TotalVoiceUsed int64 `json:"total_voice_used"`
|
||||
TotalSMSUsed int64 `json:"total_sms_used"`
|
||||
ActiveUsers int `json:"active_users"`
|
||||
AverageUsage map[string]float64 `json:"average_usage"`
|
||||
UsageByPlan map[string]int64 `json:"usage_by_plan"`
|
||||
UsageByRegion map[string]int64 `json:"usage_by_region"`
|
||||
TimelineData []TimelineDataPoint `json:"timeline_data"`
|
||||
}
|
||||
|
||||
// RevenueAnalyticsFilter defines filtering options for revenue analytics
|
||||
type RevenueAnalyticsFilter struct {
|
||||
RatePlanID string `json:"rate_plan_id,omitempty"`
|
||||
CarrierID string `json:"carrier_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
GroupBy string `json:"group_by,omitempty"`
|
||||
}
|
||||
|
||||
// RevenueAnalytics contains revenue statistics
|
||||
type RevenueAnalytics struct {
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
RevenueByPlan map[string]float64 `json:"revenue_by_plan"`
|
||||
RevenueByCarrier map[string]float64 `json:"revenue_by_carrier"`
|
||||
RevenueByRegion map[string]float64 `json:"revenue_by_region"`
|
||||
AverageRevenue map[string]float64 `json:"average_revenue"`
|
||||
TimelineData []TimelineDataPoint `json:"timeline_data"`
|
||||
}
|
||||
|
||||
// TimelineDataPoint represents a data point in time series
|
||||
type TimelineDataPoint struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
Label string `json:"label,omitempty"`
|
||||
}
|
||||
|
||||
// SearchCriteria defines search criteria for rate plans
|
||||
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"`
|
||||
}
|
||||
|
||||
// SubscribeRequest represents a request to subscribe to a rate plan
|
||||
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"`
|
||||
}
|
||||
|
||||
// RecordUsageRequest represents a request to record usage
|
||||
type RecordUsageRequest struct {
|
||||
ProfileID string `json:"profile_id"`
|
||||
DataUsed int64 `json:"data_used"`
|
||||
VoiceUsed int64 `json:"voice_used"`
|
||||
SMSUsed int64 `json:"sms_used"`
|
||||
}
|
||||
|
||||
// CalculateCostRequest represents a request to calculate cost
|
||||
type CalculateCostRequest struct {
|
||||
RatePlanID string `json:"rate_plan_id"`
|
||||
DataUsed int64 `json:"data_used"`
|
||||
VoiceUsed int64 `json:"voice_used"`
|
||||
SMSUsed int64 `json:"sms_used"`
|
||||
AppliedDiscounts []string `json:"applied_discounts,omitempty"`
|
||||
}
|
||||
|
||||
// RatePlanCostCalculation represents the result of a cost calculation
|
||||
type RatePlanCostCalculation struct {
|
||||
RatePlanID string `json:"rate_plan_id"`
|
||||
BaseCost float64 `json:"base_cost"`
|
||||
OverageCost float64 `json:"overage_cost"`
|
||||
DiscountCost float64 `json:"discount_cost"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
Currency string `json:"currency"`
|
||||
Breakdown map[string]interface{} `json:"breakdown"`
|
||||
CalculatedAt time.Time `json:"calculated_at"`
|
||||
}
|
||||
@@ -194,9 +194,9 @@ func (s *AnalyticsServiceImpl) GetTopCurrenciesByRevenue(ctx context.Context, li
|
||||
|
||||
// Convert to slice and sort
|
||||
var currencyRevenues []*currency.CurrencyRevenue
|
||||
for currency, revenue := range revenueByCurrency {
|
||||
for currCode, revenue := range revenueByCurrency {
|
||||
currencyRevenues = append(currencyRevenues, ¤cy.CurrencyRevenue{
|
||||
Currency: currency,
|
||||
Currency: currCode,
|
||||
Revenue: revenue,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,19 +6,20 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// BillingServiceImpl handles multi-currency billing operations
|
||||
type BillingServiceImpl struct {
|
||||
repository Repository
|
||||
exchangeService *ExchangeRateService
|
||||
repository currency.Repository
|
||||
exchangeService currency.ExchangeRateService
|
||||
logger *logrus.Logger
|
||||
baseCurrency string
|
||||
}
|
||||
|
||||
// NewBillingService creates a new billing service
|
||||
func NewBillingService(repository Repository, exchangeService *ExchangeRateService, logger *logrus.Logger, baseCurrency string) *BillingServiceImpl {
|
||||
func NewBillingService(repository currency.Repository, exchangeService currency.ExchangeRateService, logger *logrus.Logger, baseCurrency string) *BillingServiceImpl {
|
||||
return &BillingServiceImpl{
|
||||
repository: repository,
|
||||
exchangeService: exchangeService,
|
||||
@@ -28,7 +29,7 @@ func NewBillingService(repository Repository, exchangeService *ExchangeRateServi
|
||||
}
|
||||
|
||||
// ProcessBilling processes a billing request in multi-currency context
|
||||
func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingRequest) (*BillingResponse, error) {
|
||||
func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *currency.BillingRequest) (*currency.BillingResponse, error) {
|
||||
// Validate request
|
||||
if err := s.validateBillingRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("invalid billing request: %w", err)
|
||||
@@ -49,18 +50,18 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
|
||||
}
|
||||
|
||||
// Create transaction
|
||||
transaction := &Transaction{
|
||||
transaction := ¤cy.Transaction{
|
||||
ID: uuid.New().String(),
|
||||
ProfileID: req.ProfileID,
|
||||
SubscriptionID: req.SubscriptionID,
|
||||
Type: TransactionTypeSubscription,
|
||||
Type: currency.TransactionTypeSubscription,
|
||||
Amount: req.Amount,
|
||||
Currency: req.Currency,
|
||||
BaseAmount: baseAmount,
|
||||
BaseCurrency: s.baseCurrency,
|
||||
ExchangeRate: exchangeRate,
|
||||
Description: req.Description,
|
||||
Status: TransactionStatusPending,
|
||||
Status: currency.TransactionStatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
@@ -72,7 +73,7 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
|
||||
}
|
||||
|
||||
// Process payment (in real implementation, this would integrate with payment gateway)
|
||||
transaction.Status = TransactionStatusCompleted
|
||||
transaction.Status = currency.TransactionStatusCompleted
|
||||
if err := s.repository.UpdateTransaction(ctx, transaction); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to update transaction status")
|
||||
return nil, fmt.Errorf("failed to update transaction: %w", err)
|
||||
@@ -87,7 +88,7 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
|
||||
"base_currency": s.baseCurrency,
|
||||
}).Info("Billing processed successfully")
|
||||
|
||||
return &BillingResponse{
|
||||
return ¤cy.BillingResponse{
|
||||
TransactionID: transaction.ID,
|
||||
Amount: transaction.Amount,
|
||||
Currency: transaction.Currency,
|
||||
@@ -100,14 +101,14 @@ func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingReq
|
||||
}
|
||||
|
||||
// ConvertAmount converts an amount between currencies
|
||||
func (s *BillingServiceImpl) ConvertAmount(ctx context.Context, req *CurrencyConversionRequest) (*CurrencyConversionResponse, error) {
|
||||
func (s *BillingServiceImpl) ConvertAmount(ctx context.Context, req *currency.CurrencyConversionRequest) (*currency.CurrencyConversionResponse, error) {
|
||||
return s.exchangeService.ConvertAmount(ctx, req.Amount, req.FromCurrency, req.ToCurrency)
|
||||
}
|
||||
|
||||
// GetBillingHistory retrieves billing history for a profile
|
||||
func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID string, filter *TransactionFilter) ([]*Transaction, error) {
|
||||
func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID string, filter *currency.TransactionFilter) ([]*currency.Transaction, error) {
|
||||
if filter == nil {
|
||||
filter = &TransactionFilter{}
|
||||
filter = ¤cy.TransactionFilter{}
|
||||
}
|
||||
|
||||
filter.ProfileID = profileID
|
||||
@@ -122,7 +123,7 @@ func (s *BillingServiceImpl) GetBillingHistory(ctx context.Context, profileID st
|
||||
}
|
||||
|
||||
// validateBillingRequest validates a billing request
|
||||
func (s *BillingServiceImpl) validateBillingRequest(req *BillingRequest) error {
|
||||
func (s *BillingServiceImpl) validateBillingRequest(req *currency.BillingRequest) error {
|
||||
if req.ProfileID == "" {
|
||||
return fmt.Errorf("profile ID is required")
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func NewRatePlanCurrencyIntegrator(
|
||||
}
|
||||
|
||||
// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion
|
||||
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, currency string) (*rateplan.RatePlanSubscription, error) {
|
||||
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
@@ -49,8 +49,8 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
subscriptionPrice := plan.BasePrice
|
||||
exchangeRate := 1.0
|
||||
|
||||
if currency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, currency)
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, targetCurrency)
|
||||
if err != nil {
|
||||
rpci.logger.WithError(err).Error("Failed to convert rate plan price")
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
@@ -67,7 +67,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
StartedAt: time.Now(),
|
||||
Metadata: map[string]interface{}{
|
||||
"original_currency": plan.Currency,
|
||||
"subscription_currency": currency,
|
||||
"subscription_currency": targetCurrency,
|
||||
"original_price": plan.BasePrice,
|
||||
"subscription_price": subscriptionPrice,
|
||||
"exchange_rate": exchangeRate,
|
||||
@@ -94,7 +94,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
ProfileID: profileID,
|
||||
SubscriptionID: createdSubscription.ID,
|
||||
Amount: subscriptionPrice,
|
||||
Currency: currency,
|
||||
Currency: targetCurrency,
|
||||
Description: fmt.Sprintf("Initial subscription to %s", plan.Name),
|
||||
BillingDate: time.Now(),
|
||||
}
|
||||
@@ -108,7 +108,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
rpci.logger.WithFields(logrus.Fields{
|
||||
"profile_id": profileID,
|
||||
"plan_id": planID,
|
||||
"currency": currency,
|
||||
"currency": targetCurrency,
|
||||
"subscription_id": createdSubscription.ID,
|
||||
}).Info("Rate plan subscription created with currency support")
|
||||
|
||||
@@ -116,7 +116,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
|
||||
}
|
||||
|
||||
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency
|
||||
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, currency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
|
||||
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
@@ -140,8 +140,8 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
|
||||
convertedCost := baseCost
|
||||
exchangeRate := 1.0
|
||||
|
||||
if currency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, currency)
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
}
|
||||
@@ -153,7 +153,7 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
|
||||
summary := ¤cy.BillingSummary{
|
||||
ProfileID: usageData.ProfileID,
|
||||
TotalAmount: convertedCost,
|
||||
Currency: currency,
|
||||
Currency: targetCurrency,
|
||||
BaseTotalAmount: baseCost,
|
||||
BaseCurrency: plan.Currency,
|
||||
TransactionCount: 1,
|
||||
|
||||
@@ -2,10 +2,12 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
||||
)
|
||||
|
||||
// GetUsage retrieves usage for a profile
|
||||
func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsage, error) {
|
||||
func (s *Service) GetUsage(ctx context.Context, profileID string) (*repository.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")
|
||||
@@ -14,8 +16,9 @@ func (s *Service) GetUsage(ctx context.Context, profileID string) (*RatePlanUsag
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// GetUsageHistory retrieves usage history for a profile
|
||||
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*RatePlanUsage, error) {
|
||||
func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit int) ([]*repository.RatePlanUsage, error) {
|
||||
usageHistory, err := s.repo.ListUsageHistory(ctx, profileID, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage history")
|
||||
@@ -26,7 +29,7 @@ func (s *Service) GetUsageHistory(ctx context.Context, profileID string, limit i
|
||||
}
|
||||
|
||||
// GetUsageAnalytics retrieves usage analytics
|
||||
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsFilter) (*UsageAnalytics, error) {
|
||||
func (s *Service) GetUsageAnalytics(ctx context.Context, filter *repository.UsageAnalyticsFilter) (*repository.UsageAnalytics, error) {
|
||||
analytics, err := s.repo.GetUsageAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get usage analytics")
|
||||
@@ -37,7 +40,7 @@ func (s *Service) GetUsageAnalytics(ctx context.Context, filter *UsageAnalyticsF
|
||||
}
|
||||
|
||||
// GetRevenueAnalytics retrieves revenue analytics
|
||||
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyticsFilter) (*RevenueAnalytics, error) {
|
||||
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *repository.RevenueAnalyticsFilter) (*repository.RevenueAnalytics, error) {
|
||||
analytics, err := s.repo.GetRevenueAnalytics(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get revenue analytics")
|
||||
@@ -48,7 +51,7 @@ func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *RevenueAnalyt
|
||||
}
|
||||
|
||||
// GetPopularPlans retrieves the most popular rate plans
|
||||
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*RatePlan, error) {
|
||||
func (s *Service) GetPopularPlans(ctx context.Context, limit int) ([]*repository.RatePlan, error) {
|
||||
plans, err := s.repo.GetPopularPlans(ctx, limit)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get popular plans")
|
||||
|
||||
@@ -3,9 +3,11 @@ package services
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
||||
)
|
||||
|
||||
func (s *Service) validateRatePlan(plan *RatePlan) error {
|
||||
func (s *Service) validateRatePlan(plan *repository.RatePlan) error {
|
||||
if plan.Name == "" {
|
||||
return fmt.Errorf("rate plan name is required")
|
||||
}
|
||||
@@ -30,7 +32,7 @@ func (s *Service) validateRatePlan(plan *RatePlan) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
|
||||
func (s *Service) validateSubscribeRequest(req *repository.SubscribeRequest) error {
|
||||
if req.ProfileID == "" {
|
||||
return fmt.Errorf("profile ID is required")
|
||||
}
|
||||
@@ -40,37 +42,36 @@ func (s *Service) validateSubscribeRequest(req *SubscribeRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) calculateNextBillingDate(cycle BillingCycle, from time.Time) time.Time {
|
||||
func (s *Service) calculateNextBillingDate(cycle repository.BillingCycle, from time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
case repository.BillingCycleDaily:
|
||||
return from.AddDate(0, 0, 1)
|
||||
case BillingCycleWeekly:
|
||||
case repository.BillingCycleWeekly:
|
||||
return from.AddDate(0, 0, 7)
|
||||
case BillingCycleMonthly:
|
||||
case repository.BillingCycleMonthly:
|
||||
return from.AddDate(0, 1, 0)
|
||||
case BillingCycleQuarterly:
|
||||
case repository.BillingCycleQuarterly:
|
||||
return from.AddDate(0, 3, 0)
|
||||
case BillingCycleYearly:
|
||||
case repository.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 {
|
||||
func (s *Service) calculateCycleEnd(cycle repository.BillingCycle, cycleStart time.Time) time.Time {
|
||||
switch cycle {
|
||||
case BillingCycleDaily:
|
||||
case repository.BillingCycleDaily:
|
||||
return cycleStart.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
||||
case BillingCycleWeekly:
|
||||
case repository.BillingCycleWeekly:
|
||||
return cycleStart.AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
case BillingCycleMonthly:
|
||||
case repository.BillingCycleMonthly:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleQuarterly:
|
||||
case repository.BillingCycleQuarterly:
|
||||
return cycleStart.AddDate(0, 3, 0).Add(-time.Nanosecond)
|
||||
case BillingCycleYearly:
|
||||
case repository.BillingCycleYearly:
|
||||
return cycleStart.AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
default:
|
||||
return cycleStart.AddDate(0, 1, 0).Add(-time.Nanosecond) // Default to monthly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user