package balance import ( "context" "enaklo-pos-be/internal/common/logger" "enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/repository" "errors" "go.uber.org/zap" ) type Config interface { GetPlatformFee() int64 } type BalanceService struct { repo repository.WalletRepository trx repository.TransactionManager crypt repository.Crypto transaction repository.TransactionRepository cfg Config } func NewBalanceService(repo repository.WalletRepository, trx repository.TransactionManager, crypt repository.Crypto, cfg Config, transaction repository.TransactionRepository) *BalanceService { return &BalanceService{ repo: repo, trx: trx, crypt: crypt, cfg: cfg, transaction: transaction, } } func (s *BalanceService) GetByID(ctx context.Context, id int64) (*entity.Balance, error) { balanceDB, err := s.repo.GetByPartnerID(ctx, nil, id) if err != nil { logger.ContextLogger(ctx).Error("error when get branch by id", zap.Error(err)) return nil, err } return &entity.Balance{ PartnerID: id, Balance: balanceDB.Balance, AuthBalance: balanceDB.AuthBalance, }, nil } func (s *BalanceService) WithdrawInquiry(ctx context.Context, req *entity.BalanceWithdrawInquiry) (*entity.BalanceWithdrawInquiryResponse, error) { balanceDB, err := s.repo.GetForUpdate(ctx, nil, req.PartnerID) if err != nil { logger.ContextLogger(ctx).Error("error when get branch by id", zap.Error(err)) return nil, err } if float64(req.Amount) > balanceDB.Balance { logger.ContextLogger(ctx).Error("requested amount exceeds available balance") return nil, errors.New("insufficient balance") } token, err := s.crypt.GenerateJWTWithdraw(&entity.WalletWithdrawRequest{ ID: balanceDB.ID, PartnerID: req.PartnerID, Amount: req.Amount - s.cfg.GetPlatformFee(), Fee: s.cfg.GetPlatformFee(), Total: req.Amount, }) return &entity.BalanceWithdrawInquiryResponse{ PartnerID: req.PartnerID, Amount: req.Amount - s.cfg.GetPlatformFee(), Token: token, Fee: s.cfg.GetPlatformFee(), Total: req.Amount, }, nil } func (s *BalanceService) WithdrawExecute(ctx mycontext.Context, req *entity.WalletWithdrawRequest) (*entity.WalletWithdrawResponse, error) { decodedReq, err := s.crypt.ValidateJWTWithdraw(req.Token) if err != nil || decodedReq.PartnerID != req.PartnerID { logger.ContextLogger(ctx).Error("invalid withdrawal token", zap.Error(err)) return nil, errors.New("invalid withdrawal token") } trx, _ := s.trx.Begin(ctx) wallet, err := s.repo.GetForUpdate(ctx, trx, decodedReq.PartnerID) if err != nil { logger.ContextLogger(ctx).Error("error retrieving wallet by partner ID", zap.Error(err)) trx.Rollback() return nil, err } totalAmount := float64(decodedReq.Total) if totalAmount > wallet.Balance { logger.ContextLogger(ctx).Error("insufficient balance for withdrawal", zap.Float64("available", wallet.Balance), zap.Float64("requested", totalAmount)) trx.Rollback() return nil, errors.New("insufficient balance") } wallet.Balance -= totalAmount wallet.AuthBalance += totalAmount if _, err := s.repo.Update(ctx, trx, wallet); err != nil { logger.ContextLogger(ctx).Error("error updating wallet balance", zap.Error(err)) trx.Rollback() return nil, err } transaction := &entity.Transaction{ PartnerID: wallet.PartnerID, TransactionType: "WITHDRAW", ReferenceID: "", Status: "WAITING_APPROVAL", CreatedBy: ctx.RequestedBy(), Amount: totalAmount, } transaction, err = s.transaction.Create(ctx, trx, transaction) if err != nil { logger.ContextLogger(ctx).Error("error creating transaction record", zap.Error(err)) trx.Rollback() return nil, err } if err := trx.Commit().Error; err != nil { logger.ContextLogger(ctx).Error("error committing transaction", zap.Error(err)) return nil, err } response := &entity.WalletWithdrawResponse{ TransactionID: transaction.ID, Status: "WAITING_APPROVAL", } return response, nil }