package wallet import ( "context" "errors" "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) var ( ErrWalletNotTransferable = errors.New("wallet is not transferable") ErrInsufficientBalance = errors.New("wallet balance is insufficient") ) func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { senderWallet, err := s.walletStore.GetWalletByID(ctx, transfer.SenderWalletID.Value) receiverWallet, err := s.walletStore.GetWalletByID(ctx, transfer.ReceiverWalletID) if err != nil { return domain.Transfer{}, fmt.Errorf("failed to get sender wallet: %w", err) } // Check if wallet has sufficient balance if senderWallet.Balance < transfer.Amount || senderWallet.Balance == 0 { // Send notification to customer customerNotification := &domain.Notification{ RecipientID: receiverWallet.UserID, Type: domain.NOTIFICATION_TYPE_TRANSFER, Level: domain.NotificationLevelError, Reciever: domain.NotificationRecieverSideCustomer, DeliveryChannel: domain.DeliveryChannelInApp, Payload: domain.NotificationPayload{ Headline: "Service Temporarily Unavailable", Message: "Our payment system is currently under maintenance. Please try again later.", }, Priority: 2, Metadata: []byte(fmt.Sprintf(`{ "transfer_amount": %d, "current_balance": %d, "wallet_id": %d, "notification_type": "customer_facing" }`, transfer.Amount, senderWallet.Balance, transfer.SenderWalletID.Value)), } // Send notification to admin team adminNotification := &domain.Notification{ RecipientID: senderWallet.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", transfer.SenderWalletID.Value, float64(senderWallet.Balance)/100, float64(transfer.Amount)/100, ), }, Priority: 1, // High priority for admin alerts Metadata: fmt.Appendf(nil, `{ "wallet_id": %d, "balance": %d, "required_amount": %d, "notification_type": "admin_alert" }`, transfer.SenderWalletID.Value, senderWallet.Balance, transfer.Amount), } // Send both notifications if err := s.notificationStore.SendNotification(ctx, customerNotification); err != nil { s.logger.Error("failed to send customer notification", "user_id", "", "error", err) } // 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) } 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 domain.Transfer{}, ErrInsufficientBalance } // Proceed with transfer if balance is sufficient return s.transferStore.CreateTransfer(ctx, transfer) } func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) { return s.transferStore.GetAllTransfers(ctx) } func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) { return s.transferStore.GetTransferByID(ctx, id) } func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) { return s.transferStore.GetTransfersByWallet(ctx, walletID) } func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { return s.transferStore.UpdateTransferVerification(ctx, id, verified) } 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.Value) 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: receiverWallet.ID, Amount: transfer.Amount, Type: domain.DEPOSIT, PaymentMethod: transfer.PaymentMethod, Verified: true, }) if err != nil { return domain.Transfer{}, err } return newTransfer, nil } func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64) (domain.Transfer, error) { senderWallet, err := s.GetWalletByID(ctx, senderID) if err != nil { return domain.Transfer{}, err } if senderWallet.IsTransferable { return domain.Transfer{}, ErrWalletNotTransferable } receiverWallet, err := s.GetWalletByID(ctx, receiverID) if err != nil { return domain.Transfer{}, err } if receiverWallet.IsTransferable { return domain.Transfer{}, ErrWalletNotTransferable } // Deduct from sender if senderWallet.Balance < amount { return domain.Transfer{}, ErrBalanceInsufficient } err = s.walletStore.UpdateBalance(ctx, senderID, senderWallet.Balance-amount) if err != nil { return domain.Transfer{}, err } // Add to receiver err = s.walletStore.UpdateBalance(ctx, receiverID, receiverWallet.Balance+amount) if err != nil { return domain.Transfer{}, err } // Log the transfer so that if there is a mistake, it can be reverted transfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ SenderWalletID: domain.ValidInt64{ Value: senderID, Valid: true, }, CashierID: cashierID, ReceiverWalletID: receiverID, Amount: amount, Type: domain.WALLET, PaymentMethod: paymentMethod, Verified: true, }) if err != nil { return domain.Transfer{}, err } return transfer, nil }