218 lines
6.6 KiB
Go
218 lines
6.6 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
|
|
)
|
|
|
|
// 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,
|
|
})
|
|
}
|