package wallet import ( "context" "errors" "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" ) var ( ErrSenderWalletNotTransferable = errors.New("sender wallet is not transferable") ErrReceiverWalletNotTransferable = errors.New("receiver wallet is not transferable") ErrInsufficientBalance = errors.New("wallet balance is insufficient") ) func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { // This is just a transfer log when return s.transferStore.CreateTransfer(ctx, transfer) } func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.TransferDetail, error) { return s.transferStore.GetAllTransfers(ctx) } func (s *Service) GetTransferByReference(ctx context.Context, reference string) (domain.TransferDetail, error) { return s.transferStore.GetTransferByReference(ctx, reference) } func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.TransferDetail, error) { return s.transferStore.GetTransferByID(ctx, id) } func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.TransferDetail, error) { return s.transferStore.GetTransfersByWallet(ctx, walletID) } func (s *Service) GetTransferStats(ctx context.Context, walletID int64) (domain.TransferStats, error) { return s.transferStore.GetTransferStats(ctx, walletID) } func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error { return s.transferStore.UpdateTransferVerification(ctx, id, verified) } func (s *Service) UpdateTransferStatus(ctx context.Context, id int64, status string) error { return s.transferStore.UpdateTransferStatus(ctx, id, status) } func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64, message string) (domain.Transfer, error) { senderWallet, err := s.GetWalletByID(ctx, senderID) if err != nil { return domain.Transfer{}, err } if !senderWallet.IsTransferable { fmt.Printf("Error: %d Sender Wallet is not transferable \n", senderWallet.ID) return domain.Transfer{}, ErrSenderWalletNotTransferable } receiverWallet, err := s.GetWalletByID(ctx, receiverID) if err != nil { return domain.Transfer{}, err } if !receiverWallet.IsTransferable { fmt.Printf("Error: %d Receiver Wallet is not transferable \n", senderWallet.ID) return domain.Transfer{}, ErrReceiverWalletNotTransferable } // 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.CreateTransfer(ctx, domain.CreateTransfer{ Message: message, SenderWalletID: domain.ValidInt64{ Value: senderID, Valid: true, }, ReceiverWalletID: domain.ValidInt64{ Value: receiverID, Valid: true, }, CashierID: cashierID, Amount: amount, Type: domain.WALLET, PaymentMethod: paymentMethod, Verified: true, }) if err != nil { return domain.Transfer{}, err } return transfer, nil } func (s *Service) SendTransferNotification(ctx context.Context, senderWallet domain.Wallet, receiverWallet domain.Wallet, senderRole domain.Role, receiverRole domain.Role, amount domain.Currency) error { // Send notification to sender (this could be any role) that money was transferred senderWalletReceiverSide := domain.ReceiverFromRole(senderRole) senderNotify := &domain.Notification{ RecipientID: senderWallet.UserID, Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS, Level: domain.NotificationLevelSuccess, Reciever: senderWalletReceiverSide, DeliveryChannel: domain.DeliveryChannelInApp, Payload: domain.NotificationPayload{ Headline: "Wallet has been deducted", Message: fmt.Sprintf(`%s %d has been transferred from your wallet`, senderWallet.Currency, amount), }, Priority: 2, Metadata: []byte(fmt.Sprintf(`{ "transfer_amount": %d, "current_balance": %d, "wallet_id": %d, "notification_type": "customer_facing" }`, amount, senderWallet.Balance, senderWallet.ID)), } // Sender notifications if err := s.notificationSvc.SendNotification(ctx, senderNotify); err != nil { s.logger.Error("failed to send sender notification", "user_id", "", "error", err) return err } receiverWalletReceiverSide := domain.ReceiverFromRole(receiverRole) receiverNotify := &domain.Notification{ RecipientID: receiverWallet.UserID, Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS, Level: domain.NotificationLevelSuccess, Reciever: receiverWalletReceiverSide, DeliveryChannel: domain.DeliveryChannelInApp, Payload: domain.NotificationPayload{ Headline: "Wallet has been credited", Message: fmt.Sprintf(`%s %d has been transferred to your wallet`, receiverWallet.Currency, amount), }, Priority: 2, Metadata: []byte(fmt.Sprintf(`{ "transfer_amount": %d, "current_balance": %d, "wallet_id": %d, "notification_type": "customer_facing" }`, amount, receiverWallet.Balance, receiverWallet.ID)), } // Sender notifications if err := s.notificationSvc.SendNotification(ctx, receiverNotify); err != nil { s.logger.Error("failed to send sender notification", "user_id", "", "error", err) return err } return nil } func ApproveTransfer(ctx context.Context, approval domain.ApprovalRequest) error { return nil } /////////////////////////////////Transaction Make-Cheker///////////////////////////////////////////////// // func (s *Service) InitiateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) { // // Set transfer as unverified by default // transfer.Verified = false // // Create the transfer record // newTransfer, err := s.transferStore.CreateTransfer(ctx, transfer) // if err != nil { // return domain.Transfer{}, err // } // // Create approval record // approval := domain.TransactionApproval{ // TransferID: newTransfer.ID, // Status: "pending", // } // if err := s.approvalStore.CreateApproval(ctx, approval); err != nil { // return domain.Transfer{}, err // } // // Notify approvers // go s.notifyApprovers(ctx, newTransfer.ID) // return newTransfer, nil // } // func (s *Service) ApproveTransfer(ctx context.Context, approval domain.ApprovalRequest) error { // // Get the transfer // transfer, err := s.transferStore.GetTransferByID(ctx, approval.TransferID) // if err != nil { // return err // } // // Only allow approval of pending transfers // if transfer.Status != "pending" { // return errors.New("only pending transfers can be approved") // } // // Update approval record // if err := s.approvalStore.UpdateApprovalStatus(ctx, approval.TransferID, "approved", approval.Comments); err != nil { // return err // } // // Execute the actual transfer // if transfer.Type == domain.WALLET { // _, err = s.executeWalletTransfer(ctx, transfer) // return err // } // // For other transfer types, implement similar execution logic // return nil // } // func (s *Service) executeWalletTransfer(ctx context.Context, transfer domain.TransferDetail) (domain.Transfer, error) { // // Original transfer logic from TransferToWallet, but now guaranteed to be approved // senderWallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID.Value) // if err != nil { // return domain.Transfer{}, err // } // receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID.Value) // if err != nil { // return domain.Transfer{}, err // } // // Deduct from sender // if senderWallet.Balance < transfer.Amount { // return domain.Transfer{}, ErrBalanceInsufficient // } // err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID.Value, senderWallet.Balance-transfer.Amount) // if err != nil { // return domain.Transfer{}, err // } // // Add to receiver // err = s.walletStore.UpdateBalance(ctx, transfer.ReceiverWalletID.Value, receiverWallet.Balance+transfer.Amount) // if err != nil { // return domain.Transfer{}, err // } // // Mark transfer as completed // if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, "completed"); err != nil { // return domain.Transfer{}, err // } // // Send notifications // go s.SendTransferNotification(ctx, senderWallet, receiverWallet, // domain.RoleFromString(transfer.SenderWalletID.Value), // domain.RoleFromString(transfer.ReceiverWalletID.Value), // transfer.Amount) // return transfer, nil // } // func (s *Service) RejectTransfer(ctx context.Context, approval domain.ApprovalRequest) error { // // Update approval record // if err := s.approvalStore.UpdateApprovalStatus(ctx, approval.TransferID, "rejected", approval.Comments); err != nil { // return err // } // // Update transfer status // if err := s.transferStore.UpdateTransferStatus(ctx, approval.TransferID, "rejected"); err != nil { // return err // } // // Notify the initiator // go s.notifyInitiator(ctx, approval.TransferID, approval.Comments) // return nil // } // func (s *Service) GetPendingApprovals(ctx context.Context) ([]domain.TransferDetail, error) { // return s.approvalStore.GetPendingApprovals(ctx) // } // func (s *Service) GetTransferApprovalHistory(ctx context.Context, transferID int64) ([]domain.TransactionApproval, error) { // return s.approvalStore.GetApprovalsByTransfer(ctx, transferID) // } // func (s *Service) notifyApprovers(ctx context.Context, transferID int64) { // // Get approvers (could be from a config or role-based) // approvers, err := s.userStore.GetUsersByRole(ctx, "approver") // if err != nil { // s.logger.Error("failed to get approvers", zap.Error(err)) // return // } // transfer, err := s.transferStore.GetTransferByID(ctx, transferID) // if err != nil { // s.logger.Error("failed to get transfer for notification", zap.Error(err)) // return // } // for _, approver := range approvers { // notification := &domain.Notification{ // RecipientID: approver.ID, // Type: domain.NOTIFICATION_TYPE_APPROVAL_REQUIRED, // Level: domain.NotificationLevelWarning, // Reciever: domain.NotificationRecieverSideAdmin, // DeliveryChannel: domain.DeliveryChannelInApp, // Payload: domain.NotificationPayload{ // Headline: "Transfer Approval Required", // Message: fmt.Sprintf("Transfer #%d requires your approval", transfer.ID), // }, // Priority: 1, // Metadata: []byte(fmt.Sprintf(`{ // "transfer_id": %d, // "amount": %d, // "notification_type": "approval_request" // }`, transfer.ID, transfer.Amount)), // } // if err := s.notificationStore.SendNotification(ctx, notification); err != nil { // s.logger.Error("failed to send approval notification", // zap.Int64("approver_id", approver.ID), // zap.Error(err)) // } // } // } // func (s *Service) notifyInitiator(ctx context.Context, transferID int64, comments string) { // transfer, err := s.transferStore.GetTransferByID(ctx, transferID) // if err != nil { // s.logger.Error("failed to get transfer for notification", zap.Error(err)) // return // } // // Determine who initiated the transfer (could be cashier or user) // recipientID := transfer.CashierID.Value // if !transfer.CashierID.Valid { // // If no cashier, assume user initiated // wallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID.Value) // if err != nil { // s.logger.Error("failed to get wallet for notification", zap.Error(err)) // return // } // recipientID = wallet.UserID // } // notification := &domain.Notification{ // RecipientID: recipientID, // Type: domain.NOTIFICATION_TYPE_TRANSFER_REJECTED, // Level: domain.NotificationLevelWarning, // Reciever: domain.NotificationRecieverSideCustomer, // DeliveryChannel: domain.DeliveryChannelInApp, // Payload: domain.NotificationPayload{ // Headline: "Transfer Rejected", // Message: fmt.Sprintf("Your transfer #%d was rejected. Comments: %s", transfer.ID, comments), // }, // Priority: 2, // Metadata: []byte(fmt.Sprintf(`{ // "transfer_id": %d, // "notification_type": "transfer_rejected" // }`, transfer.ID)), // } // if err := s.notificationStore.SendNotification(ctx, notification); err != nil { // s.logger.Error("failed to send rejection notification", // zap.Int64("recipient_id", recipientID), // zap.Error(err)) // } // }