mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-22 04:34:27 +00:00
feat: Add multi-currency billing service with transaction processing, currency conversion, and history tracking
- Add BillingServiceImpl struct with repository, exchange service, logger, and base currency fields - Add NewBillingService constructor with dependency injection - Add ProcessBilling with request validation, currency conversion, transaction creation, and payment processing - Add ConvertAmount wrapper delegating to exchange service - Add GetBillingHistory with profile ID filtering and transaction retries
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package currency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// BillingServiceImpl handles multi-currency billing operations
|
||||
type BillingServiceImpl struct {
|
||||
repository Repository
|
||||
exchangeService *ExchangeRateService
|
||||
logger *logrus.Logger
|
||||
baseCurrency string
|
||||
}
|
||||
|
||||
// NewBillingService creates a new billing service
|
||||
func NewBillingService(repository Repository, exchangeService *ExchangeRateService, logger *logrus.Logger, baseCurrency string) *BillingServiceImpl {
|
||||
return &BillingServiceImpl{
|
||||
repository: repository,
|
||||
exchangeService: exchangeService,
|
||||
logger: logger,
|
||||
baseCurrency: baseCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessBilling processes a billing request in multi-currency context
|
||||
func (s *BillingServiceImpl) ProcessBilling(ctx context.Context, req *BillingRequest) (*BillingResponse, error) {
|
||||
// Validate request
|
||||
if err := s.validateBillingRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("invalid billing request: %w", err)
|
||||
}
|
||||
|
||||
// Convert to base currency if needed
|
||||
baseAmount := req.Amount
|
||||
exchangeRate := 1.0
|
||||
|
||||
if req.Currency != s.baseCurrency {
|
||||
conversion, err := s.exchangeService.ConvertAmount(ctx, req.Amount, req.Currency, s.baseCurrency)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to convert currency for billing")
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
}
|
||||
baseAmount = conversion.ConvertedAmount
|
||||
exchangeRate = conversion.ExchangeRate
|
||||
}
|
||||
|
||||
// Create transaction
|
||||
transaction := &Transaction{
|
||||
ID: uuid.New().String(),
|
||||
ProfileID: req.ProfileID,
|
||||
SubscriptionID: req.SubscriptionID,
|
||||
Type: TransactionTypeSubscription,
|
||||
Amount: req.Amount,
|
||||
Currency: req.Currency,
|
||||
BaseAmount: baseAmount,
|
||||
BaseCurrency: s.baseCurrency,
|
||||
ExchangeRate: exchangeRate,
|
||||
Description: req.Description,
|
||||
Status: TransactionStatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Save transaction
|
||||
if err := s.repository.CreateTransaction(ctx, transaction); err != nil {
|
||||
s.logger.WithError(err).Error("Failed to create billing transaction")
|
||||
return nil, fmt.Errorf("failed to create transaction: %w", err)
|
||||
}
|
||||
|
||||
// Process payment (in real implementation, this would integrate with payment gateway)
|
||||
transaction.Status = 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)
|
||||
}
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"transaction_id": transaction.ID,
|
||||
"profile_id": req.ProfileID,
|
||||
"amount": req.Amount,
|
||||
"currency": req.Currency,
|
||||
"base_amount": baseAmount,
|
||||
"base_currency": s.baseCurrency,
|
||||
}).Info("Billing processed successfully")
|
||||
|
||||
return &BillingResponse{
|
||||
TransactionID: transaction.ID,
|
||||
Amount: transaction.Amount,
|
||||
Currency: transaction.Currency,
|
||||
BaseAmount: transaction.BaseAmount,
|
||||
BaseCurrency: transaction.BaseCurrency,
|
||||
ExchangeRate: transaction.ExchangeRate,
|
||||
Status: string(transaction.Status),
|
||||
ProcessedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ConvertAmount converts an amount between currencies
|
||||
func (s *BillingServiceImpl) ConvertAmount(ctx context.Context, req *CurrencyConversionRequest) (*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) {
|
||||
if filter == nil {
|
||||
filter = &TransactionFilter{}
|
||||
}
|
||||
|
||||
filter.ProfileID = profileID
|
||||
|
||||
transactions, err := s.repository.ListTransactions(ctx, filter)
|
||||
if err != nil {
|
||||
s.logger.WithError(err).Error("Failed to get billing history")
|
||||
return nil, fmt.Errorf("failed to get billing history: %w", err)
|
||||
}
|
||||
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
// validateBillingRequest validates a billing request
|
||||
func (s *BillingServiceImpl) validateBillingRequest(req *BillingRequest) error {
|
||||
if req.ProfileID == "" {
|
||||
return fmt.Errorf("profile ID is required")
|
||||
}
|
||||
if req.SubscriptionID == "" {
|
||||
return fmt.Errorf("subscription ID is required")
|
||||
}
|
||||
if req.Amount <= 0 {
|
||||
return fmt.Errorf("amount must be positive")
|
||||
}
|
||||
if req.Currency == "" {
|
||||
return fmt.Errorf("currency is required")
|
||||
}
|
||||
if req.BillingDate.IsZero() {
|
||||
req.BillingDate = time.Now()
|
||||
}
|
||||
|
||||
// Validate currency
|
||||
if err := s.exchangeService.ValidateCurrencyPair(context.Background(), req.Currency, s.baseCurrency); err != nil {
|
||||
return fmt.Errorf("invalid currency: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user