/cancel callback nil dereference fix

This commit is contained in:
Yared Yemane 2025-08-19 19:21:44 +03:00
parent c5fe2b8297
commit 8aefd54562
4 changed files with 53 additions and 58 deletions

View File

@ -153,7 +153,7 @@ func main() {
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger) aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
veliCLient := veli.NewClient(cfg, walletSvc) veliCLient := veli.NewClient(cfg, walletSvc)
veliVirtualGameService := veli.New(veliCLient, walletSvc, cfg) veliVirtualGameService := veli.New(veliCLient, walletSvc, wallet.TransferStore(store), cfg)
recommendationSvc := recommendation.NewService(recommendationRepo) recommendationSvc := recommendation.NewService(recommendationRepo)
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY) chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)

View File

@ -136,6 +136,7 @@ type CancelRequest struct {
CorrelationID string `json:"correlationId,omitempty"` CorrelationID string `json:"correlationId,omitempty"`
ProviderID string `json:"providerId"` ProviderID string `json:"providerId"`
BrandID string `json:"brandId"` BrandID string `json:"brandId"`
IsAdjustment bool `json:"isAdjustment,omitempty"`
AdjustmentRefund *struct { AdjustmentRefund *struct {
Amount float64 `json:"amount"` Amount float64 `json:"amount"`
Currency string `json:"currency"` Currency string `json:"currency"`

View File

@ -19,16 +19,18 @@ var (
) )
type service struct { type service struct {
client *Client client *Client
walletSvc *wallet.Service walletSvc *wallet.Service
cfg *config.Config transfetStore wallet.TransferStore
cfg *config.Config
} }
func New(client *Client, walletSvc *wallet.Service, cfg *config.Config) VeliVirtualGameService { func New(client *Client, walletSvc *wallet.Service, transferStore wallet.TransferStore, cfg *config.Config) VeliVirtualGameService {
return &service{ return &service{
client: client, client: client,
walletSvc: walletSvc, walletSvc: walletSvc,
cfg: cfg, transfetStore: transferStore,
cfg: cfg,
} }
} }
@ -338,7 +340,7 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
// --- 1. Validate PlayerID --- // --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid PlayerID %s", req.PlayerID) return nil, fmt.Errorf("invalid PlayerID %q", req.PlayerID)
} }
// --- 2. Get player wallets --- // --- 2. Get player wallets ---
@ -358,18 +360,23 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
bonusBalance = float64(playerWallets[1].Balance) bonusBalance = float64(playerWallets[1].Balance)
} }
// --- 3. Refund handling --- // --- 3. Determine refund amount based on IsAdjustment ---
var refundAmount float64 var refundAmount float64
if req.AdjustmentRefund.Amount > 0 { if req.IsAdjustment {
if req.AdjustmentRefund.Amount <= 0 {
return nil, fmt.Errorf("missing adjustmentRefund for adjustment cancel")
}
refundAmount = req.AdjustmentRefund.Amount refundAmount = req.AdjustmentRefund.Amount
} else { } else {
// If cancelType = CANCEL_BET and no explicit adjustmentRefund, // Regular cancel: fetch original bet amount if needed
// we may need to look up the original bet transaction and refund that. originalTransfer, err := s.transfetStore.GetTransferByReference(ctx, req.RefTransactionID)
// TODO: implement transaction lookup if required by your domain. if err != nil {
return nil, fmt.Errorf("missing adjustmentRefund for CANCEL_BET") return nil, fmt.Errorf("failed to get original bet for cancellation: %w", err)
}
refundAmount = float64(originalTransfer.Amount)
} }
// For now, we assume refund goes back fully to real wallet // --- 4. Refund to wallet ---
usedReal := refundAmount usedReal := refundAmount
usedBonus := 0.0 usedBonus := 0.0
@ -392,7 +399,7 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return nil, fmt.Errorf("failed to refund wallet: %w", err) return nil, fmt.Errorf("failed to refund wallet: %w", err)
} }
// --- 4. Reload balances after refund --- // --- 5. Reload balances after refund ---
updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to reload balances: %w", err) return nil, fmt.Errorf("failed to reload balances: %w", err)
@ -405,11 +412,11 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
bonusBalance = float64(updatedWallets[1].Balance) bonusBalance = float64(updatedWallets[1].Balance)
} }
// --- 5. Build response --- // --- 6. Build response ---
res := &domain.CancelResponse{ res := &domain.CancelResponse{
WalletTransactionID: req.TransactionID, WalletTransactionID: req.TransactionID,
Real: domain.BalanceDetail{ Real: domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency, Currency: "ETB",
Amount: realBalance, Amount: realBalance,
}, },
UsedRealAmount: usedReal, UsedRealAmount: usedReal,
@ -418,7 +425,7 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
if bonusBalance > 0 { if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{ res.Bonus = &domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency, Currency: "ETB",
Amount: bonusBalance, Amount: bonusBalance,
} }
} }
@ -426,6 +433,12 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return res, nil return res, nil
} }
// Example helper to fetch original bet
// func (s *service) getOriginalBet(ctx context.Context, transactionID string) (*domain.BetRecord, error) {
// // TODO: implement actual lookup
// return &domain.BetRecord{Amount: 50}, nil
// }
func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) { func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) {
// --- Signature Params (flattened strings for signing) --- // --- Signature Params (flattened strings for signing) ---
sigParams := map[string]any{ sigParams := map[string]any{

View File

@ -110,7 +110,7 @@ func (h *Handler) HandlePlayerInfo(c *fiber.Ctx) error {
} }
func (h *Handler) HandleBet(c *fiber.Ctx) error { func (h *Handler) HandleBet(c *fiber.Ctx) error {
// Read the raw body to avoid parsing issues // Read the raw body
body := c.Body() body := c.Body()
if len(body) == 0 { if len(body) == 0 {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
@ -119,26 +119,11 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
}) })
} }
// Try to identify the provider based on the request structure // Try parsing as Veli bet request
provider, err := identifyBetProvider(body) var veliReq domain.BetRequest
if err != nil { if err := json.Unmarshal(body, &veliReq); err == nil && veliReq.SessionID != "" && veliReq.BrandID != "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ // Process as Veli
Message: "Unrecognized request format", res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), veliReq)
Error: err.Error(),
})
}
switch provider {
case "veli":
var req domain.BetRequest
if err := json.Unmarshal(body, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid Veli bet request",
Error: err.Error(),
})
}
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
if err != nil { if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
@ -152,17 +137,13 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
}) })
} }
return c.JSON(res) return c.JSON(res)
}
case "popok": // Try parsing as PopOK bet request
var req domain.PopOKBetRequest var popokReq domain.PopOKBetRequest
if err := json.Unmarshal(body, &req); err != nil { if err := json.Unmarshal(body, &popokReq); err == nil && popokReq.ExternalToken != "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ // Process as PopOK
Message: "Invalid PopOK bet request", resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &popokReq)
Error: err.Error(),
})
}
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &req)
if err != nil { if err != nil {
code := fiber.StatusInternalServerError code := fiber.StatusInternalServerError
switch err.Error() { switch err.Error() {
@ -177,13 +158,13 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
}) })
} }
return c.JSON(resp) return c.JSON(resp)
default:
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Unsupported provider",
Error: "Request format doesn't match any supported provider",
})
} }
// If neither works
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Unsupported provider",
Error: "Request format doesn't match any supported provider",
})
} }
// identifyProvider examines the request body to determine the provider // identifyProvider examines the request body to determine the provider
@ -513,7 +494,7 @@ func (h *Handler) ListFavorites(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(games) return c.Status(fiber.StatusOK).JSON(games)
} }
func identifyBetProvider(body []byte) (string, error) { func IdentifyBetProvider(body []byte) (string, error) {
// Check for Veli signature fields // Check for Veli signature fields
var veliCheck struct { var veliCheck struct {
SessionID string `json:"sessionId"` SessionID string `json:"sessionId"`