/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)
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
veliCLient := veli.NewClient(cfg, walletSvc)
veliVirtualGameService := veli.New(veliCLient, walletSvc, cfg)
veliVirtualGameService := veli.New(veliCLient, walletSvc, wallet.TransferStore(store), cfg)
recommendationSvc := recommendation.NewService(recommendationRepo)
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"`
ProviderID string `json:"providerId"`
BrandID string `json:"brandId"`
IsAdjustment bool `json:"isAdjustment,omitempty"`
AdjustmentRefund *struct {
Amount float64 `json:"amount"`
Currency string `json:"currency"`

View File

@ -21,13 +21,15 @@ var (
type service struct {
client *Client
walletSvc *wallet.Service
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{
client: client,
walletSvc: walletSvc,
transfetStore: transferStore,
cfg: cfg,
}
}
@ -338,7 +340,7 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
// --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
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 ---
@ -358,18 +360,23 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
bonusBalance = float64(playerWallets[1].Balance)
}
// --- 3. Refund handling ---
// --- 3. Determine refund amount based on IsAdjustment ---
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
} else {
// If cancelType = CANCEL_BET and no explicit adjustmentRefund,
// we may need to look up the original bet transaction and refund that.
// TODO: implement transaction lookup if required by your domain.
return nil, fmt.Errorf("missing adjustmentRefund for CANCEL_BET")
// Regular cancel: fetch original bet amount if needed
originalTransfer, err := s.transfetStore.GetTransferByReference(ctx, req.RefTransactionID)
if err != nil {
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
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)
}
// --- 4. Reload balances after refund ---
// --- 5. Reload balances after refund ---
updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
if err != nil {
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)
}
// --- 5. Build response ---
// --- 6. Build response ---
res := &domain.CancelResponse{
WalletTransactionID: req.TransactionID,
Real: domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency,
Currency: "ETB",
Amount: realBalance,
},
UsedRealAmount: usedReal,
@ -418,7 +425,7 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency,
Currency: "ETB",
Amount: bonusBalance,
}
}
@ -426,6 +433,12 @@ func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
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) {
// --- Signature Params (flattened strings for signing) ---
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 {
// Read the raw body to avoid parsing issues
// Read the raw body
body := c.Body()
if len(body) == 0 {
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
provider, err := identifyBetProvider(body)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Unrecognized request format",
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)
// Try parsing as Veli bet request
var veliReq domain.BetRequest
if err := json.Unmarshal(body, &veliReq); err == nil && veliReq.SessionID != "" && veliReq.BrandID != "" {
// Process as Veli
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), veliReq)
if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
@ -152,17 +137,13 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
})
}
return c.JSON(res)
case "popok":
var req domain.PopOKBetRequest
if err := json.Unmarshal(body, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid PopOK bet request",
Error: err.Error(),
})
}
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &req)
// Try parsing as PopOK bet request
var popokReq domain.PopOKBetRequest
if err := json.Unmarshal(body, &popokReq); err == nil && popokReq.ExternalToken != "" {
// Process as PopOK
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &popokReq)
if err != nil {
code := fiber.StatusInternalServerError
switch err.Error() {
@ -177,13 +158,13 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
})
}
return c.JSON(resp)
}
default:
// 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
@ -513,7 +494,7 @@ func (h *Handler) ListFavorites(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(games)
}
func identifyBetProvider(body []byte) (string, error) {
func IdentifyBetProvider(body []byte) (string, error) {
// Check for Veli signature fields
var veliCheck struct {
SessionID string `json:"sessionId"`