feat: Fix type conversions, rename ExchangeRateProvider to RateProviderType, and improve currency integration

- Fix GetRevenueAnalytics return type by dereferencing metrics struct
- Fix getCarrierMetrics to use int64 for Count and convert to int for ActiveCarriers
- Rename ExchangeRateProvider to RateProviderType for consistency
- Change RatePlanCurrencyIntegrator.exchangeService from pointer to interface type
- Add function documentation comments for NewRatePlanCurrencyIntegrator, SubscribeToPlanWith
This commit is contained in:
nutcas3
2026-05-03 10:52:29 +03:00
parent 79fa56f25d
commit c6fccc893d
3 changed files with 49 additions and 32 deletions
@@ -64,7 +64,8 @@ func (s *Service) GetDashboard(ctx context.Context, filter *AnalyticsFilter) (*D
// GetRevenueAnalytics retrieves detailed revenue analytics
func (s *Service) GetRevenueAnalytics(ctx context.Context, filter *AnalyticsFilter) (*RevenueMetrics, error) {
return s.getRevenueMetrics(ctx, filter)
metrics, err := s.getRevenueMetrics(ctx, filter)
return &metrics, err
}
func (s *Service) getRevenueMetrics(ctx context.Context, filter *AnalyticsFilter) (RevenueMetrics, error) {
@@ -141,9 +142,11 @@ func (s *Service) getCarrierMetrics(ctx context.Context, filter *AnalyticsFilter
}
// Query carrier stats
var activeCount int64
s.db.WithContext(ctx).Table("carriers").
Where("is_active = ?", true).
Count((*int64)(&metrics.ActiveCarriers))
Count(&activeCount)
metrics.ActiveCarriers = int(activeCount)
return metrics, nil
}
@@ -11,19 +11,19 @@ import (
"github.com/sirupsen/logrus"
)
// ExchangeRateProvider defines external rate providers
type ExchangeRateProvider string
// RateProviderType defines external rate providers
type RateProviderType string
const (
ProviderOpenExchange ExchangeRateProvider = "openexchangerates"
ProviderFixer ExchangeRateProvider = "fixer"
ProviderXE ExchangeRateProvider = "xe"
ProviderInternal ExchangeRateProvider = "internal"
ProviderOpenExchange RateProviderType = "openexchangerates"
ProviderFixer RateProviderType = "fixer"
ProviderXE RateProviderType = "xe"
ProviderInternal RateProviderType = "internal"
)
// RealTimeExchangeService provides real-time exchange rates
type RealTimeExchangeService struct {
provider ExchangeRateProvider
provider RateProviderType
apiKey string
baseCurrency string
rates map[string]float64
@@ -36,7 +36,7 @@ type RealTimeExchangeService struct {
// ExchangeRateConfig configures the exchange rate service
type ExchangeRateConfig struct {
Provider ExchangeRateProvider
Provider RateProviderType
APIKey string
BaseCurrency string
CacheTTL time.Duration
@@ -13,15 +13,16 @@ import (
type RatePlanCurrencyIntegrator struct {
billingService currency.BillingService
exchangeService *currency.ExchangeRateService
exchangeService currency.ExchangeRateService
ratePlanService rateplan.Service
logger *logrus.Logger
baseCurrency string
}
// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator
func NewRatePlanCurrencyIntegrator(
billingService currency.BillingService,
exchangeService *currency.ExchangeRateService,
exchangeService currency.ExchangeRateService,
ratePlanService rateplan.Service,
logger *logrus.Logger,
baseCurrency string,
@@ -35,22 +36,24 @@ func NewRatePlanCurrencyIntegrator(
}
}
// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion
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 {
return nil, fmt.Errorf("failed to get rate plan: %w", err)
}
// Convert price to requested currency if needed
subscriptionPrice := plan.BasePrice
exchangeRate := 1.0
if targetCurrency != plan.Currency {
conversionReq := &currency.CurrencyConversionRequest{
conversion, err := rpci.billingService.ConvertAmount(ctx, &currency.CurrencyConversionRequest{
Amount: plan.BasePrice,
FromCurrency: plan.Currency,
ToCurrency: targetCurrency,
}
conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq)
})
if err != nil {
rpci.logger.WithError(err).Error("Failed to convert rate plan price")
return nil, fmt.Errorf("currency conversion failed: %w", err)
@@ -59,19 +62,28 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
exchangeRate = conversion.ExchangeRate
}
metadata := map[string]any{
// Create subscription with currency information
subscription := &rateplan.RatePlanSubscription{
ProfileID: profileID,
RatePlanID: planID,
Status: rateplan.SubscriptionStatusActive,
StartedAt: time.Now(),
Metadata: map[string]any{
"original_currency": plan.Currency,
"subscription_currency": targetCurrency,
"original_price": plan.BasePrice,
"subscription_price": subscriptionPrice,
"exchange_rate": exchangeRate,
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
subscribeReq := &rateplan.SubscribeRequest{
ProfileID: profileID,
RatePlanID: planID,
AutoRenew: true,
Metadata: metadata,
Metadata: subscription.Metadata,
}
createdSubscription, err := rpci.ratePlanService.SubscribeToPlan(ctx, subscribeReq)
@@ -79,6 +91,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
return nil, fmt.Errorf("failed to create subscription: %w", err)
}
// Process initial billing
billingReq := &currency.BillingRequest{
ProfileID: profileID,
SubscriptionID: createdSubscription.ID,
@@ -104,14 +117,18 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
return createdSubscription, nil
}
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency
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 {
return nil, fmt.Errorf("failed to get rate plan: %w", err)
}
// Calculate base cost
baseCost := plan.BasePrice
// Add overage costs if usage data is provided
if usageData != nil {
overageCost, err := rpci.calculateOverageCost(ctx, plan, usageData)
if err != nil {
@@ -121,16 +138,12 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
}
}
// Convert to requested currency
convertedCost := baseCost
exchangeRate := 1.0
if targetCurrency != plan.Currency {
conversionReq := &currency.CurrencyConversionRequest{
Amount: baseCost,
FromCurrency: plan.Currency,
ToCurrency: targetCurrency,
}
conversion, err := rpci.billingService.ConvertAmount(ctx, conversionReq)
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency)
if err != nil {
return nil, fmt.Errorf("currency conversion failed: %w", err)
}
@@ -138,6 +151,7 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
exchangeRate = conversion.ExchangeRate
}
// Create billing summary
summary := &currency.BillingSummary{
ProfileID: usageData.ProfileID,
TotalAmount: convertedCost,
@@ -160,13 +174,11 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
return summary, nil
}
// calculateOverageCost calculates overage costs for usage
func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) {
// Check for context cancellation before calculation
if err := ctx.Err(); err != nil {
return 0.0, fmt.Errorf("overage calculation cancelled: %w", err)
}
overageCost := 0.0
// Calculate data overage
if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount {
dataOverage := usage.DataUsed - plan.DataAllowance.Amount
if plan.OverageRates != nil {
@@ -174,6 +186,7 @@ func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context
}
}
// Calculate voice overage
if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes {
voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes
if plan.OverageRates != nil {
@@ -181,6 +194,7 @@ func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context
}
}
// Calculate SMS overage
if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages {
smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages
if plan.OverageRates != nil {