diff --git a/backend/gestion/db/db_commands.go b/backend/gestion/db/db_commands.go index 38b69f46..dba5f664 100644 --- a/backend/gestion/db/db_commands.go +++ b/backend/gestion/db/db_commands.go @@ -148,8 +148,26 @@ func (d *Database) CreateCommand(username string) (*models.Command, error) { return nil, fmt.Errorf("erreur lors de l'insertion des items: %w", err) } - if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil { - return nil, fmt.Errorf("erreur lors du vidage du panier: %w", err) + // Décrémenter le stock et vider le panier de manière atomique. + if err := d.GDB.Transaction(func(tx *gorm.DB) error { + for _, item := range basketItems { + if item.IsReward { + continue + } + var currentStock float64 + if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, item.ProductID).Scan(¤tStock).Error; err != nil { + return fmt.Errorf("erreur lecture stock produit %d: %w", item.ProductID, err) + } + if currentStock < item.Quantity { + return fmt.Errorf("stock insuffisant pour le produit %d", item.ProductID) + } + if err := tx.Exec(`UPDATE products SET stock = stock - ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, item.Quantity, item.ProductID).Error; err != nil { + return fmt.Errorf("erreur décrémentation stock produit %d: %w", item.ProductID, err) + } + } + return tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error + }); err != nil { + return nil, err } command := &models.Command{ @@ -256,8 +274,27 @@ func (d *Database) CreateCommandWithAddress(username, deliveryAddress string) (* return nil, fmt.Errorf("erreur insertion items: %w", err) } - if err := d.GDB.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error; err != nil { - log.Printf("⚠️ Erreur vidage panier: %v", err) + // Décrémenter le stock et vider le panier de manière atomique. + if err := d.GDB.Transaction(func(tx *gorm.DB) error { + for _, item := range basketItems { + if item.IsReward { + continue + } + var currentStock float64 + if err := tx.Raw(`SELECT stock FROM products WHERE id = ? FOR UPDATE`, item.ProductID).Scan(¤tStock).Error; err != nil { + return fmt.Errorf("erreur lecture stock produit %d: %w", item.ProductID, err) + } + if currentStock < item.Quantity { + return fmt.Errorf("stock insuffisant pour le produit %d", item.ProductID) + } + if err := tx.Exec(`UPDATE products SET stock = stock - ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, item.Quantity, item.ProductID).Error; err != nil { + return fmt.Errorf("erreur décrémentation stock produit %d: %w", item.ProductID, err) + } + } + return tx.Exec(`DELETE FROM baskets WHERE username = ?`, username).Error + }); err != nil { + log.Printf("❌ Erreur décrément stock / vidage panier: %v", err) + return nil, err } sanitizedAddress := sanitizeLogMessage(deliveryAddress) diff --git a/backend/gestion/handlers/panier.go b/backend/gestion/handlers/panier.go index bf918923..c6119f8c 100644 --- a/backend/gestion/handlers/panier.go +++ b/backend/gestion/handlers/panier.go @@ -434,6 +434,10 @@ func ValidateBasket(c *gin.Context) { _ = database.CreditClientReferral(usernameStr, referralUsed) } log.Printf("❌ [CHECKOUT] Erreur création commande: %v", err) + if strings.Contains(err.Error(), "stock insuffisant") { + c.JSON(http.StatusBadRequest, gin.H{"error": "Désolé ! Le stock ou le produit n'est plus disponible, repasse commande"}) + return + } c.JSON(http.StatusInternalServerError, gin.H{"error": "Erreur création commande"}) return } @@ -447,7 +451,6 @@ func ValidateBasket(c *gin.Context) { log.Printf("✅ [CHECKOUT] Commande %d créée", commandID) - // Pour le paiement crypto, le panier/stock sera décrémenté à la confirmation du webhook NowPayments. if isCrypto { np := c.MustGet("nowpayments").(*services.NowPaymentsClient) ipnURL := fmt.Sprintf("%s/api/v1/webhooks/nowpayments", getBaseURL(c)) @@ -507,27 +510,7 @@ func ValidateBasket(c *gin.Context) { go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress) // ============================================ - // 3️⃣ Décrémenter le stock et vider le panier - // ============================================ - err = database.ClearBasketOnCheckout(usernameStr) - if err != nil { - // Stock insuffisant au moment du checkout (concurrent) → annuler la commande - if strings.Contains(err.Error(), "stock insuffisant") { - _ = database.CancelCryptoCommand(commandID) - if referralUsed > 0 { - _ = database.CreditClientReferral(usernameStr, referralUsed) - } - log.Printf("❌ [CHECKOUT] Stock insuffisant au moment de la validation: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Désolé ! Tu as trop attendu pour passer commande! Le stock ou le produit n'est plus disponible, repasse commande"}) - return - } - utils.ServerErr(c, "Impossible de valider le panier", err) - return - } - log.Printf("🧹 [CHECKOUT] Stock décrémenté et panier vidé") - - // ============================================ - // 4️⃣ Auto-assignation livreur (optionnel) + // 3️⃣ Auto-assignation livreur (optionnel) // ============================================ var assigned bool var assignInfo gin.H