package wallet import ( "context" "errors" "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) var ( ErrBalanceInsufficient = errors.New("wallet balance is insufficient") ) func (s *Service) CreateWallet(ctx context.Context, wallet domain.CreateWallet) (domain.Wallet, error) { return s.walletStore.CreateWallet(ctx, wallet) } func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64) (domain.CustomerWallet, error) { regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ IsWithdraw: true, IsBettable: true, UserID: customerID, }) if err != nil { return domain.CustomerWallet{}, err } staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{ IsWithdraw: false, IsBettable: true, UserID: customerID, }) if err != nil { return domain.CustomerWallet{}, err } return s.walletStore.CreateCustomerWallet(ctx, domain.CreateCustomerWallet{ CustomerID: customerID, RegularWalletID: regularWallet.ID, StaticWalletID: staticWallet.ID, }) } func (s *Service) GetWalletByID(ctx context.Context, id int64) (domain.Wallet, error) { return s.walletStore.GetWalletByID(ctx, id) } func (s *Service) GetAllWallets(ctx context.Context) ([]domain.Wallet, error) { return s.walletStore.GetAllWallets(ctx) } func (s *Service) GetWalletsByUser(ctx context.Context, id int64) ([]domain.Wallet, error) { return s.walletStore.GetWalletsByUser(ctx, id) } func (s *Service) GetAllCustomerWallet(ctx context.Context) ([]domain.GetCustomerWallet, error) { return s.walletStore.GetAllCustomerWallets(ctx) } func (s *Service) GetCustomerWallet(ctx context.Context, customerID int64) (domain.GetCustomerWallet, error) { return s.walletStore.GetCustomerWallet(ctx, customerID) } func (s *Service) GetAllBranchWallets(ctx context.Context) ([]domain.BranchWallet, error) { return s.walletStore.GetAllBranchWallets(ctx) } func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Currency) error { err := s.walletStore.UpdateBalance(ctx, id, balance) if err != nil { return err } wallet, err := s.GetWalletByID(ctx, id) if err != nil { return err } go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) return nil } func (s *Service) AddToWallet( ctx context.Context, id int64, amount domain.Currency, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, paymentDetails domain.PaymentDetails, message string) (domain.Transfer, error) { wallet, err := s.GetWalletByID(ctx, id) if err != nil { return domain.Transfer{}, err } err = s.walletStore.UpdateBalance(ctx, id, wallet.Balance+amount) if err != nil { return domain.Transfer{}, err } // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ Message: message, Amount: amount, Verified: true, ReceiverWalletID: domain.ValidInt64{ Value: wallet.ID, Valid: true, }, CashierID: cashierID, PaymentMethod: paymentMethod, Type: domain.DEPOSIT, ReferenceNumber: paymentDetails.ReferenceNumber.Value, }) return newTransfer, err } func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.Currency, walletType domain.WalletType, cashierID domain.ValidInt64, paymentMethod domain.PaymentMethod, message string) (domain.Transfer, error) { wallet, err := s.GetWalletByID(ctx, id) if err != nil { return domain.Transfer{}, err } if wallet.Balance < amount { // Send Wallet low to admin if walletType == domain.CompanyWalletType || walletType == domain.BranchWalletType { s.SendAdminWalletLowNotification(ctx, wallet, amount) } return domain.Transfer{}, ErrBalanceInsufficient } err = s.walletStore.UpdateBalance(ctx, id, wallet.Balance-amount) if err != nil { return domain.Transfer{}, nil } // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ Message: message, Amount: amount, Verified: true, SenderWalletID: domain.ValidInt64{ Value: wallet.ID, Valid: true, }, CashierID: cashierID, PaymentMethod: paymentMethod, Type: domain.WITHDRAW, ReferenceNumber: "", }) return newTransfer, err } // Directly Refilling wallet without // func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { // receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID) // if err != nil { // return domain.Transfer{}, err // } // // Add to receiver // senderWallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID) // if err != nil { // return domain.Transfer{}, err // } else if senderWallet.Balance < transfer.Amount { // return domain.Transfer{}, ErrInsufficientBalance // } // err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount) // if err != nil { // return domain.Transfer{}, err // } // // Log the transfer so that if there is a mistake, it can be reverted // newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ // CashierID: transfer.CashierID, // ReceiverWalletID: transfer.ReceiverWalletID, // Amount: transfer.Amount, // Type: domain.DEPOSIT, // PaymentMethod: transfer.PaymentMethod, // Verified: true, // }) // if err != nil { // return domain.Transfer{}, err // } // return newTransfer, nil // } func (s *Service) UpdateWalletActive(ctx context.Context, id int64, isActive bool) error { return s.walletStore.UpdateWalletActive(ctx, id, isActive) } func (s *Service) SendAdminWalletLowNotification(ctx context.Context, adminWallet domain.Wallet, amount domain.Currency) error { // Send notification to admin team adminNotification := &domain.Notification{ RecipientID: adminWallet.UserID, Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT, Level: domain.NotificationLevelError, Reciever: domain.NotificationRecieverSideAdmin, DeliveryChannel: domain.DeliveryChannelEmail, // Or any preferred admin channel Payload: domain.NotificationPayload{ Headline: "CREDIT WARNING: System Running Out of Funds", Message: fmt.Sprintf( "Wallet ID %d has insufficient balance for transfer. Current balance: %.2f, Attempted transfer: %.2f", adminWallet.ID, adminWallet.Balance.Float32(), amount.Float32(), ), }, Priority: 1, // High priority for admin alerts Metadata: fmt.Appendf(nil, `{ "wallet_id": %d, "balance": %d, "required_amount": %d, "notification_type": "admin_alert" }`, adminWallet.ID, adminWallet.Balance, amount), } // Get admin recipients and send to all adminRecipients, err := s.notificationStore.ListRecipientIDs(ctx, domain.NotificationRecieverSideAdmin) if err != nil { s.logger.Error("failed to get admin recipients", "error", err) return err } else { for _, adminID := range adminRecipients { adminNotification.RecipientID = adminID if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil { s.logger.Error("failed to send admin notification", "admin_id", adminID, "error", err) } } } return nil }