306 lines
8.4 KiB
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
|
|
}
|