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/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 {

View File

@ -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"`
}

View File

@ -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
}

View File

@ -8,4 +8,5 @@ import (
type ChapaPort interface {
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/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 dont 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
}
}

View File

@ -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,

View File

@ -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)

View File

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