more chapa fix

This commit is contained in:
Yared Yemane 2025-05-30 23:59:55 +03:00
parent aef5c4410d
commit b4609cdd5b
8 changed files with 67 additions and 29 deletions

View File

@ -19,6 +19,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "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/company"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
@ -107,6 +108,13 @@ func main() {
logger, logger,
) )
recommendationSvc := recommendation.NewService(recommendationRepo) recommendationSvc := recommendation.NewService(recommendationRepo)
chapaSvc := chapa.NewService(
transaction.TransactionStore(store),
wallet.WalletStore(store),
user.UserStore(store),
referalSvc,
store,
)
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc) httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
httpserver.StartTicketCrons(*ticketSvc) httpserver.StartTicketCrons(*ticketSvc)
@ -115,7 +123,7 @@ func main() {
JwtAccessKey: cfg.JwtKey, JwtAccessKey: cfg.JwtKey,
JwtAccessExpiry: cfg.AccessExpiry, JwtAccessExpiry: cfg.AccessExpiry,
}, userSvc, }, 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) logger.Info("Starting server", "port", cfg.Port)
if err := app.Run(); err != nil { if err := app.Run(); err != nil {

View File

@ -289,3 +289,8 @@ func (c *Config) loadEnv() error {
c.Bet365Token = betToken c.Bet365Token = betToken
return nil return nil
} }
type ChapaConfig struct {
ChapaPaymentType string `mapstructure:"chapa_payment_type"`
ChapaTransferType string `mapstructure:"chapa_transfer_type"`
}

View File

@ -5,6 +5,7 @@ import (
"time" "time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
@ -39,3 +40,12 @@ func OpenDB(url string) (*pgxpool.Pool, func(), error) {
conn.Close() conn.Close()
}, nil }, 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
}

View File

@ -8,4 +8,5 @@ import (
type ChapaPort interface { type ChapaPort interface {
HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error
HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error
} }

View File

@ -7,7 +7,9 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "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/transaction"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
) )
@ -15,36 +17,55 @@ import (
type Service struct { type Service struct {
transactionStore transaction.TransactionStore transactionStore transaction.TransactionStore
walletStore wallet.WalletStore walletStore wallet.WalletStore
userStore user.UserStore
referralStore referralservice.ReferralStore
store *repository.Store 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 { 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 { if err != nil {
return err return err
} }
defer tx.Rollback(ctx) defer tx.Rollback(ctx)
// 1. Fetch transaction // Use your services normally (they dont use the transaction, unless you wire `q`)
referenceID, err := strconv.ParseInt(req.Reference, 10, 64) 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) txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
if err != nil { if err != nil {
return err return err
} }
if txn.Verified { if txn.Verified {
return nil // already processed return nil
} }
// 2. Compare amount
webhookAmount, _ := decimal.NewFromString(req.Amount) webhookAmount, _ := decimal.NewFromString(req.Amount)
storedAmount, _ := decimal.NewFromString(txn.Amount.String()) storedAmount, _ := decimal.NewFromString(txn.Amount.String())
if !webhookAmount.Equal(storedAmount) { if !webhookAmount.Equal(storedAmount) {
return fmt.Errorf("amount mismatch") return fmt.Errorf("amount mismatch")
} }
// 3. Update transaction
txn.Verified = true txn.Verified = true
if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, txn.Verified, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil { if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, txn.Verified, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil {
return err 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 { 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 { if err != nil {
return err return err
} }
@ -78,11 +99,8 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap
return nil // already processed return nil // already processed
} }
// 3. Amount validation webhookAmount, _ := strconv.ParseFloat(req.Amount, 32)
webhookAmount, _ := decimal.NewFromString(req.Amount) if webhookAmount < float64(txn.Amount) {
storedAmount := txn.Amount // assuming it's domain.Currency (decimal.Decimal alias)
if webhookAmount.LessThan(storedAmount) {
return fmt.Errorf("webhook amount is less than expected") 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 // 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 { if err := s.walletStore.UpdateBalance(ctx, wallet.ID, newBalance); err != nil {
return err return err
} }
@ -103,23 +121,14 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap
return err return err
} }
// 7. Check & generate referral code // 7. Check & Create Referral
hasCode, err := s.userStore.HasReferralCode(ctx, wallet.UserID) stats, err := s.referralStore.GetReferralStats(ctx, string(wallet.UserID))
if err != nil { if err != nil {
return err return err
} }
if !hasCode { if stats == nil {
code := misc.GenerateReferralCode(req.FirstName) if err := s.referralStore.CreateReferral(ctx, wallet.UserID); err != nil {
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 {
return err return err
} }
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "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/company"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
@ -44,6 +45,7 @@ type App struct {
userSvc *user.Service userSvc *user.Service
betSvc *bet.Service betSvc *bet.Service
virtualGameSvc virtualgameservice.VirtualGameService virtualGameSvc virtualgameservice.VirtualGameService
chapaSvc *chapa.Service
walletSvc *wallet.Service walletSvc *wallet.Service
transactionSvc *transaction.Service transactionSvc *transaction.Service
ticketSvc *ticket.Service ticketSvc *ticket.Service
@ -65,6 +67,7 @@ func NewApp(
userSvc *user.Service, userSvc *user.Service,
ticketSvc *ticket.Service, ticketSvc *ticket.Service,
betSvc *bet.Service, betSvc *bet.Service,
chapaSvc *chapa.Service,
walletSvc *wallet.Service, walletSvc *wallet.Service,
transactionSvc *transaction.Service, transactionSvc *transaction.Service,
branchSvc *branch.Service, branchSvc *branch.Service,
@ -104,6 +107,7 @@ func NewApp(
userSvc: userSvc, userSvc: userSvc,
ticketSvc: ticketSvc, ticketSvc: ticketSvc,
betSvc: betSvc, betSvc: betSvc,
chapaSvc: chapaSvc,
walletSvc: walletSvc, walletSvc: walletSvc,
transactionSvc: transactionSvc, transactionSvc: transactionSvc,
branchSvc: branchSvc, branchSvc: branchSvc,

View File

@ -299,7 +299,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
} }
switch txType.Type { switch txType.Type {
case config.ChapaTransferType: case config.ChapaConfig.ChapaTransferType:
var payload domain.ChapaWebHookTransfer var payload domain.ChapaWebHookTransfer
if err := c.BodyParser(&payload); err != nil { if err := c.BodyParser(&payload); err != nil {
return domain.UnProcessableEntityResponse(c) return domain.UnProcessableEntityResponse(c)
@ -315,7 +315,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,
}) })
case config.ChapaPaymentType: case config.ChapaConfig.ChapaPaymentType:
var payload domain.ChapaWebHookPayment var payload domain.ChapaWebHookPayment
if err := c.BodyParser(&payload); err != nil { if err := c.BodyParser(&payload); err != nil {
return domain.UnProcessableEntityResponse(c) return domain.UnProcessableEntityResponse(c)

View File

@ -18,6 +18,7 @@ func (a *App) initAppRoutes() {
a.logger, a.logger,
a.NotidicationStore, a.NotidicationStore,
a.validator, a.validator,
a.chapaSvc,
a.walletSvc, a.walletSvc,
a.referralSvc, a.referralSvc,
a.virtualGameSvc, a.virtualGameSvc,