package config import ( "errors" "fmt" "log/slog" "os" "strconv" "strings" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger" "github.com/joho/godotenv" ) var ( ErrInvalidDbUrl = errors.New("db url is invalid") ErrInvalidPort = errors.New("port number is invalid") ErrRefreshExpiry = errors.New("refresh token expiry is invalid") ErrAccessExpiry = errors.New("access token expiry is invalid") ErrInvalidJwtKey = errors.New("jwt key is invalid") ErrLogLevel = errors.New("log level not set") ErrInvalidLevel = errors.New("invalid log level") ErrInvalidEnv = errors.New("env not set or invalid") ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid") ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env") ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid") ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid") ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid") ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid") ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid") ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid") ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid") ErrInvalidAtlasBaseUrl = errors.New("Atlas Base URL is invalid") ErrInvalidAtlasOperatorID = errors.New("Atlas operator ID is invalid") ErrInvalidAtlasSecretKey = errors.New("Atlas secret key is invalid") ErrInvalidAtlasBrandID = errors.New("Atlas brand ID is invalid") ErrInvalidAtlasPartnerID = errors.New("Atlas Partner ID is invalid") ErrMissingResendApiKey = errors.New("missing Resend Api key") ErrMissingResendSenderEmail = errors.New("missing Resend sender name") ErrMissingTwilioAccountSid = errors.New("missing twilio account sid") ErrMissingTwilioAuthToken = errors.New("missing twilio auth token") ErrMissingTwilioSenderPhoneNumber = errors.New("missing twilio sender phone number") ) type EnetPulseConfig struct { UserName string `mapstructure:"username"` Token string `mapstructure:"token"` ProviderID string `mapstructure:"provider_id"` } type AleaPlayConfig struct { Enabled bool `mapstructure:"enabled"` BaseURL string `mapstructure:"base_url"` // "https://api.aleaplay.com" OperatorID string `mapstructure:"operator_id"` // Your operator ID with Alea SecretKey string `mapstructure:"secret_key"` // API secret for signatures GameListURL string `mapstructure:"game_list_url"` // Endpoint to fetch available games DefaultCurrency string `mapstructure:"default_currency"` // "USD", "EUR", etc. SessionTimeout int `mapstructure:"session_timeout"` // In hours } type VeliConfig struct { APIKey string `mapstructure:"VELI_API_KEY"` BaseURL string `mapstructure:"VELI_BASE_URL"` SecretKey string `mapstructure:"VELI_SECRET_KEY"` OperatorID string `mapstructure:"VELI_OPERATOR_ID"` BrandID string `mapstructure:"VELI_BRAND_ID"` Currency string `mapstructure:"VELI_DEFAULT_CURRENCY"` WebhookURL string `mapstructure:"VELI_WEBHOOK_URL"` Enabled bool `mapstructure:"Enabled"` } type AtlasConfig struct { BaseURL string `mapstructure:"ATLAS_BASE_URL"` SecretKey string `mapstructure:"ATLAS_SECRET_KEY"` OperatorID string `mapstructure:"ATLAS_OPERATOR_ID"` CasinoID string `mapstructure:"ATLAS_BRAND_ID"` PartnerID string `mapstructure:"ATLAS_PARTNER_ID"` } type ARIFPAYConfig struct { APIKey string `mapstructure:"ARIFPAY_API_KEY"` BaseURL string `mapstructure:"ARIFPAY_BASE_URL"` // Default URLs CancelUrl string `mapstructure:"cancelUrl"` SuccessUrl string `mapstructure:"successUrl"` ErrorUrl string `mapstructure:"errorUrl"` B2CNotifyUrl string `mapstructure:"notifyUrl"` C2BNotifyUrl string `mapstructure:"notifyUrl"` // Default Payment Configs PaymentMethods []string `mapstructure:"paymentMethods"` ExpireDate string `mapstructure:"expireDate"` ItemName string `mapstructure:"name"` Quantity int `mapstructure:"quantity"` Description string `mapstructure:"description"` BeneficiaryAccountNumber string `mapstructure:"accountNumber"` Bank string `mapstructure:"bank"` Lang string `mapstructure:"amount"` } type SANTIMPAYConfig struct { SecretKey string `mapstructure:"secret_key"` MerchantID string `mapstructure:"merchantId"` BaseURL string `mapstructure:"base_url"` NotifyURL string `mapstructure:"notifyUrl"` CancelUrl string `mapstructure:"cancelUrl"` SuccessUrl string `mapstructure:"successUrl"` } type TELEBIRRConfig struct { TelebirrFabricAppID string `mapstructure:"fabric_app_id"` TelebirrAppSecret string `mapstructure:"appSecret"` TelebirrBaseURL string `mapstructure:"base_url"` TelebirrMerchantCode string `mapstructure:"merchant_code"` TelebirrCallbackURL string `mapstructure:"callback_url"` } type Config struct { FIXER_API_KEY string FIXER_BASE_URL string BASE_CURRENCY domain.IntCurrency Port int Service string DbUrl string RefreshExpiry int AccessExpiry int JwtKey string LogLevel slog.Level Env string ReportExportPath string `mapstructure:"REPORT_EXPORT_PATH"` AFRO_SMS_API_KEY string AFRO_SMS_SENDER_NAME string AFRO_SMS_RECEIVER_PHONE_NUMBER string ADRO_SMS_HOST_URL string CHAPA_TRANSFER_TYPE string CHAPA_PAYMENT_TYPE string CHAPA_SECRET_KEY string CHAPA_PUBLIC_KEY string CHAPA_BASE_URL string CHAPA_ENCRYPTION_KEY string CHAPA_CALLBACK_URL string CHAPA_RETURN_URL string CHAPA_RECEIPT_URL string Bet365Token string EnetPulseConfig EnetPulseConfig PopOK domain.PopOKConfig AleaPlay AleaPlayConfig `mapstructure:"alea_play"` Atlas AtlasConfig `mapstructure:"atlas"` VeliGames VeliConfig `mapstructure:"veli_games"` ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"` SANTIMPAY SANTIMPAYConfig `mapstructure:"santimpay_config"` TELEBIRR TELEBIRRConfig `mapstructure:"telebirr_config"` ResendApiKey string ResendSenderEmail string TwilioAccountSid string TwilioAuthToken string TwilioSenderPhoneNumber string RedisAddr string KafkaBrokers []string } func NewConfig() (*Config, error) { config := &Config{} if err := config.loadEnv(); err != nil { return nil, err } return config, nil } func (c *Config) loadEnv() error { err := godotenv.Load() if err != nil && !os.IsNotExist(err) { return errors.New("failed to load env file: " + err.Error()) } env := os.Getenv("ENV") if env == "" { return ErrInvalidEnv } c.Env = env c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH") c.RedisAddr = os.Getenv("REDIS_ADDR") c.KafkaBrokers = strings.Split(os.Getenv("KAFKA_BROKERS"), ",") c.EnetPulseConfig.Token = os.Getenv("ENETPULSE_TOKEN") c.EnetPulseConfig.UserName = os.Getenv("ENETPULSE_USERNAME") c.EnetPulseConfig.ProviderID = os.Getenv("ENETPULSE_PROVIDER_ID") c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE") c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE") c.FIXER_API_KEY = os.Getenv("FIXER_API_KEY") c.BASE_CURRENCY = domain.IntCurrency(os.Getenv("BASE_CURRENCY")) c.FIXER_BASE_URL = os.Getenv("FIXER_BASE_URL") portStr := os.Getenv("PORT") if portStr == "" { return ErrInvalidPort } port, err := strconv.Atoi(portStr) if err != nil || port < 1 || port > 65535 { return ErrInvalidPort } c.Port = port dbUrl := os.Getenv("DB_URL") if dbUrl == "" { return ErrInvalidDbUrl } c.DbUrl = dbUrl refreshExpiryStr := os.Getenv("REFRESH_EXPIRY") if refreshExpiryStr == "" { return ErrRefreshExpiry } refreshExpiry, err := strconv.Atoi(refreshExpiryStr) if err != nil || refreshExpiry <= 0 { return ErrRefreshExpiry } c.RefreshExpiry = refreshExpiry jwtKey := os.Getenv("JWT_KEY") if jwtKey == "" { return ErrInvalidJwtKey } c.JwtKey = jwtKey accessExpiryStr := os.Getenv("ACCESS_EXPIRY") if accessExpiryStr == "" { return ErrAccessExpiry } accessExpiry, err := strconv.Atoi(accessExpiryStr) if err != nil || accessExpiry <= 0 { return ErrAccessExpiry } c.AccessExpiry = accessExpiry logLevel := os.Getenv("LOG_LEVEL") if logLevel == "" { return ErrLogLevel } lvl, ok := customlogger.LogLevels[logLevel] if !ok { return ErrInvalidLevel } //Telebirr c.TELEBIRR.TelebirrBaseURL = os.Getenv("TELEBIRR_BASE_URL") c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_APP_SECRET") c.TELEBIRR.TelebirrAppSecret = os.Getenv("TELEBIRR_FABRIC_APP_ID") c.TELEBIRR.TelebirrMerchantCode = os.Getenv("TELEBIRR_MERCHANT_CODE") c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL") //Chapa c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY") c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY") c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY") c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL") c.CHAPA_RECEIPT_URL = os.Getenv("CHAPA_RECEIPT_URL") if c.CHAPA_BASE_URL == "" { c.CHAPA_BASE_URL = "https://api.chapa.co/v1" } c.CHAPA_CALLBACK_URL = os.Getenv("CHAPA_CALLBACK_URL") c.CHAPA_RETURN_URL = os.Getenv("CHAPA_RETURN_URL") c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY") c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL") c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL") c.ARIFPAY.C2BNotifyUrl = os.Getenv("ARIFPAY_C2B_NOTIFY_URL") c.ARIFPAY.B2CNotifyUrl = os.Getenv("ARIFPAY_B2C_NOTIFY_URL") c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL") c.ARIFPAY.BaseURL = os.Getenv("ARIFPAY_BASE_URL") c.ARIFPAY.Bank = os.Getenv("ARIFPAY_BANK") c.ARIFPAY.BeneficiaryAccountNumber = os.Getenv("ARIFPAY_BENEFICIARY_ACCOUNT_NUMBER") c.ARIFPAY.Description = os.Getenv("ARIFPAY_DESCRIPTION") c.ARIFPAY.ExpireDate = time.Now().Add(time.Hour * 3).Format("2006-01-02") c.ARIFPAY.ItemName = os.Getenv("ARIFPAY_ITEM_NAME") c.ARIFPAY.Lang = "EN" c.ARIFPAY.Quantity = 1 c.ARIFPAY.PaymentMethods = []string{"TELEBIRR", "AWAASH", "AWAASH_WALLET", "PSS", "CBE", "AMOLE", "BOA", "KACHA", "TELEBIRR", "HELLOCASH", "MPESSA"} c.SANTIMPAY.SecretKey = os.Getenv("SANTIMPAY_SECRET_KEY") c.SANTIMPAY.MerchantID = os.Getenv("SANTIMPAY_MERCHANT_ID") c.SANTIMPAY.BaseURL = os.Getenv("SANTIMPAY_BASE_URL") c.SANTIMPAY.NotifyURL = os.Getenv("SANTIMPAY_NOTIFY_URL") c.SANTIMPAY.CancelUrl = os.Getenv("SANTIMPAY_CANCEL_URL") //Alea Play aleaEnabled := os.Getenv("ALEA_ENABLED") if aleaEnabled == "" { aleaEnabled = "false" // Default disabled } if enabled, err := strconv.ParseBool(aleaEnabled); err != nil { return fmt.Errorf("invalid ALEA_ENABLED value: %w", err) } else { c.AleaPlay.Enabled = enabled } c.AleaPlay.BaseURL = os.Getenv("ALEA_BASE_URL") if c.AleaPlay.BaseURL == "" && c.AleaPlay.Enabled { return errors.New("ALEA_BASE_URL is required when Alea is enabled") } c.AleaPlay.OperatorID = os.Getenv("ALEA_OPERATOR_ID") if c.AleaPlay.OperatorID == "" && c.AleaPlay.Enabled { return errors.New("ALEA_OPERATOR_ID is required when Alea is enabled") } c.AleaPlay.SecretKey = os.Getenv("ALEA_SECRET_KEY") if c.AleaPlay.SecretKey == "" && c.AleaPlay.Enabled { return errors.New("ALEA_SECRET_KEY is required when Alea is enabled") } c.AleaPlay.GameListURL = os.Getenv("ALEA_GAME_LIST_URL") c.AleaPlay.DefaultCurrency = os.Getenv("ALEA_DEFAULT_CURRENCY") if c.AleaPlay.DefaultCurrency == "" { c.AleaPlay.DefaultCurrency = "USD" } sessionTimeoutStr := os.Getenv("ALEA_SESSION_TIMEOUT") if sessionTimeoutStr != "" { timeout, err := strconv.Atoi(sessionTimeoutStr) if err == nil { c.AleaPlay.SessionTimeout = timeout } } //Veli Games veliEnabled := os.Getenv("VELI_ENABLED") if veliEnabled == "" { veliEnabled = "true" // Default to disabled if not specified } // veliOperatorID := os.Getenv("VELI_OPERATOR_ID") // veliBrandID := os.Getenv("VELI_BRAND_ID") c.VeliGames.OperatorID = os.Getenv("VELI_OPERATOR_ID") c.VeliGames.BrandID = os.Getenv("VELI_BRAND_ID") if enabled, err := strconv.ParseBool(veliEnabled); err != nil { return fmt.Errorf("invalid VELI_ENABLED value: %w", err) } else { c.VeliGames.Enabled = enabled } // apiURL := os.Getenv("VELI_BASE_URL") // if apiURL == "" { // apiURL = "https://api.velitech.games" // Default production URL // } c.VeliGames.BaseURL = os.Getenv("VELI_BASE_URL") operatorKey := os.Getenv("VELI_OPERATOR_KEY") if operatorKey == "" && c.VeliGames.Enabled { return ErrInvalidVeliOperatorKey } // c.VeliGames.OperatorKey = operatorKey secretKey := os.Getenv("VELI_SECRET_KEY") if secretKey == "" && c.VeliGames.Enabled { return ErrInvalidVeliSecretKey } c.VeliGames.SecretKey = secretKey // c.VeliGames.GameIDs.Aviator = os.Getenv("VELI_GAME_ID_AVIATOR") defaultCurrency := os.Getenv("VELI_DEFAULT_CURRENCY") if defaultCurrency == "" { defaultCurrency = "USD" // Default currency } // c.VeliGames.DefaultCurrency = defaultCurrency c.LogLevel = lvl c.AFRO_SMS_API_KEY = os.Getenv("AFRO_SMS_API_KEY") if c.AFRO_SMS_API_KEY == "" { return ErrInvalidSMSAPIKey } c.AFRO_SMS_SENDER_NAME = os.Getenv("AFRO_SMS_SENDER_NAME") if c.AFRO_SMS_SENDER_NAME == "" { c.AFRO_SMS_SENDER_NAME = "FortuneBet" } c.AFRO_SMS_RECEIVER_PHONE_NUMBER = os.Getenv("AFRO_SMS_RECEIVER_PHONE_NUMBER") c.ADRO_SMS_HOST_URL = os.Getenv("ADRO_SMS_HOST_URL") if c.ADRO_SMS_HOST_URL == "" { c.ADRO_SMS_HOST_URL = "https://api.afrosms.com" } //Atlas c.Atlas.BaseURL = os.Getenv("ATLAS_BASE_URL") c.Atlas.CasinoID = os.Getenv("ATLAS_CASINO_ID") c.Atlas.OperatorID = os.Getenv("ATLAS_OPERATOR_ID") c.Atlas.PartnerID = os.Getenv("ATLAS_PARTNER_ID") c.Atlas.SecretKey = os.Getenv("ATLAS_SECRET_KEY") popOKClientID := os.Getenv("POPOK_CLIENT_ID") popOKPlatform := os.Getenv("POPOK_PLATFORM") if popOKClientID == "" { return ErrInvalidPopOKClientID } popOKSecretKey := os.Getenv("POPOK_SECRET_KEY") if popOKSecretKey == "" { return ErrInvalidPopOKSecretKey } popOKBaseURL := os.Getenv("POPOK_BASE_URL") if popOKBaseURL == "" { return ErrInvalidPopOKBaseURL } popOKCallbackURL := os.Getenv("POPOK_CALLBACK_URL") if popOKCallbackURL == "" { return ErrInvalidPopOKCallbackURL } c.PopOK = domain.PopOKConfig{ ClientID: popOKClientID, SecretKey: popOKSecretKey, BaseURL: popOKBaseURL, CallbackURL: popOKCallbackURL, Platform: popOKPlatform, } // AtlasBaseUrl := os.Getenv("ATLAS_BASE_URL") // if AtlasBaseUrl == "" { // return ErrInvalidAtlasBaseUrl // } // AtlasSecretKey := os.Getenv("ATLAS_SECRET_KEY") // if AtlasSecretKey == "" { // return ErrInvalidAtlasSecretKey // } // AtlasBrandID := os.Getenv("ATLAS_BRAND_ID") // if AtlasBrandID == "" { // return ErrInvalidAtlasBrandID // } // AtlasPartnerID := os.Getenv("ATLAS_PARTNER_ID") // if AtlasPartnerID == "" { // return ErrInvalidAtlasPartnerID // } // AtlasOperatorID := os.Getenv("ATLAS_OPERATOR_ID") // if AtlasOperatorID == "" { // return ErrInvalidAtlasOperatorID // } // c.Atlas = AtlasConfig{ // BaseURL: AtlasBaseUrl, // SecretKey: AtlasSecretKey, // CasinoID: AtlasBrandID, // PartnerID: AtlasPartnerID, // OperatorID: AtlasOperatorID, // } betToken := os.Getenv("BET365_TOKEN") if betToken == "" { return ErrMissingBetToken } c.Bet365Token = betToken resendApiKey := os.Getenv("RESEND_API_KEY") if resendApiKey == "" { return ErrMissingResendApiKey } c.ResendApiKey = resendApiKey resendSenderEmail := os.Getenv("RESEND_SENDER_EMAIL") if resendSenderEmail == "" { return ErrMissingResendSenderEmail } c.ResendSenderEmail = resendSenderEmail twilioAccountSid := os.Getenv("TWILIO_ACCOUNT_SID") if twilioAccountSid == "" { return ErrMissingTwilioAccountSid } c.TwilioAccountSid = twilioAccountSid twilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN") if twilioAuthToken == "" { return ErrMissingTwilioAuthToken } c.TwilioAuthToken = twilioAuthToken twilioSenderPhoneNumber := os.Getenv("TWILIO_SENDER_PHONE_NUMBER") if twilioSenderPhoneNumber == "" { return ErrMissingTwilioSenderPhoneNumber } c.TwilioSenderPhoneNumber = twilioSenderPhoneNumber return nil } type ChapaConfig struct { ChapaPaymentType string `mapstructure:"chapa_payment_type"` ChapaTransferType string `mapstructure:"chapa_transfer_type"` }