208 lines
6.6 KiB
Go
208 lines
6.6 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
)
|
|
|
|
var (
|
|
ErrWalletNotTransferable = errors.New("wallet is not transferable")
|
|
ErrInsufficientBalance = errors.New("wallet balance is insufficient")
|
|
)
|
|
|
|
func (s *Service) CreateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) {
|
|
senderWallet, err := s.walletStore.GetWalletByID(ctx, transfer.SenderWalletID.Value)
|
|
receiverWallet, err := s.walletStore.GetWalletByID(ctx, transfer.ReceiverWalletID)
|
|
if err != nil {
|
|
return domain.Transfer{}, fmt.Errorf("failed to get sender wallet: %w", err)
|
|
}
|
|
|
|
// Check if wallet has sufficient balance
|
|
if senderWallet.Balance < transfer.Amount || senderWallet.Balance == 0 {
|
|
// Send notification to customer
|
|
customerNotification := &domain.Notification{
|
|
RecipientID: receiverWallet.UserID,
|
|
Type: domain.NOTIFICATION_TYPE_TRANSFER,
|
|
Level: domain.NotificationLevelError,
|
|
Reciever: domain.NotificationRecieverSideCustomer,
|
|
DeliveryChannel: domain.DeliveryChannelInApp,
|
|
Payload: domain.NotificationPayload{
|
|
Headline: "Service Temporarily Unavailable",
|
|
Message: "Our payment system is currently under maintenance. Please try again later.",
|
|
},
|
|
Priority: 2,
|
|
Metadata: []byte(fmt.Sprintf(`{
|
|
"transfer_amount": %d,
|
|
"current_balance": %d,
|
|
"wallet_id": %d,
|
|
"notification_type": "customer_facing"
|
|
}`, transfer.Amount, senderWallet.Balance, transfer.SenderWalletID.Value)),
|
|
}
|
|
|
|
// Send notification to admin team
|
|
adminNotification := &domain.Notification{
|
|
RecipientID: senderWallet.UserID,
|
|
Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT,
|
|
Level: domain.NotificationLevelError,
|
|
Reciever: domain.NotificationRecieverSideAdmin,
|
|
DeliveryChannel: domain.DeliveryChannelEmail, // Or any preferred admin channel
|
|
Payload: domain.NotificationPayload{
|
|
Headline: "CREDIT WARNING: System Running Out of Funds",
|
|
Message: fmt.Sprintf(
|
|
"Wallet ID %d has insufficient balance for transfer. Current balance: %.2f, Attempted transfer: %.2f",
|
|
transfer.SenderWalletID.Value,
|
|
float64(senderWallet.Balance)/100,
|
|
float64(transfer.Amount)/100,
|
|
),
|
|
},
|
|
Priority: 1, // High priority for admin alerts
|
|
Metadata: fmt.Appendf(nil, `{
|
|
"wallet_id": %d,
|
|
"balance": %d,
|
|
"required_amount": %d,
|
|
"notification_type": "admin_alert"
|
|
}`, transfer.SenderWalletID.Value, senderWallet.Balance, transfer.Amount),
|
|
}
|
|
|
|
// Send both notifications
|
|
if err := s.notificationStore.SendNotification(ctx, customerNotification); err != nil {
|
|
s.logger.Error("failed to send customer notification",
|
|
"user_id", "",
|
|
"error", err)
|
|
}
|
|
|
|
// Get admin recipients and send to all
|
|
adminRecipients, err := s.notificationStore.ListRecipientIDs(ctx, domain.NotificationRecieverSideAdmin)
|
|
if err != nil {
|
|
s.logger.Error("failed to get admin recipients", "error", err)
|
|
} else {
|
|
for _, adminID := range adminRecipients {
|
|
adminNotification.RecipientID = adminID
|
|
if err := s.notificationStore.SendNotification(ctx, adminNotification); err != nil {
|
|
s.logger.Error("failed to send admin notification",
|
|
"admin_id", adminID,
|
|
"error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return domain.Transfer{}, ErrInsufficientBalance
|
|
}
|
|
|
|
// Proceed with transfer if balance is sufficient
|
|
return s.transferStore.CreateTransfer(ctx, transfer)
|
|
}
|
|
|
|
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
|
|
return s.transferStore.GetAllTransfers(ctx)
|
|
}
|
|
|
|
func (s *Service) GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error) {
|
|
return s.transferStore.GetTransferByID(ctx, id)
|
|
}
|
|
|
|
func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
|
return s.transferStore.GetTransfersByWallet(ctx, walletID)
|
|
}
|
|
|
|
func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
|
return s.transferStore.UpdateTransferVerification(ctx, id, verified)
|
|
}
|
|
|
|
func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) {
|
|
receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID)
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
// Add to receiver
|
|
senderWallet, err := s.GetWalletByID(ctx, transfer.SenderWalletID.Value)
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
} else if senderWallet.Balance < transfer.Amount {
|
|
return domain.Transfer{}, ErrInsufficientBalance
|
|
}
|
|
|
|
err = s.walletStore.UpdateBalance(ctx, receiverWallet.ID, receiverWallet.Balance+transfer.Amount)
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
// Log the transfer so that if there is a mistake, it can be reverted
|
|
newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{
|
|
CashierID: transfer.CashierID,
|
|
ReceiverWalletID: receiverWallet.ID,
|
|
Amount: transfer.Amount,
|
|
Type: domain.DEPOSIT,
|
|
PaymentMethod: transfer.PaymentMethod,
|
|
Verified: true,
|
|
})
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
return newTransfer, nil
|
|
|
|
}
|
|
|
|
func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64, amount domain.Currency, paymentMethod domain.PaymentMethod, cashierID domain.ValidInt64) (domain.Transfer, error) {
|
|
|
|
senderWallet, err := s.GetWalletByID(ctx, senderID)
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
if senderWallet.IsTransferable {
|
|
return domain.Transfer{}, ErrWalletNotTransferable
|
|
}
|
|
|
|
receiverWallet, err := s.GetWalletByID(ctx, receiverID)
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
if receiverWallet.IsTransferable {
|
|
return domain.Transfer{}, ErrWalletNotTransferable
|
|
}
|
|
|
|
// 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.transferStore.CreateTransfer(ctx, domain.CreateTransfer{
|
|
SenderWalletID: domain.ValidInt64{
|
|
Value: senderID,
|
|
Valid: true,
|
|
},
|
|
CashierID: cashierID,
|
|
ReceiverWalletID: receiverID,
|
|
Amount: amount,
|
|
Type: domain.WALLET,
|
|
PaymentMethod: paymentMethod,
|
|
Verified: true,
|
|
})
|
|
if err != nil {
|
|
return domain.Transfer{}, err
|
|
}
|
|
|
|
return transfer, nil
|
|
}
|