refactor: Rename service package to services and move repository implementations to repository package

- Rename internal/service to internal/services in carrier-connector
- Update service imports in selection_integration.go and smdp_integration.go
- Move currency repository files from internal/currency to internal/repository
- Move rate plan repository files from internal/rateplan to internal/repository
- Update package declarations and imports for currency domain types
- Add currency package imports
This commit is contained in:
nutcas3
2026-05-02 01:21:18 +03:00
parent c93dd63474
commit 295649bcea
21 changed files with 163 additions and 177 deletions
@@ -8,15 +8,15 @@ import (
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/service" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/services"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp"
) )
type SelectionIntegration struct { type SelectionIntegration struct {
manager *smdp.SMDPManager manager *smdp.SMDPManager
selectionService *service.SelectionService selectionService *services.SelectionService
selectionHandler *handlers.SelectionHandler selectionHandler *handlers.SelectionHandler
smdpService *service.SMDPService smdpService *services.SMDPService
server *http.Server server *http.Server
} }
@@ -33,10 +33,10 @@ func NewSelectionIntegration(repo *repository.PostgresProfileStore) *SelectionIn
manager := smdp.NewSMDPManager(repo, config) manager := smdp.NewSMDPManager(repo, config)
// Create selection service // Create selection service
selectionService := service.NewSelectionService(manager) selectionService := services.NewSelectionService(manager)
// Create SMDP service // Create SMDP service
smdpService := service.NewSMDPService(repo) smdpService := services.NewSMDPService(repo)
return &SelectionIntegration{ return &SelectionIntegration{
manager: manager, manager: manager,
@@ -85,7 +85,7 @@ func (si *SelectionIntegration) GetManager() *smdp.SMDPManager {
} }
// GetSelectionService returns the selection service for testing // GetSelectionService returns the selection service for testing
func (si *SelectionIntegration) GetSelectionService() *service.SelectionService { func (si *SelectionIntegration) GetSelectionService() *services.SelectionService {
return si.selectionService return si.selectionService
} }
@@ -6,14 +6,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/handlers"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/service" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/services"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/smdp"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// SMDPIntegration integrates the multi-carrier SM-DP+ system with the existing carrier connector // SMDPIntegration integrates the multi-carrier SM-DP+ system with the existing carrier connector
type SMDPIntegration struct { type SMDPIntegration struct {
service *service.SMDPService service *services.SMDPService
handler *handlers.SMDPHandler handler *handlers.SMDPHandler
repository *repository.PostgresProfileStore repository *repository.PostgresProfileStore
logger *logrus.Logger logger *logrus.Logger
@@ -24,7 +24,7 @@ func NewSMDPIntegration(repo *repository.PostgresProfileStore) *SMDPIntegration
logger := logrus.New() logger := logrus.New()
logger.SetLevel(logrus.InfoLevel) logger.SetLevel(logrus.InfoLevel)
svc := service.NewSMDPService(repo) svc := services.NewSMDPService(repo)
manager := svc.GetManager() manager := svc.GetManager()
hnd := handlers.NewSMDPHandler(manager) hnd := handlers.NewSMDPHandler(manager)
@@ -148,7 +148,7 @@ func (i *SMDPIntegration) metricsHandler(c *gin.Context) {
} }
// GetService returns the SM-DP+ service for direct access // GetService returns the SM-DP+ service for direct access
func (i *SMDPIntegration) GetService() *service.SMDPService { func (i *SMDPIntegration) GetService() *services.SMDPService {
return i.service return i.service
} }
@@ -1,9 +1,10 @@
package currency package repository
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -23,7 +24,7 @@ func NewGormRepository(db *gorm.DB, logger *logrus.Logger) *GormRepository {
} }
// CreateCurrency creates a new currency // CreateCurrency creates a new currency
func (r *GormRepository) CreateCurrency(ctx context.Context, currency *Currency) error { func (r *GormRepository) CreateCurrency(ctx context.Context, currency *currency.Currency) error {
model := r.currencyToModel(currency) model := r.currencyToModel(currency)
if err := r.db.WithContext(ctx).Create(model).Error; err != nil { if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
@@ -35,8 +36,8 @@ func (r *GormRepository) CreateCurrency(ctx context.Context, currency *Currency)
} }
// GetCurrency retrieves a currency by code // GetCurrency retrieves a currency by code
func (r *GormRepository) GetCurrency(ctx context.Context, code string) (*Currency, error) { func (r *GormRepository) GetCurrency(ctx context.Context, code string) (*currency.Currency, error) {
var model CurrencyModel var model currency.CurrencyModel
if err := r.db.WithContext(ctx).Where("code = ?", code).First(&model).Error; err != nil { if err := r.db.WithContext(ctx).Where("code = ?", code).First(&model).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("currency not found: %s", code) return nil, fmt.Errorf("currency not found: %s", code)
@@ -49,7 +50,7 @@ func (r *GormRepository) GetCurrency(ctx context.Context, code string) (*Currenc
} }
// UpdateCurrency updates an existing currency // UpdateCurrency updates an existing currency
func (r *GormRepository) UpdateCurrency(ctx context.Context, currency *Currency) error { func (r *GormRepository) UpdateCurrency(ctx context.Context, currency *currency.Currency) error {
model := r.currencyToModel(currency) model := r.currencyToModel(currency)
if err := r.db.WithContext(ctx).Save(model).Error; err != nil { if err := r.db.WithContext(ctx).Save(model).Error; err != nil {
@@ -62,7 +63,7 @@ func (r *GormRepository) UpdateCurrency(ctx context.Context, currency *Currency)
// DeleteCurrency deletes a currency // DeleteCurrency deletes a currency
func (r *GormRepository) DeleteCurrency(ctx context.Context, code string) error { func (r *GormRepository) DeleteCurrency(ctx context.Context, code string) error {
if err := r.db.WithContext(ctx).Delete(&CurrencyModel{}, "code = ?", code).Error; err != nil { if err := r.db.WithContext(ctx).Delete(&currency.CurrencyModel{}, "code = ?", code).Error; err != nil {
r.logger.WithError(err).Error("Failed to delete currency") r.logger.WithError(err).Error("Failed to delete currency")
return fmt.Errorf("failed to delete currency: %w", err) return fmt.Errorf("failed to delete currency: %w", err)
} }
@@ -71,8 +72,8 @@ func (r *GormRepository) DeleteCurrency(ctx context.Context, code string) error
} }
// ListCurrencies retrieves currencies based on filter // ListCurrencies retrieves currencies based on filter
func (r *GormRepository) ListCurrencies(ctx context.Context, filter *CurrencyFilter) ([]*Currency, error) { func (r *GormRepository) ListCurrencies(ctx context.Context, filter *currency.CurrencyFilter) ([]*currency.Currency, error) {
query := r.db.WithContext(ctx).Model(&CurrencyModel{}) query := r.db.WithContext(ctx).Model(&currency.CurrencyModel{})
// Apply filters // Apply filters
if filter.IsActive != nil { if filter.IsActive != nil {
@@ -101,13 +102,13 @@ func (r *GormRepository) ListCurrencies(ctx context.Context, filter *CurrencyFil
query = query.Offset(filter.Offset) query = query.Offset(filter.Offset)
} }
var models []CurrencyModel var models []currency.CurrencyModel
if err := query.Find(&models).Error; err != nil { if err := query.Find(&models).Error; err != nil {
r.logger.WithError(err).Error("Failed to list currencies") r.logger.WithError(err).Error("Failed to list currencies")
return nil, fmt.Errorf("failed to list currencies: %w", err) return nil, fmt.Errorf("failed to list currencies: %w", err)
} }
currencies := make([]*Currency, 0, len(models)) currencies := make([]*currency.Currency, 0, len(models))
for _, model := range models { for _, model := range models {
curr, err := r.modelToCurrency(&model) curr, err := r.modelToCurrency(&model)
if err != nil { if err != nil {
@@ -121,8 +122,8 @@ func (r *GormRepository) ListCurrencies(ctx context.Context, filter *CurrencyFil
} }
// CountCurrencies counts currencies based on filter // CountCurrencies counts currencies based on filter
func (r *GormRepository) CountCurrencies(ctx context.Context, filter *CurrencyFilter) (int, error) { func (r *GormRepository) CountCurrencies(ctx context.Context, filter *currency.CurrencyFilter) (int, error) {
query := r.db.WithContext(ctx).Model(&CurrencyModel{}) query := r.db.WithContext(ctx).Model(&currency.CurrencyModel{})
// Apply filters // Apply filters
if filter.IsActive != nil { if filter.IsActive != nil {
@@ -142,7 +143,7 @@ func (r *GormRepository) CountCurrencies(ctx context.Context, filter *CurrencyFi
} }
// CreateExchangeRate creates a new exchange rate // CreateExchangeRate creates a new exchange rate
func (r *GormRepository) CreateExchangeRate(ctx context.Context, rate *ExchangeRate) error { func (r *GormRepository) CreateExchangeRate(ctx context.Context, rate *currency.ExchangeRate) error {
model := r.exchangeRateToModel(rate) model := r.exchangeRateToModel(rate)
if err := r.db.WithContext(ctx).Create(model).Error; err != nil { if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
@@ -154,8 +155,8 @@ func (r *GormRepository) CreateExchangeRate(ctx context.Context, rate *ExchangeR
} }
// GetExchangeRate retrieves an exchange rate by ID // GetExchangeRate retrieves an exchange rate by ID
func (r *GormRepository) GetExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*ExchangeRate, error) { func (r *GormRepository) GetExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*currency.ExchangeRate, error) {
var model ExchangeRateModel var model currency.ExchangeRateModel
if err := r.db.WithContext(ctx).Where("from_currency = ? AND to_currency = ?", fromCurrency, toCurrency).First(&model).Error; err != nil { if err := r.db.WithContext(ctx).Where("from_currency = ? AND to_currency = ?", fromCurrency, toCurrency).First(&model).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("exchange rate not found: %s to %s", fromCurrency, toCurrency) return nil, fmt.Errorf("exchange rate not found: %s to %s", fromCurrency, toCurrency)
@@ -168,7 +169,7 @@ func (r *GormRepository) GetExchangeRate(ctx context.Context, fromCurrency, toCu
} }
// UpdateExchangeRate updates an existing exchange rate // UpdateExchangeRate updates an existing exchange rate
func (r *GormRepository) UpdateExchangeRate(ctx context.Context, rate *ExchangeRate) error { func (r *GormRepository) UpdateExchangeRate(ctx context.Context, rate *currency.ExchangeRate) error {
model := r.exchangeRateToModel(rate) model := r.exchangeRateToModel(rate)
if err := r.db.WithContext(ctx).Save(model).Error; err != nil { if err := r.db.WithContext(ctx).Save(model).Error; err != nil {
@@ -181,7 +182,7 @@ func (r *GormRepository) UpdateExchangeRate(ctx context.Context, rate *ExchangeR
// DeleteExchangeRate deletes an exchange rate // DeleteExchangeRate deletes an exchange rate
func (r *GormRepository) DeleteExchangeRate(ctx context.Context, id string) error { func (r *GormRepository) DeleteExchangeRate(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&ExchangeRateModel{}, "id = ?", id).Error; err != nil { if err := r.db.WithContext(ctx).Delete(&currency.ExchangeRateModel{}, "id = ?", id).Error; err != nil {
r.logger.WithError(err).Error("Failed to delete exchange rate") r.logger.WithError(err).Error("Failed to delete exchange rate")
return fmt.Errorf("failed to delete exchange rate: %w", err) return fmt.Errorf("failed to delete exchange rate: %w", err)
} }
@@ -1,14 +1,16 @@
package currency package repository
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
) )
// currencyToModel converts currency domain model to database model // currencyToModel converts currency domain model to database model
func (r *GormRepository) currencyToModel(currency *Currency) *CurrencyModel { func (r *GormRepository) currencyToModel(currency *currency.Currency) *currency.CurrencyModel {
return &CurrencyModel{ return &currency.CurrencyModel{
Code: currency.Code, Code: currency.Code,
Name: currency.Name, Name: currency.Name,
Symbol: currency.Symbol, Symbol: currency.Symbol,
@@ -21,13 +23,13 @@ func (r *GormRepository) currencyToModel(currency *Currency) *CurrencyModel {
} }
// modelToCurrency converts database model to currency domain model // modelToCurrency converts database model to currency domain model
func (r *GormRepository) modelToCurrency(model *CurrencyModel) (*Currency, error) { func (r *GormRepository) modelToCurrency(model *currency.CurrencyModel) (*currency.Currency, error) {
var regions []string var regions []string
if model.SupportedRegions != "" { if model.SupportedRegions != "" {
regions = strings.Split(model.SupportedRegions, ",") regions = strings.Split(model.SupportedRegions, ",")
} }
return &Currency{ return &currency.Currency{
Code: model.Code, Code: model.Code,
Name: model.Name, Name: model.Name,
Symbol: model.Symbol, Symbol: model.Symbol,
@@ -40,8 +42,8 @@ func (r *GormRepository) modelToCurrency(model *CurrencyModel) (*Currency, error
} }
// exchangeRateToModel converts exchange rate domain model to database model // exchangeRateToModel converts exchange rate domain model to database model
func (r *GormRepository) exchangeRateToModel(rate *ExchangeRate) *ExchangeRateModel { func (r *GormRepository) exchangeRateToModel(rate *currency.ExchangeRate) *currency.ExchangeRateModel {
return &ExchangeRateModel{ return &currency.ExchangeRateModel{
ID: rate.ID, ID: rate.ID,
FromCurrency: rate.FromCurrency, FromCurrency: rate.FromCurrency,
ToCurrency: rate.ToCurrency, ToCurrency: rate.ToCurrency,
@@ -56,8 +58,8 @@ func (r *GormRepository) exchangeRateToModel(rate *ExchangeRate) *ExchangeRateMo
} }
// modelToExchangeRate converts database model to exchange rate domain model // modelToExchangeRate converts database model to exchange rate domain model
func (r *GormRepository) modelToExchangeRate(model *ExchangeRateModel) (*ExchangeRate, error) { func (r *GormRepository) modelToExchangeRate(model *currency.ExchangeRateModel) (*currency.ExchangeRate, error) {
return &ExchangeRate{ return &currency.ExchangeRate{
ID: model.ID, ID: model.ID,
FromCurrency: model.FromCurrency, FromCurrency: model.FromCurrency,
ToCurrency: model.ToCurrency, ToCurrency: model.ToCurrency,
@@ -72,7 +74,7 @@ func (r *GormRepository) modelToExchangeRate(model *ExchangeRateModel) (*Exchang
} }
// transactionToModel converts transaction domain model to database model // transactionToModel converts transaction domain model to database model
func (r *GormRepository) transactionToModel(transaction *Transaction) *TransactionModel { func (r *GormRepository) transactionToModel(transaction *currency.Transaction) *currency.TransactionModel {
metadata := "" metadata := ""
if transaction.Metadata != nil { if transaction.Metadata != nil {
if data, err := json.Marshal(transaction.Metadata); err == nil { if data, err := json.Marshal(transaction.Metadata); err == nil {
@@ -80,7 +82,7 @@ func (r *GormRepository) transactionToModel(transaction *Transaction) *Transacti
} }
} }
return &TransactionModel{ return &currency.TransactionModel{
ID: transaction.ID, ID: transaction.ID,
ProfileID: transaction.ProfileID, ProfileID: transaction.ProfileID,
SubscriptionID: transaction.SubscriptionID, SubscriptionID: transaction.SubscriptionID,
@@ -99,7 +101,7 @@ func (r *GormRepository) transactionToModel(transaction *Transaction) *Transacti
} }
// modelToTransaction converts database model to transaction domain model // modelToTransaction converts database model to transaction domain model
func (r *GormRepository) modelToTransaction(model *TransactionModel) (*Transaction, error) { func (r *GormRepository) modelToTransaction(model *currency.TransactionModel) (*currency.Transaction, error) {
var metadata map[string]interface{} var metadata map[string]interface{}
if model.Metadata != "" { if model.Metadata != "" {
if err := json.Unmarshal([]byte(model.Metadata), &metadata); err != nil { if err := json.Unmarshal([]byte(model.Metadata), &metadata); err != nil {
@@ -107,18 +109,18 @@ func (r *GormRepository) modelToTransaction(model *TransactionModel) (*Transacti
} }
} }
return &Transaction{ return &currency.Transaction{
ID: model.ID, ID: model.ID,
ProfileID: model.ProfileID, ProfileID: model.ProfileID,
SubscriptionID: model.SubscriptionID, SubscriptionID: model.SubscriptionID,
Type: TransactionType(model.Type), Type: currency.TransactionType(model.Type),
Amount: model.Amount, Amount: model.Amount,
Currency: model.Currency, Currency: model.Currency,
BaseAmount: model.BaseAmount, BaseAmount: model.BaseAmount,
BaseCurrency: model.BaseCurrency, BaseCurrency: model.BaseCurrency,
ExchangeRate: model.ExchangeRate, ExchangeRate: model.ExchangeRate,
Description: model.Description, Description: model.Description,
Status: TransactionStatus(model.Status), Status: currency.TransactionStatus(model.Status),
Metadata: metadata, Metadata: metadata,
CreatedAt: model.CreatedAt, CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt, UpdatedAt: model.UpdatedAt,
@@ -1,27 +1,13 @@
package rateplan package repository
import ( import (
"context" "context"
"fmt" "fmt"
"time" "time"
"github.com/sirupsen/logrus"
"gorm.io/gorm" "gorm.io/gorm"
) )
// GormRepository implements the Repository interface using GORM
type GormRepository struct {
db *gorm.DB
logger *logrus.Logger
}
// NewGormRepository creates a new GORM repository
func NewGormRepository(db *gorm.DB, logger *logrus.Logger) *GormRepository {
return &GormRepository{
db: db,
logger: logger,
}
}
// CreateUsage creates a new usage record // CreateUsage creates a new usage record
func (r *GormRepository) CreateUsage(ctx context.Context, usage *RatePlanUsage) error { func (r *GormRepository) CreateUsage(ctx context.Context, usage *RatePlanUsage) error {
usage.LastUpdated = time.Now() usage.LastUpdated = time.Now()
@@ -1,4 +1,4 @@
package rateplan package repository
import ( import (
"context" "context"
@@ -6,6 +6,8 @@ import (
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
) )
// CreateRatePlan creates a new rate plan // CreateRatePlan creates a new rate plan
@@ -1,4 +1,4 @@
package rateplan package repository
import ( import (
"context" "context"
@@ -7,6 +7,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// CreateSubscription creates a new rate plan subscription // CreateSubscription creates a new rate plan subscription
func (r *GormRepository) CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error { func (r *GormRepository) CreateSubscription(ctx context.Context, subscription *RatePlanSubscription) error {
now := time.Now() now := time.Now()
@@ -85,6 +86,7 @@ func (r *GormRepository) DeleteSubscription(ctx context.Context, id string) erro
return nil return nil
} }
// ListSubscriptions retrieves subscriptions for a profile // ListSubscriptions retrieves subscriptions for a profile
func (r *GormRepository) ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) { func (r *GormRepository) ListSubscriptions(ctx context.Context, profileID string, filter *SubscriptionFilter) ([]*RatePlanSubscription, error) {
query := r.db.WithContext(ctx).Where("profile_id = ?", profileID) query := r.db.WithContext(ctx).Where("profile_id = ?", profileID)
@@ -1,4 +1,4 @@
package currency package repository
import ( import (
"context" "context"
@@ -6,11 +6,13 @@ import (
"time" "time"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
) )
// ListExchangeRates retrieves exchange rates based on filter // ListExchangeRates retrieves exchange rates based on filter
func (r *GormRepository) ListExchangeRates(ctx context.Context, filter *ExchangeRateFilter) ([]*ExchangeRate, error) { func (r *GormRepository) ListExchangeRates(ctx context.Context, filter *currency.ExchangeRateFilter) ([]*currency.ExchangeRate, error) {
query := r.db.WithContext(ctx).Model(&ExchangeRateModel{}) query := r.db.WithContext(ctx).Model(&currency.ExchangeRateModel{})
// Apply filters // Apply filters
if filter.FromCurrency != "" { if filter.FromCurrency != "" {
@@ -50,13 +52,13 @@ func (r *GormRepository) ListExchangeRates(ctx context.Context, filter *Exchange
query = query.Offset(filter.Offset) query = query.Offset(filter.Offset)
} }
var models []ExchangeRateModel var models []currency.ExchangeRateModel
if err := query.Find(&models).Error; err != nil { if err := query.Find(&models).Error; err != nil {
r.logger.WithError(err).Error("Failed to list exchange rates") r.logger.WithError(err).Error("Failed to list exchange rates")
return nil, fmt.Errorf("failed to list exchange rates: %w", err) return nil, fmt.Errorf("failed to list exchange rates: %w", err)
} }
rates := make([]*ExchangeRate, 0, len(models)) rates := make([]*currency.ExchangeRate, 0, len(models))
for _, model := range models { for _, model := range models {
rate, err := r.modelToExchangeRate(&model) rate, err := r.modelToExchangeRate(&model)
if err != nil { if err != nil {
@@ -70,9 +72,9 @@ func (r *GormRepository) ListExchangeRates(ctx context.Context, filter *Exchange
} }
// GetLatestExchangeRate gets the latest valid exchange rate // GetLatestExchangeRate gets the latest valid exchange rate
func (r *GormRepository) GetLatestExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*ExchangeRate, error) { func (r *GormRepository) GetLatestExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*currency.ExchangeRate, error) {
now := time.Now() now := time.Now()
var model ExchangeRateModel var model currency.ExchangeRateModel
if err := r.db.WithContext(ctx). if err := r.db.WithContext(ctx).
Where("from_currency = ? AND to_currency = ? AND is_active = ?", fromCurrency, toCurrency, true). Where("from_currency = ? AND to_currency = ? AND is_active = ?", fromCurrency, toCurrency, true).
@@ -1,14 +1,16 @@
package currency package repository
import ( import (
"context" "context"
"fmt" "fmt"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
) )
// CreateTransaction creates a new transaction // CreateTransaction creates a new transaction
func (r *GormRepository) CreateTransaction(ctx context.Context, transaction *Transaction) error { func (r *GormRepository) CreateTransaction(ctx context.Context, transaction *currency.Transaction) error {
model := r.transactionToModel(transaction) model := r.transactionToModel(transaction)
if err := r.db.WithContext(ctx).Create(model).Error; err != nil { if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
@@ -20,8 +22,8 @@ func (r *GormRepository) CreateTransaction(ctx context.Context, transaction *Tra
} }
// GetTransaction retrieves a transaction by ID // GetTransaction retrieves a transaction by ID
func (r *GormRepository) GetTransaction(ctx context.Context, id string) (*Transaction, error) { func (r *GormRepository) GetTransaction(ctx context.Context, id string) (*currency.Transaction, error) {
var model TransactionModel var model currency.TransactionModel
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil { if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("transaction not found: %s", id) return nil, fmt.Errorf("transaction not found: %s", id)
@@ -34,7 +36,7 @@ func (r *GormRepository) GetTransaction(ctx context.Context, id string) (*Transa
} }
// UpdateTransaction updates an existing transaction // UpdateTransaction updates an existing transaction
func (r *GormRepository) UpdateTransaction(ctx context.Context, transaction *Transaction) error { func (r *GormRepository) UpdateTransaction(ctx context.Context, transaction *currency.Transaction) error {
model := r.transactionToModel(transaction) model := r.transactionToModel(transaction)
if err := r.db.WithContext(ctx).Save(model).Error; err != nil { if err := r.db.WithContext(ctx).Save(model).Error; err != nil {
@@ -47,7 +49,7 @@ func (r *GormRepository) UpdateTransaction(ctx context.Context, transaction *Tra
// DeleteTransaction deletes a transaction // DeleteTransaction deletes a transaction
func (r *GormRepository) DeleteTransaction(ctx context.Context, id string) error { func (r *GormRepository) DeleteTransaction(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&TransactionModel{}, "id = ?", id).Error; err != nil { if err := r.db.WithContext(ctx).Delete(&currency.TransactionModel{}, "id = ?", id).Error; err != nil {
r.logger.WithError(err).Error("Failed to delete transaction") r.logger.WithError(err).Error("Failed to delete transaction")
return fmt.Errorf("failed to delete transaction: %w", err) return fmt.Errorf("failed to delete transaction: %w", err)
} }
@@ -56,8 +58,8 @@ func (r *GormRepository) DeleteTransaction(ctx context.Context, id string) error
} }
// ListTransactions retrieves transactions based on filter // ListTransactions retrieves transactions based on filter
func (r *GormRepository) ListTransactions(ctx context.Context, filter *TransactionFilter) ([]*Transaction, error) { func (r *GormRepository) ListTransactions(ctx context.Context, filter *currency.TransactionFilter) ([]*currency.Transaction, error) {
query := r.db.WithContext(ctx).Model(&TransactionModel{}) query := r.db.WithContext(ctx).Model(&currency.TransactionModel{})
// Apply filters // Apply filters
if filter.ProfileID != "" { if filter.ProfileID != "" {
@@ -107,13 +109,13 @@ func (r *GormRepository) ListTransactions(ctx context.Context, filter *Transacti
query = query.Offset(filter.Offset) query = query.Offset(filter.Offset)
} }
var models []TransactionModel var models []currency.TransactionModel
if err := query.Find(&models).Error; err != nil { if err := query.Find(&models).Error; err != nil {
r.logger.WithError(err).Error("Failed to list transactions") r.logger.WithError(err).Error("Failed to list transactions")
return nil, fmt.Errorf("failed to list transactions: %w", err) return nil, fmt.Errorf("failed to list transactions: %w", err)
} }
transactions := make([]*Transaction, 0, len(models)) transactions := make([]*currency.Transaction, 0, len(models))
for _, model := range models { for _, model := range models {
tx, err := r.modelToTransaction(&model) tx, err := r.modelToTransaction(&model)
if err != nil { if err != nil {
@@ -127,8 +129,8 @@ func (r *GormRepository) ListTransactions(ctx context.Context, filter *Transacti
} }
// CountTransactions counts transactions based on filter // CountTransactions counts transactions based on filter
func (r *GormRepository) CountTransactions(ctx context.Context, filter *TransactionFilter) (int, error) { func (r *GormRepository) CountTransactions(ctx context.Context, filter *currency.TransactionFilter) (int, error) {
query := r.db.WithContext(ctx).Model(&TransactionModel{}) query := r.db.WithContext(ctx).Model(&currency.TransactionModel{})
// Apply filters // Apply filters
if filter.ProfileID != "" { if filter.ProfileID != "" {
@@ -1,21 +1,22 @@
package currency package services
import ( import (
"context" "context"
"fmt" "fmt"
"time" "time"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// AnalyticsServiceImpl handles currency analytics operations // AnalyticsServiceImpl handles currency analytics operations
type AnalyticsServiceImpl struct { type AnalyticsServiceImpl struct {
repository Repository repository currency.Repository
logger *logrus.Logger logger *logrus.Logger
} }
// NewAnalyticsService creates a new analytics service // NewAnalyticsService creates a new analytics service
func NewAnalyticsService(repository Repository, logger *logrus.Logger) *AnalyticsServiceImpl { func NewAnalyticsService(repository currency.Repository, logger *logrus.Logger) *AnalyticsServiceImpl {
return &AnalyticsServiceImpl{ return &AnalyticsServiceImpl{
repository: repository, repository: repository,
logger: logger, logger: logger,
@@ -23,12 +24,12 @@ func NewAnalyticsService(repository Repository, logger *logrus.Logger) *Analytic
} }
// GetRevenueByCurrency calculates revenue breakdown by currency // GetRevenueByCurrency calculates revenue breakdown by currency
func (s *AnalyticsServiceImpl) GetRevenueByCurrency(ctx context.Context, filter *TransactionFilter) (map[string]float64, error) { func (s *AnalyticsServiceImpl) GetRevenueByCurrency(ctx context.Context, filter *currency.TransactionFilter) (map[string]float64, error) {
if filter == nil { if filter == nil {
filter = &TransactionFilter{} filter = &currency.TransactionFilter{}
} }
filter.Status = TransactionStatusCompleted filter.Status = currency.TransactionStatusCompleted
transactions, err := s.repository.ListTransactions(ctx, filter) transactions, err := s.repository.ListTransactions(ctx, filter)
if err != nil { if err != nil {
@@ -39,7 +40,7 @@ func (s *AnalyticsServiceImpl) GetRevenueByCurrency(ctx context.Context, filter
revenueByCurrency := make(map[string]float64) revenueByCurrency := make(map[string]float64)
for _, tx := range transactions { for _, tx := range transactions {
if tx.Type == TransactionTypeSubscription || tx.Type == TransactionTypeUsage || tx.Type == TransactionTypeOverage { if tx.Type == currency.TransactionTypeSubscription || tx.Type == currency.TransactionTypeUsage || tx.Type == currency.TransactionTypeOverage {
revenueByCurrency[tx.Currency] += tx.Amount revenueByCurrency[tx.Currency] += tx.Amount
} }
} }
@@ -48,9 +49,9 @@ func (s *AnalyticsServiceImpl) GetRevenueByCurrency(ctx context.Context, filter
} }
// GetTransactionVolumeByCurrency calculates transaction volume by currency // GetTransactionVolumeByCurrency calculates transaction volume by currency
func (s *AnalyticsServiceImpl) GetTransactionVolumeByCurrency(ctx context.Context, filter *TransactionFilter) (map[string]int64, error) { func (s *AnalyticsServiceImpl) GetTransactionVolumeByCurrency(ctx context.Context, filter *currency.TransactionFilter) (map[string]int64, error) {
if filter == nil { if filter == nil {
filter = &TransactionFilter{} filter = &currency.TransactionFilter{}
} }
transactions, err := s.repository.ListTransactions(ctx, filter) transactions, err := s.repository.ListTransactions(ctx, filter)
@@ -69,8 +70,8 @@ func (s *AnalyticsServiceImpl) GetTransactionVolumeByCurrency(ctx context.Contex
} }
// GetExchangeRateTrends retrieves exchange rate trends for a currency pair // GetExchangeRateTrends retrieves exchange rate trends for a currency pair
func (s *AnalyticsServiceImpl) GetExchangeRateTrends(ctx context.Context, fromCurrency, toCurrency string, days int) ([]*ExchangeRate, error) { func (s *AnalyticsServiceImpl) GetExchangeRateTrends(ctx context.Context, fromCurrency, toCurrency string, days int) ([]*currency.ExchangeRate, error) {
filter := &ExchangeRateFilter{ filter := &currency.ExchangeRateFilter{
FromCurrency: fromCurrency, FromCurrency: fromCurrency,
ToCurrency: toCurrency, ToCurrency: toCurrency,
IsValid: &[]bool{false}[0], // Include historical rates IsValid: &[]bool{false}[0], // Include historical rates
@@ -89,15 +90,15 @@ func (s *AnalyticsServiceImpl) GetExchangeRateTrends(ctx context.Context, fromCu
} }
// GetCurrencyUsageStats retrieves currency usage statistics // GetCurrencyUsageStats retrieves currency usage statistics
func (s *AnalyticsServiceImpl) GetCurrencyUsageStats(ctx context.Context) (*CurrencyUsageStats, error) { func (s *AnalyticsServiceImpl) GetCurrencyUsageStats(ctx context.Context) (*currency.CurrencyUsageStats, error) {
// Get total currencies // Get total currencies
totalCurrencies, err := s.repository.CountCurrencies(ctx, &CurrencyFilter{}) totalCurrencies, err := s.repository.CountCurrencies(ctx, &currency.CurrencyFilter{})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count currencies: %w", err) return nil, fmt.Errorf("failed to count currencies: %w", err)
} }
// Get active currencies // Get active currencies
activeCurrencies, err := s.repository.CountCurrencies(ctx, &CurrencyFilter{ activeCurrencies, err := s.repository.CountCurrencies(ctx, &currency.CurrencyFilter{
IsActive: &[]bool{true}[0], IsActive: &[]bool{true}[0],
}) })
if err != nil { if err != nil {
@@ -105,14 +106,14 @@ func (s *AnalyticsServiceImpl) GetCurrencyUsageStats(ctx context.Context) (*Curr
} }
// Get total transactions // Get total transactions
totalTransactions, err := s.repository.CountTransactions(ctx, &TransactionFilter{}) totalTransactions, err := s.repository.CountTransactions(ctx, &currency.TransactionFilter{})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count transactions: %w", err) return nil, fmt.Errorf("failed to count transactions: %w", err)
} }
// Get total volume // Get total volume
transactions, err := s.repository.ListTransactions(ctx, &TransactionFilter{ transactions, err := s.repository.ListTransactions(ctx, &currency.TransactionFilter{
Status: TransactionStatusCompleted, Status: currency.TransactionStatusCompleted,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get transactions for volume: %w", err) return nil, fmt.Errorf("failed to get transactions for volume: %w", err)
@@ -138,12 +139,12 @@ func (s *AnalyticsServiceImpl) GetCurrencyUsageStats(ctx context.Context) (*Curr
} }
// Get exchange rate count (using ListExchangeRates for now since CountExchangeRates doesn't exist) // Get exchange rate count (using ListExchangeRates for now since CountExchangeRates doesn't exist)
exchangeRates, err := s.repository.ListExchangeRates(ctx, &ExchangeRateFilter{}) exchangeRates, err := s.repository.ListExchangeRates(ctx, &currency.ExchangeRateFilter{})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to count exchange rates: %w", err) return nil, fmt.Errorf("failed to count exchange rates: %w", err)
} }
return &CurrencyUsageStats{ return &currency.CurrencyUsageStats{
TotalCurrencies: totalCurrencies, TotalCurrencies: totalCurrencies,
ActiveCurrencies: activeCurrencies, ActiveCurrencies: activeCurrencies,
TotalTransactions: int64(totalTransactions), TotalTransactions: int64(totalTransactions),
@@ -160,8 +161,8 @@ func (s *AnalyticsServiceImpl) GetMonthlyRevenueTrends(ctx context.Context, mont
endDate := time.Now() endDate := time.Now()
startDate := endDate.AddDate(0, -months, 0) startDate := endDate.AddDate(0, -months, 0)
filter := &TransactionFilter{ filter := &currency.TransactionFilter{
Status: TransactionStatusCompleted, Status: currency.TransactionStatusCompleted,
FromDate: &startDate, FromDate: &startDate,
ToDate: &endDate, ToDate: &endDate,
} }
@@ -183,18 +184,18 @@ func (s *AnalyticsServiceImpl) GetMonthlyRevenueTrends(ctx context.Context, mont
} }
// GetTopCurrenciesByRevenue returns top currencies by revenue // GetTopCurrenciesByRevenue returns top currencies by revenue
func (s *AnalyticsServiceImpl) GetTopCurrenciesByRevenue(ctx context.Context, limit int) ([]*CurrencyRevenue, error) { func (s *AnalyticsServiceImpl) GetTopCurrenciesByRevenue(ctx context.Context, limit int) ([]*currency.CurrencyRevenue, error) {
revenueByCurrency, err := s.GetRevenueByCurrency(ctx, &TransactionFilter{ revenueByCurrency, err := s.GetRevenueByCurrency(ctx, &currency.TransactionFilter{
Status: TransactionStatusCompleted, Status: currency.TransactionStatusCompleted,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Convert to slice and sort // Convert to slice and sort
var currencyRevenues []*CurrencyRevenue var currencyRevenues []*currency.CurrencyRevenue
for currency, revenue := range revenueByCurrency { for currency, revenue := range revenueByCurrency {
currencyRevenues = append(currencyRevenues, &CurrencyRevenue{ currencyRevenues = append(currencyRevenues, &currency.CurrencyRevenue{
Currency: currency, Currency: currency,
Revenue: revenue, Revenue: revenue,
}) })
@@ -208,16 +209,10 @@ func (s *AnalyticsServiceImpl) GetTopCurrenciesByRevenue(ctx context.Context, li
return currencyRevenues, nil return currencyRevenues, nil
} }
// CurrencyRevenue represents revenue for a specific currency
type CurrencyRevenue struct {
Currency string `json:"currency"`
Revenue float64 `json:"revenue"`
}
// GetTransactionTypeAnalytics returns analytics by transaction type // GetTransactionTypeAnalytics returns analytics by transaction type
func (s *AnalyticsServiceImpl) GetTransactionTypeAnalytics(ctx context.Context, filter *TransactionFilter) (map[string]*TransactionTypeStats, error) { func (s *AnalyticsServiceImpl) GetTransactionTypeAnalytics(ctx context.Context, filter *currency.TransactionFilter) (map[string]*currency.TransactionTypeStats, error) {
if filter == nil { if filter == nil {
filter = &TransactionFilter{} filter = &currency.TransactionFilter{}
} }
transactions, err := s.repository.ListTransactions(ctx, filter) transactions, err := s.repository.ListTransactions(ctx, filter)
@@ -226,13 +221,13 @@ func (s *AnalyticsServiceImpl) GetTransactionTypeAnalytics(ctx context.Context,
return nil, fmt.Errorf("failed to get transactions: %w", err) return nil, fmt.Errorf("failed to get transactions: %w", err)
} }
typeStats := make(map[string]*TransactionTypeStats) typeStats := make(map[string]*currency.TransactionTypeStats)
for _, tx := range transactions { for _, tx := range transactions {
typeKey := string(tx.Type) typeKey := string(tx.Type)
if _, exists := typeStats[typeKey]; !exists { if _, exists := typeStats[typeKey]; !exists {
typeStats[typeKey] = &TransactionTypeStats{ typeStats[typeKey] = &currency.TransactionTypeStats{
Type: tx.Type, Type: tx.Type,
Count: 0, Count: 0,
Amount: 0.0, Amount: 0.0,
@@ -247,11 +242,3 @@ func (s *AnalyticsServiceImpl) GetTransactionTypeAnalytics(ctx context.Context,
return typeStats, nil return typeStats, nil
} }
// TransactionTypeStats represents statistics for a transaction type
type TransactionTypeStats struct {
Type TransactionType `json:"type"`
Count int `json:"count"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
@@ -1,4 +1,4 @@
package currency package services
import ( import (
"context" "context"
@@ -1,36 +1,37 @@
package currency package services
import ( import (
"context" "context"
"fmt" "fmt"
"time" "time"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type ExchangeRateService struct { type ExchangeRateService struct {
repository Repository repository currency.Repository
logger *logrus.Logger logger *logrus.Logger
providers []ExchangeRateProvider providers []currency.ExchangeRateProvider
baseCurrency string baseCurrency string
} }
func NewExchangeRateService(repository Repository, logger *logrus.Logger, baseCurrency string) *ExchangeRateService { func NewExchangeRateService(repository currency.Repository, logger *logrus.Logger, baseCurrency string) *ExchangeRateService {
return &ExchangeRateService{ return &ExchangeRateService{
repository: repository, repository: repository,
logger: logger, logger: logger,
providers: make([]ExchangeRateProvider, 0), providers: make([]currency.ExchangeRateProvider, 0),
baseCurrency: baseCurrency, baseCurrency: baseCurrency,
} }
} }
func (s *ExchangeRateService) AddProvider(provider ExchangeRateProvider) { func (s *ExchangeRateService) AddProvider(provider currency.ExchangeRateProvider) {
s.providers = append(s.providers, provider) s.providers = append(s.providers, provider)
} }
func (s *ExchangeRateService) GetExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*ExchangeRate, error) { func (s *ExchangeRateService) GetExchangeRate(ctx context.Context, fromCurrency, toCurrency string) (*currency.ExchangeRate, error) {
if fromCurrency == toCurrency { if fromCurrency == toCurrency {
return &ExchangeRate{ return &currency.ExchangeRate{
FromCurrency: fromCurrency, FromCurrency: fromCurrency,
ToCurrency: toCurrency, ToCurrency: toCurrency,
Rate: 1.0, Rate: 1.0,
@@ -48,7 +49,7 @@ func (s *ExchangeRateService) GetExchangeRate(ctx context.Context, fromCurrency,
for _, provider := range s.providers { for _, provider := range s.providers {
providerRate, err := provider.GetRate(ctx, fromCurrency, toCurrency) providerRate, err := provider.GetRate(ctx, fromCurrency, toCurrency)
if err == nil { if err == nil {
newRate := &ExchangeRate{ newRate := &currency.ExchangeRate{
ID: fmt.Sprintf("%s_%s_%d", fromCurrency, toCurrency, time.Now().Unix()), ID: fmt.Sprintf("%s_%s_%d", fromCurrency, toCurrency, time.Now().Unix()),
FromCurrency: fromCurrency, FromCurrency: fromCurrency,
ToCurrency: toCurrency, ToCurrency: toCurrency,
@@ -69,7 +70,7 @@ func (s *ExchangeRateService) GetExchangeRate(ctx context.Context, fromCurrency,
return nil, fmt.Errorf("exchange rate not found: %s to %s", fromCurrency, toCurrency) return nil, fmt.Errorf("exchange rate not found: %s to %s", fromCurrency, toCurrency)
} }
func (s *ExchangeRateService) ConvertAmount(ctx context.Context, amount float64, fromCurrency, toCurrency string) (*CurrencyConversionResponse, error) { func (s *ExchangeRateService) ConvertAmount(ctx context.Context, amount float64, fromCurrency, toCurrency string) (*currency.CurrencyConversionResponse, error) {
rate, err := s.GetExchangeRate(ctx, fromCurrency, toCurrency) rate, err := s.GetExchangeRate(ctx, fromCurrency, toCurrency)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get exchange rate: %w", err) return nil, fmt.Errorf("failed to get exchange rate: %w", err)
@@ -77,7 +78,7 @@ func (s *ExchangeRateService) ConvertAmount(ctx context.Context, amount float64,
convertedAmount := amount * rate.Rate convertedAmount := amount * rate.Rate
return &CurrencyConversionResponse{ return &currency.CurrencyConversionResponse{
OriginalAmount: amount, OriginalAmount: amount,
OriginalCurrency: fromCurrency, OriginalCurrency: fromCurrency,
ConvertedAmount: convertedAmount, ConvertedAmount: convertedAmount,
@@ -101,8 +102,8 @@ func (s *ExchangeRateService) RefreshRates(ctx context.Context) error {
return nil return nil
} }
func (s *ExchangeRateService) GetRateHistory(ctx context.Context, fromCurrency, toCurrency string, days int) ([]*ExchangeRate, error) { func (s *ExchangeRateService) GetRateHistory(ctx context.Context, fromCurrency, toCurrency string, days int) ([]*currency.ExchangeRate, error) {
filter := &ExchangeRateFilter{ filter := &currency.ExchangeRateFilter{
FromCurrency: fromCurrency, FromCurrency: fromCurrency,
ToCurrency: toCurrency, ToCurrency: toCurrency,
IsValid: &[]bool{false}[0], // Include historical rates IsValid: &[]bool{false}[0], // Include historical rates
@@ -119,7 +120,7 @@ func (s *ExchangeRateService) GetRateHistory(ctx context.Context, fromCurrency,
return rates, nil return rates, nil
} }
func (s *ExchangeRateService) UpdateExchangeRate(ctx context.Context, rate *ExchangeRate) error { func (s *ExchangeRateService) UpdateExchangeRate(ctx context.Context, rate *currency.ExchangeRate) error {
if rate.Rate <= 0 { if rate.Rate <= 0 {
return fmt.Errorf("invalid exchange rate: must be positive") return fmt.Errorf("invalid exchange rate: must be positive")
} }
@@ -132,7 +133,7 @@ func (s *ExchangeRateService) UpdateExchangeRate(ctx context.Context, rate *Exch
rate.ValidFrom = now rate.ValidFrom = now
rate.IsActive = true rate.IsActive = true
filter := &ExchangeRateFilter{ filter := &currency.ExchangeRateFilter{
FromCurrency: rate.FromCurrency, FromCurrency: rate.FromCurrency,
ToCurrency: rate.ToCurrency, ToCurrency: rate.ToCurrency,
IsValid: &[]bool{true}[0], IsValid: &[]bool{true}[0],
@@ -163,8 +164,8 @@ func (s *ExchangeRateService) UpdateExchangeRate(ctx context.Context, rate *Exch
return nil return nil
} }
func (s *ExchangeRateService) GetSupportedCurrencies(ctx context.Context) ([]*Currency, error) { func (s *ExchangeRateService) GetSupportedCurrencies(ctx context.Context) ([]*currency.Currency, error) {
filter := &CurrencyFilter{ filter := &currency.CurrencyFilter{
IsActive: &[]bool{true}[0], IsActive: &[]bool{true}[0],
} }
@@ -1,4 +1,4 @@
package currency package services
import ( import (
"context" "context"
@@ -7,21 +7,22 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan" "github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
) )
// RatePlanCurrencyIntegrator integrates currency system with rate plans // RatePlanCurrencyIntegrator integrates currency system with rate plans
type RatePlanCurrencyIntegrator struct { type RatePlanCurrencyIntegrator struct {
billingService BillingService billingService currency.BillingService
exchangeService *ExchangeRateService exchangeService *ExchangeRateService
ratePlanService rateplan.Service ratePlanService rateplan.Service
logger *logrus.Logger logger *logrus.Logger
baseCurrency string baseCurrency string
} }
// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator // NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator
func NewRatePlanCurrencyIntegrator( func NewRatePlanCurrencyIntegrator(
billingService BillingService, billingService currency.BillingService,
exchangeService *ExchangeRateService, exchangeService *ExchangeRateService,
ratePlanService rateplan.Service, ratePlanService rateplan.Service,
logger *logrus.Logger, logger *logrus.Logger,
@@ -30,9 +31,9 @@ func NewRatePlanCurrencyIntegrator(
return &RatePlanCurrencyIntegrator{ return &RatePlanCurrencyIntegrator{
billingService: billingService, billingService: billingService,
exchangeService: exchangeService, exchangeService: exchangeService,
ratePlanService: rateplanService, ratePlanService: ratePlanService,
logger: logger, logger: logger,
baseCurrency: baseCurrency, baseCurrency: baseCurrency,
} }
} }
@@ -47,7 +48,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
// Convert price to requested currency if needed // Convert price to requested currency if needed
subscriptionPrice := plan.BasePrice subscriptionPrice := plan.BasePrice
exchangeRate := 1.0 exchangeRate := 1.0
if currency != plan.Currency { if currency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, currency) conversion, err := rpci.exchangeService.ConvertAmount(ctx, plan.BasePrice, plan.Currency, currency)
if err != nil { if err != nil {
@@ -60,10 +61,10 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
// Create subscription with currency information // Create subscription with currency information
subscription := &rateplan.RatePlanSubscription{ subscription := &rateplan.RatePlanSubscription{
ProfileID: profileID, ProfileID: profileID,
RatePlanID: planID, RatePlanID: planID,
Status: rateplan.SubscriptionStatusActive, Status: rateplan.SubscriptionStatusActive,
StartedAt: time.Now(), StartedAt: time.Now(),
Metadata: map[string]interface{}{ Metadata: map[string]interface{}{
"original_currency": plan.Currency, "original_currency": plan.Currency,
"subscription_currency": currency, "subscription_currency": currency,
@@ -89,13 +90,13 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
} }
// Process initial billing // Process initial billing
billingReq := &BillingRequest{ billingReq := &currency.BillingRequest{
ProfileID: profileID, ProfileID: profileID,
SubscriptionID: createdSubscription.ID, SubscriptionID: createdSubscription.ID,
Amount: subscriptionPrice, Amount: subscriptionPrice,
Currency: currency, Currency: currency,
Description: fmt.Sprintf("Initial subscription to %s", plan.Name), Description: fmt.Sprintf("Initial subscription to %s", plan.Name),
BillingDate: time.Now(), BillingDate: time.Now(),
} }
_, err = rpci.billingService.ProcessBilling(ctx, billingReq) _, err = rpci.billingService.ProcessBilling(ctx, billingReq)
@@ -115,7 +116,7 @@ func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.
} }
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency // 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) (*BillingSummary, error) { func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, currency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
// Get the rate plan // Get the rate plan
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID) plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
if err != nil { if err != nil {
@@ -138,7 +139,7 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
// Convert to requested currency // Convert to requested currency
convertedCost := baseCost convertedCost := baseCost
exchangeRate := 1.0 exchangeRate := 1.0
if currency != plan.Currency { if currency != plan.Currency {
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, currency) conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, currency)
if err != nil { if err != nil {
@@ -149,15 +150,15 @@ func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.
} }
// Create billing summary // Create billing summary
summary := &BillingSummary{ summary := &currency.BillingSummary{
ProfileID: usageData.ProfileID, ProfileID: usageData.ProfileID,
TotalAmount: convertedCost, TotalAmount: convertedCost,
Currency: currency, Currency: currency,
BaseTotalAmount: baseCost, BaseTotalAmount: baseCost,
BaseCurrency: plan.Currency, BaseCurrency: plan.Currency,
TransactionCount: 1, TransactionCount: 1,
FromDate: time.Now().AddDate(0, -1, 0), FromDate: time.Now().AddDate(0, -1, 0),
ToDate: time.Now(), ToDate: time.Now(),
Breakdown: map[string]interface{}{ Breakdown: map[string]interface{}{
"plan_id": planID, "plan_id": planID,
"plan_name": plan.Name, "plan_name": plan.Name,
@@ -1,4 +1,4 @@
package currency package services
import ( import (
"context" "context"
@@ -85,7 +85,7 @@ func (rpci *RatePlanCurrencyIntegrator) UpdatePlanCurrency(ctx context.Context,
if err != nil { if err != nil {
return fmt.Errorf("failed to update rate plan: %w", err) return fmt.Errorf("failed to update rate plan: %w", err)
} }
// Use the updated plan for logging // Use the updated plan for logging
plan = updatedPlan plan = updatedPlan
@@ -145,7 +145,7 @@ func (rpci *RatePlanCurrencyIntegrator) GetCurrencyUsageForPlan(ctx context.Cont
// Count currencies // Count currencies
currencyUsage := make(map[string]int64) currencyUsage := make(map[string]int64)
for _, subscription := range subscriptions { for _, subscription := range subscriptions {
currency := rpci.baseCurrency // Default to base currency currency := rpci.baseCurrency // Default to base currency
if subscription.Metadata != nil { if subscription.Metadata != nil {
@@ -1,4 +1,4 @@
package service package services
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package service package services
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package rateplan package services
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package rateplan package services
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package rateplan package services
import ( import (
"fmt" "fmt"
@@ -1,4 +1,4 @@
package rateplan package services
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package service package services
import ( import (
"context" "context"