From b4609cdd5b1e27ba53c4617bca8064e3b08f8237 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Fri, 30 May 2025 23:59:55 +0300 Subject: [PATCH] more chapa fix --- cmd/main.go | 10 ++++- internal/config/config.go | 5 +++ internal/repository/store.go | 10 +++++ internal/services/chapa/port.go | 1 + internal/services/chapa/service.go | 61 +++++++++++++++------------ internal/web_server/app.go | 4 ++ internal/web_server/handlers/chapa.go | 4 +- internal/web_server/routes.go | 1 + 8 files changed, 67 insertions(+), 29 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 102d78b..4e87efa 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,6 +19,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" @@ -107,6 +108,13 @@ func main() { logger, ) recommendationSvc := recommendation.NewService(recommendationRepo) + chapaSvc := chapa.NewService( + transaction.TransactionStore(store), + wallet.WalletStore(store), + user.UserStore(store), + referalSvc, + store, + ) httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc) httpserver.StartTicketCrons(*ticketSvc) @@ -115,7 +123,7 @@ func main() { JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, }, userSvc, - ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg) + ticketSvc, betSvc, chapaSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg) logger.Info("Starting server", "port", cfg.Port) if err := app.Run(); err != nil { diff --git a/internal/config/config.go b/internal/config/config.go index eba9702..db606c8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -289,3 +289,8 @@ func (c *Config) loadEnv() error { c.Bet365Token = betToken return nil } + +type ChapaConfig struct { + ChapaPaymentType string `mapstructure:"chapa_payment_type"` + ChapaTransferType string `mapstructure:"chapa_transfer_type"` +} diff --git a/internal/repository/store.go b/internal/repository/store.go index 02e1403..f3e7579 100644 --- a/internal/repository/store.go +++ b/internal/repository/store.go @@ -5,6 +5,7 @@ import ( "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) @@ -39,3 +40,12 @@ func OpenDB(url string) (*pgxpool.Pool, func(), error) { conn.Close() }, nil } + +func (s *Store) BeginTx(ctx context.Context) (*dbgen.Queries, pgx.Tx, error) { + tx, err := s.conn.Begin(ctx) + if err != nil { + return nil, nil, err + } + q := s.queries.WithTx(tx) + return q, tx, nil +} diff --git a/internal/services/chapa/port.go b/internal/services/chapa/port.go index 2c0d267..57ca589 100644 --- a/internal/services/chapa/port.go +++ b/internal/services/chapa/port.go @@ -8,4 +8,5 @@ import ( type ChapaPort interface { HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error + HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error } diff --git a/internal/services/chapa/service.go b/internal/services/chapa/service.go index 2272381..ea7915a 100644 --- a/internal/services/chapa/service.go +++ b/internal/services/chapa/service.go @@ -7,7 +7,9 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" + referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/shopspring/decimal" ) @@ -15,36 +17,55 @@ import ( type Service struct { transactionStore transaction.TransactionStore walletStore wallet.WalletStore + userStore user.UserStore + referralStore referralservice.ReferralStore store *repository.Store } +func NewService( + txStore transaction.TransactionStore, + walletStore wallet.WalletStore, + userStore user.UserStore, + referralStore referralservice.ReferralStore, + store *repository.Store, +) *Service { + return &Service{ + transactionStore: txStore, + walletStore: walletStore, + userStore: userStore, + referralStore: referralStore, + store: store, + } +} + func (s *Service) HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error { - tx, err := s.store.Begin(ctx) + _, tx, err := s.store.BeginTx(ctx) if err != nil { return err } defer tx.Rollback(ctx) - // 1. Fetch transaction + // Use your services normally (they don’t use the transaction, unless you wire `q`) referenceID, err := strconv.ParseInt(req.Reference, 10, 64) + if err != nil { + return fmt.Errorf("invalid reference ID: %w", err) + } + txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID) if err != nil { return err } if txn.Verified { - return nil // already processed + return nil } - // 2. Compare amount webhookAmount, _ := decimal.NewFromString(req.Amount) storedAmount, _ := decimal.NewFromString(txn.Amount.String()) if !webhookAmount.Equal(storedAmount) { return fmt.Errorf("amount mismatch") } - // 3. Update transaction txn.Verified = true - if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, txn.Verified, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil { return err } @@ -53,7 +74,7 @@ func (s *Service) HandleChapaTransferWebhook(ctx context.Context, req domain.Cha } func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error { - tx, err := s.store.Begin(ctx) + _, tx, err := s.store.BeginTx(ctx) if err != nil { return err } @@ -78,11 +99,8 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap return nil // already processed } - // 3. Amount validation - webhookAmount, _ := decimal.NewFromString(req.Amount) - storedAmount := txn.Amount // assuming it's domain.Currency (decimal.Decimal alias) - - if webhookAmount.LessThan(storedAmount) { + webhookAmount, _ := strconv.ParseFloat(req.Amount, 32) + if webhookAmount < float64(txn.Amount) { return fmt.Errorf("webhook amount is less than expected") } @@ -93,7 +111,7 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap } // 5. Update wallet balance - newBalance := wallet.Balance.Add(storedAmount) + newBalance := wallet.Balance + txn.Amount if err := s.walletStore.UpdateBalance(ctx, wallet.ID, newBalance); err != nil { return err } @@ -103,23 +121,14 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap return err } - // 7. Check & generate referral code - hasCode, err := s.userStore.HasReferralCode(ctx, wallet.UserID) + // 7. Check & Create Referral + stats, err := s.referralStore.GetReferralStats(ctx, string(wallet.UserID)) if err != nil { return err } - if !hasCode { - code := misc.GenerateReferralCode(req.FirstName) - - if err := s.userStore.SetReferralCode(ctx, wallet.UserID, code); err != nil { - return err - } - - if err := s.referralStore.CreateReferralCode(ctx, domain.ReferralCode{ - Code: code, - Amount: config.ReferralRewardBase, - }); err != nil { + if stats == nil { + if err := s.referralStore.CreateReferral(ctx, wallet.UserID); err != nil { return err } } diff --git a/internal/web_server/app.go b/internal/web_server/app.go index 18e4411..d7c0b46 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -8,6 +8,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/company" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" @@ -44,6 +45,7 @@ type App struct { userSvc *user.Service betSvc *bet.Service virtualGameSvc virtualgameservice.VirtualGameService + chapaSvc *chapa.Service walletSvc *wallet.Service transactionSvc *transaction.Service ticketSvc *ticket.Service @@ -65,6 +67,7 @@ func NewApp( userSvc *user.Service, ticketSvc *ticket.Service, betSvc *bet.Service, + chapaSvc *chapa.Service, walletSvc *wallet.Service, transactionSvc *transaction.Service, branchSvc *branch.Service, @@ -104,6 +107,7 @@ func NewApp( userSvc: userSvc, ticketSvc: ticketSvc, betSvc: betSvc, + chapaSvc: chapaSvc, walletSvc: walletSvc, transactionSvc: transactionSvc, branchSvc: branchSvc, diff --git a/internal/web_server/handlers/chapa.go b/internal/web_server/handlers/chapa.go index 0e69c83..bfd1541 100644 --- a/internal/web_server/handlers/chapa.go +++ b/internal/web_server/handlers/chapa.go @@ -299,7 +299,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error { } switch txType.Type { - case config.ChapaTransferType: + case config.ChapaConfig.ChapaTransferType: var payload domain.ChapaWebHookTransfer if err := c.BodyParser(&payload); err != nil { return domain.UnProcessableEntityResponse(c) @@ -315,7 +315,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error { StatusCode: fiber.StatusOK, }) - case config.ChapaPaymentType: + case config.ChapaConfig.ChapaPaymentType: var payload domain.ChapaWebHookPayment if err := c.BodyParser(&payload); err != nil { return domain.UnProcessableEntityResponse(c) diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index a7c0810..0e1acfc 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -18,6 +18,7 @@ func (a *App) initAppRoutes() { a.logger, a.NotidicationStore, a.validator, + a.chapaSvc, a.walletSvc, a.referralSvc, a.virtualGameSvc,