package chapa import ( "context" "fmt" "strconv" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/shopspring/decimal" ) type Service struct { transactionStore transaction.TransactionStore walletStore wallet.WalletStore store *repository.Store } func (s *Service) HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error { tx, err := s.store.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) // 1. Fetch transaction referenceID, err := strconv.ParseInt(req.Reference, 10, 64) txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID) if err != nil { return err } if txn.Verified { return nil // already processed } // 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 } return tx.Commit(ctx) } func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error { tx, err := s.store.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) if req.Status != "success" { return fmt.Errorf("payment status not successful") } // 1. Parse reference ID referenceID, err := strconv.ParseInt(req.TxRef, 10, 64) if err != nil { return fmt.Errorf("invalid tx_ref: %w", err) } // 2. Fetch transaction txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID) if err != nil { return err } if txn.Verified { 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) { return fmt.Errorf("webhook amount is less than expected") } // 4. Fetch wallet wallet, err := s.walletStore.GetWalletByID(ctx, txn.ID) if err != nil { return err } // 5. Update wallet balance newBalance := wallet.Balance.Add(storedAmount) if err := s.walletStore.UpdateBalance(ctx, wallet.ID, newBalance); err != nil { return err } // 6. Mark transaction as verified if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, true, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil { return err } // 7. Check & generate referral code hasCode, err := s.userStore.HasReferralCode(ctx, 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 { return err } } return tx.Commit(ctx) }