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

217 lines
6.6 KiB
Go

package wallet
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
// InitiateDirectDeposit creates a pending deposit request
func (s *Service) InitiateDirectDeposit(
ctx context.Context,
customerID int64,
amount domain.Currency,
bankRef string, // Mobile banking transaction reference
senderAccount string, // Customer's account number
) (domain.DirectDeposit, error) {
// Get customer's betting wallet
customerWallet, err := s.GetCustomerWallet(ctx, customerID)
if err != nil {
return domain.DirectDeposit{}, fmt.Errorf("failed to get customer wallet: %w", err)
}
// Create pending deposit record
deposit, err := s.directDepositStore.CreateDirectDeposit(ctx, domain.CreateDirectDeposit{
CustomerID: customerID,
WalletID: customerWallet.ID,
Amount: amount,
BankReference: bankRef,
SenderAccount: senderAccount,
Status: domain.DepositStatusPending,
})
if err != nil {
return domain.DirectDeposit{}, fmt.Errorf("failed to create deposit record: %w", err)
}
// Notify cashiers for manual verification
go s.notifyCashiersForVerification(ctx, deposit.ID, customerID, amount)
return deposit, nil
}
// VerifyDirectDeposit verifies and processes the deposit
func (s *Service) VerifyDirectDeposit(
ctx context.Context,
depositID int64,
cashierID int64,
isVerified bool,
verificationNotes string,
) (domain.DirectDeposit, error) {
// Get the deposit record
deposit, err := s.directDepositStore.GetDirectDeposit(ctx, depositID)
if err != nil {
return domain.DirectDeposit{}, fmt.Errorf("failed to get deposit: %w", err)
}
// Validate deposit status
if deposit.Status != domain.DepositStatusPending {
return domain.DirectDeposit{}, errors.New("only pending deposits can be verified")
}
// Update based on verification result
if isVerified {
// Credit the wallet
err = s.walletStore.UpdateBalance(ctx, deposit.WalletID,
deposit.Wallet.Balance+deposit.Amount)
if err != nil {
return domain.DirectDeposit{}, fmt.Errorf("failed to update wallet balance: %w", err)
}
// Publish wallet update event
// go s.publishWalletUpdate(ctx, deposit.WalletID, deposit.Wallet.UserID,
// deposit.Wallet.Balance+deposit.Amount, "direct_deposit_verified")
// Update deposit status
deposit.Status = domain.DepositStatusCompleted
} else {
deposit.Status = domain.DepositStatusRejected
}
// Update deposit record
updatedDeposit, err := s.directDepositStore.UpdateDirectDeposit(ctx, domain.UpdateDirectDeposit{
ID: depositID,
Status: deposit.Status,
VerifiedBy: cashierID,
VerificationNotes: verificationNotes,
VerifiedAt: time.Now(),
})
if err != nil {
return domain.DirectDeposit{}, fmt.Errorf("failed to update deposit: %w", err)
}
// Notify customer of verification result
go s.notifyCustomerVerificationResult(ctx, updatedDeposit)
return updatedDeposit, nil
}
// GetPendingDirectDeposits returns deposits needing verification
func (s *Service) GetPendingDirectDeposits(ctx context.Context) ([]domain.DirectDeposit, error) {
return s.directDepositStore.GetDirectDepositsByStatus(ctx, domain.DepositStatusPending)
}
// Helper functions
func (s *Service) notifyCashiersForVerification(ctx context.Context, depositID, customerID int64, amount domain.Currency) {
cashiers, _, err := s.userSvc.GetAllCashiers(ctx, domain.UserFilter{Role: string(domain.RoleCashier)})
if err != nil {
s.logger.Error("failed to get cashiers for notification",
"error", err,
"deposit_id", depositID)
return
}
customer, err := s.userSvc.GetUserByID(ctx, customerID)
if err != nil {
s.logger.Error("failed to get customer details",
"error", err,
"customer_id", customerID)
return
}
for _, cashier := range cashiers {
metadataMap := map[string]interface{}{
"deposit_id": depositID,
"customer_id": customerID,
"amount": amount.Float32(),
}
metadataJSON, err := json.Marshal(metadataMap)
if err != nil {
s.logger.Error("failed to marshal notification metadata",
"error", err,
"deposit_id", depositID)
continue
}
notification := &domain.Notification{
RecipientID: cashier.ID,
Type: domain.NotificationTypeDepositVerification,
Level: domain.NotificationLevelInfo,
DeliveryChannel: domain.DeliveryChannelInApp,
Payload: domain.NotificationPayload{
Headline: "Direct Deposit Requires Verification",
Message: fmt.Sprintf("Customer %s deposited %.2f - please verify", customer.FirstName+" "+customer.LastName, amount.Float32()),
},
Metadata: metadataJSON,
}
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
s.logger.Error("failed to send verification notification",
"cashier_id", cashier.ID,
"error", err)
}
}
}
func (s *Service) notifyCustomerVerificationResult(ctx context.Context, deposit domain.DirectDeposit) {
var (
headline string
message string
level domain.NotificationLevel
)
if deposit.Status == domain.DepositStatusCompleted {
headline = "Deposit Verified"
message = fmt.Sprintf("Your deposit of %.2f has been credited to your wallet", deposit.Amount.Float32())
level = domain.NotificationLevelSuccess
} else {
headline = "Deposit Rejected"
message = fmt.Sprintf("Your deposit of %.2f was not verified. Reason: %s",
deposit.Amount.Float32(), deposit.VerificationNotes)
level = domain.NotificationLevelError
}
metadataMap := map[string]interface{}{
"deposit_id": deposit.ID,
"amount": deposit.Amount.Float32(),
"status": string(deposit.Status),
}
metadataJSON, err := json.Marshal(metadataMap)
if err != nil {
s.logger.Error("failed to marshal notification metadata",
"error", err,
"deposit_id", deposit.ID)
return
}
notification := &domain.Notification{
RecipientID: deposit.CustomerID,
Type: domain.NotificationTypeDepositResult,
Level: level,
DeliveryChannel: domain.DeliveryChannelInApp,
Payload: domain.NotificationPayload{
Headline: headline,
Message: message,
},
Metadata: metadataJSON,
}
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
s.logger.Error("failed to send deposit result notification",
"customer_id", deposit.CustomerID,
"error", err)
}
}
// func (s *Service) publishWalletUpdate(ctx context.Context, walletID, userID int64, newBalance domain.Currency, trigger string) {
// s.kafkaProducer.Publish(ctx, fmt.Sprint(walletID), event.WalletEvent{
// EventType: event.WalletBalanceUpdated,
// WalletID: walletID,
// UserID: userID,
// Balance: newBalance,
// Trigger: trigger,
// })
// }