Yimaru-BackEnd/internal/services/direct_deposit/service.go

306 lines
8.4 KiB
Go

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
}