package directdeposit import ( "context" "encoding/json" "fmt" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/ports" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" ) type Service struct { walletSvc wallet.Service transferStore ports.TransferStore directDepositStore repository.DirectDepositRepository notificationSvc *notificationservice.Service userSvc *user.Service // mongoLogger *zap.Logger // logger *slog.Logger } func NewService( walletSvc wallet.Service, transferStore ports.TransferStore, directDepositStore repository.DirectDepositRepository, notificationSvc *notificationservice.Service, userSvc *user.Service, // mongoLogger *zap.Logger, // logger *slog.Logger, ) *Service { return &Service{ walletSvc: walletSvc, transferStore: transferStore, directDepositStore: directDepositStore, notificationSvc: notificationSvc, userSvc: userSvc, // mongoLogger: mongoLogger, // logger: logger, } } func (s *Service) CreateDirectDeposit( ctx context.Context, req domain.CreateDirectDeposit, ) (domain.DirectDeposit, error) { deposit := domain.DirectDeposit{ CustomerID: req.CustomerID, WalletID: req.WalletID, BankName: req.BankName, AccountNumber: req.AccountNumber, AccountHolder: req.AccountHolder, Amount: req.Amount, ReferenceNumber: req.ReferenceNumber, TransferScreenshot: req.TransferScreenshot, Status: "PENDING", } // Step 1: create the deposit in DB if err := s.directDepositStore.CreateDirectDeposit(ctx, &deposit); err != nil { return domain.DirectDeposit{}, err } // Step 2: prepare common notification metadata raw, _ := json.Marshal(map[string]any{ "deposit_id": deposit.ID, "customer_id": deposit.CustomerID, "amount": deposit.Amount, "status": deposit.Status, "timestamp": time.Now(), }) // ------------------------------- // Step 3a: notify the customer customerNotification := &domain.Notification{ RecipientID: int64(deposit.CustomerID), DeliveryChannel: domain.DeliveryChannelInApp, Reciever: domain.NotificationRecieverSideCustomer, Type: domain.NOTIFICATION_TYPE_WALLET, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Level: domain.NotificationLevelInfo, Priority: 2, Metadata: raw, Payload: domain.NotificationPayload{ Headline: "Direct Deposit Created", Message: fmt.Sprintf("Your direct deposit of %.2f is now pending approval.", deposit.Amount), }, } if err := s.notificationSvc.SendNotification(ctx, customerNotification); err != nil { return domain.DirectDeposit{}, err } // ------------------------------- // Step 3b: notify admins adminNotification := &domain.Notification{ RecipientID: 0, // 0 or special ID for admin-wide notifications DeliveryChannel: domain.DeliveryChannelInApp, Reciever: domain.NotificationRecieverSideAdmin, Type: domain.NOTIFICATION_TYPE_APPROVAL_REQUIRED, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Level: domain.NotificationLevelInfo, Priority: 2, Metadata: raw, Payload: domain.NotificationPayload{ Headline: "New Direct Deposit Pending Approval", Message: fmt.Sprintf("Customer #%d has created a direct deposit of %.2f that requires your approval.", deposit.CustomerID, deposit.Amount), }, } if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil { return domain.DirectDeposit{}, err } return deposit, nil } func (s *Service) GetDirectDepositsByStatus( ctx context.Context, status string, page int, pageSize int, ) ([]domain.DirectDeposit, int64, error) { deposits, total, err := s.directDepositStore.GetDirectDepositsByStatus( ctx, status, page, pageSize, ) if err != nil { return nil, 0, err } return deposits, total, nil } func (s *Service) ApproveDirectDeposit( ctx context.Context, depositID int, adminID int, ) error { // Step 1: fetch deposit (ensure it exists) deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID) if err != nil { return err } // Step 2: approve in DB if err := s.directDepositStore.ApproveDirectDeposit(ctx, depositID, adminID); err != nil { return err } // Step 3: credit wallet balance wallet, err := s.walletSvc.GetCustomerWallet(ctx, int64(deposit.CustomerID)) if err != nil { return err } if _, err := s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(deposit.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, "", ); err != nil { return err } // Step 4: record transfer transfer := domain.CreateTransfer{ Amount: domain.Currency(deposit.Amount), Verified: true, Message: "Direct deposit approved and credited", Type: domain.DEPOSIT, PaymentMethod: domain.TRANSFER_DIRECT, ReceiverWalletID: domain.ValidInt64{Valid: true, Value: wallet.RegularID}, ReferenceNumber: deposit.ReferenceNumber, Status: string(domain.DepositStatusCompleted), } if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil { return err } // Step 5: send customer notification raw, _ := json.Marshal(map[string]any{ "deposit_id": deposit.ID, "amount": deposit.Amount, "status": "APPROVED", "timestamp": time.Now(), }) notification := &domain.Notification{ RecipientID: int64(deposit.CustomerID), DeliveryChannel: domain.DeliveryChannelInApp, Reciever: domain.NotificationRecieverSideCustomer, Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Level: domain.NotificationLevelInfo, Priority: 2, Metadata: raw, Payload: domain.NotificationPayload{ Headline: "Direct Deposit Approved", Message: fmt.Sprintf("Your direct deposit of %.2f has been approved and credited to your wallet.", deposit.Amount), }, } if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { return err } return nil } func (s *Service) RejectDirectDeposit( ctx context.Context, depositID int, adminID int, reason string, ) error { // Step 1: fetch deposit to ensure it exists deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID) if err != nil { return err } // Step 2: reject operation if err := s.directDepositStore.RejectDirectDeposit(ctx, depositID, adminID, reason); err != nil { return err } // Step 3: send customer notification raw, _ := json.Marshal(map[string]any{ "deposit_id": deposit.ID, "amount": deposit.Amount, "status": "REJECTED", "reason": reason, "timestamp": time.Now(), }) notification := &domain.Notification{ RecipientID: int64(deposit.CustomerID), DeliveryChannel: domain.DeliveryChannelInApp, Reciever: domain.NotificationRecieverSideCustomer, Type: domain.NOTIFICATION_TYPE_TRANSFER_REJECTED, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Level: domain.NotificationLevelWarning, Priority: 2, Metadata: raw, Payload: domain.NotificationPayload{ Headline: "Direct Deposit Rejected", Message: fmt.Sprintf("Your direct deposit of %.2f was rejected. Reason: %s", deposit.Amount, reason), }, } if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { return err } return nil } func (s *Service) GetDirectDepositByID( ctx context.Context, depositID int, ) (*domain.DirectDeposit, error) { deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID) if err != nil { return nil, err } return deposit, nil } func (s *Service) DeleteDirectDeposit( ctx context.Context, depositID int, ) error { // Optional: fetch first to ensure deposit exists _, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID) if err != nil { return err } // Perform deletion if err := s.directDepositStore.DeleteDirectDeposit(ctx, depositID); err != nil { return err } return nil }