chore: build

This commit is contained in:
2026-06-14 17:50:35 +02:00
parent 3c92a0371f
commit 3a0f725159
93 changed files with 5311 additions and 4224 deletions
+87 -61
View File
@@ -1,7 +1,3 @@
// ============================================
// handlers/basket_handlers_CORRIGES.go
// ============================================
package handlers
import (
@@ -12,6 +8,8 @@ import (
"gestion/utils"
"log"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
)
@@ -48,41 +46,38 @@ func AddProductsBasket(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Quantité invalide"})
return
}
// Si product_id fourni par le mobile, on l'utilise directement (plus fiable)
if req.ProductID > 0 || req.NameProduct == "" || req.Category == "" {
stock, err := database.GetProductStockByID(req.ProductID)
if err != nil {
log.Printf("❌ [ADD_PANIER] Produit %d non trouvé: %v", req.ProductID, err)
c.JSON(http.StatusNotFound, gin.H{"error": "Produit non trouvé"})
return
}
if stock < req.Quantity {
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant", "available": stock})
return
}
if err := database.DecrementProductStockByID(req.ProductID, req.Quantity); err != nil {
log.Printf("❌ [ADD_PANIER] Erreur décrement stock product_id=%d: %v", req.ProductID, err)
utils.ServerErr(c, "Impossible d'ajouter le produit au panier", err)
return
}
panier, err := database.AddProductInBasketByID(req.Username, req.ProductID, req.Quantity)
if err != nil {
log.Printf("❌ [ADD_PANIER] Erreur ajout product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
utils.ServerErr(c, "Impossible d'ajouter le produit au panier", err)
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Produit ajouté au panier avec succès", "panier": panier})
if req.ProductID <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "product_id requis"})
return
}
if p, err := database.GetProductByID(req.ProductID); err == nil && p.ComingSoon {
c.JSON(http.StatusBadRequest, gin.H{"error": "Ce produit n'est pas encore disponible"})
return
}
panier, err := database.AddToBasket(req.Username, req.ProductID, req.Quantity)
if err != nil {
log.Printf("❌ [ADD_PANIER] product_id=%d qty=%.3f: %v", req.ProductID, req.Quantity, err)
if err.Error() == "stock insuffisant" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Stock insuffisant"})
return
}
if strings.Contains(err.Error(), "prix introuvable") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Aucun prix configuré pour ce produit"})
return
}
utils.ServerErr(c, "Impossible d'ajouter le produit au panier", err)
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Produit ajouté au panier avec succès",
"panier": panier,
})
}
// ============================================
// ============================================
// GET /api/v1/panier/:username
// Récupère le panier du client authentifié
func GetAllBaskets(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
username := c.Param("username")
@@ -149,11 +144,6 @@ func GetAllBaskets(c *gin.Context) {
})
}
// ============================================
// ✅ SÉCURISÉ: DeleteProductFromBasket
// ============================================
// DELETE /api/v1/panier/remove
// Supprime un produit du panier
func DeleteProductFromBasket(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
@@ -206,18 +196,12 @@ func DeleteProductFromBasket(c *gin.Context) {
log.Printf("✅ [DEL_PANIER] Article %d supprimé", req.ID)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Produit supprimé du panier avec succès",
"item_id": req.ID,
"stock_released": true,
"success": true,
"message": "Produit supprimé du panier avec succès",
"item_id": req.ID,
})
}
// ============================================
// ✅ SÉCURISÉ: ClearBasket
// ============================================
// DELETE /api/v1/panier/clear
// Vide le panier du client
func ClearBasket(c *gin.Context) {
database := c.MustGet("database").(*db.Database)
@@ -250,9 +234,8 @@ func ClearBasket(c *gin.Context) {
log.Printf("✅ [CLEAR_PANIER] Panier %s vidé: %d articles supprimés", authUsernameStr, len(baskets))
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Panier vidé avec succès",
"stock_released": len(baskets),
"success": true,
"message": "Panier vidé avec succès",
})
}
@@ -268,6 +251,14 @@ func ValidateBasket(c *gin.Context) {
}
usernameStr := username.(string)
lockKey := fmt.Sprintf("checkout_lock:%s", usernameStr)
locked, errLock := db.Redis.SetNX(db.RedisCtx, lockKey, "1", 30*time.Second).Result()
if errLock != nil || !locked {
c.JSON(http.StatusConflict, gin.H{"error": "Un checkout est déjà en cours pour ce compte"})
return
}
defer db.Redis.Del(db.RedisCtx, lockKey)
var req struct {
DeliveryAddress string `json:"delivery_address" binding:"required"`
UseReferralBalance bool `json:"use_referral_balance"`
@@ -314,7 +305,7 @@ func ValidateBasket(c *gin.Context) {
log.Printf("🛒 [CHECKOUT] Panier: %d articles", len(items))
// ============================================
// 1️⃣b Vérifier le minimum de commande selon la zone
// 1️⃣b Calculer le total et vérifier la présence d'au moins un article payant
// ============================================
var cartTotal float64
for _, item := range items {
@@ -323,6 +314,21 @@ func ValidateBasket(c *gin.Context) {
}
}
// Détecter si le panier contient un article récompense (prix 0)
hasRewardItem := false
for _, item := range items {
if price, ok := item["price"].(float64); ok && price == 0 {
hasRewardItem = true
break
}
}
// Si récompense présente mais aucun produit payant → refuser
if hasRewardItem && cartTotal <= 0 {
log.Printf("❌ [CHECKOUT] Panier contient uniquement des récompenses pour %s", usernameStr)
c.JSON(http.StatusBadRequest, gin.H{"error": "Vous devez commander au moins un produit de la boutique pour bénéficier de votre récompense"})
return
}
// Récupérer les paramètres globaux (zones + parrainage)
appSettings, _ := database.GetSettings()
@@ -384,9 +390,6 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Zone OK: %s, total=%.2f€ >= %.2f€, crédit parrainage utilisé: %.2f€", zoneResult.ZoneName, cartTotal, zoneResult.MinAmount, referralUsed)
// ============================================
// 2️⃣ Débiter le parrainage AVANT la commande (évite double-spend)
// ============================================
if referralUsed > 0 {
if err := database.DebitReferralBalance(usernameStr, referralUsed); err != nil {
log.Printf("❌ [CHECKOUT] Solde parrainage insuffisant pour %s: %v", usernameStr, err)
@@ -396,6 +399,21 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Crédit parrainage -%.2f€ débité pour %s", referralUsed, usernameStr)
}
// Vérifier que tous les produits du panier ont encore un prix actif
unavailable, err := database.GetUnavailableBasketItems(usernameStr)
if err != nil {
utils.ServerErr(c, "Erreur vérification produits", err)
return
}
if len(unavailable) > 0 {
log.Printf("❌ [CHECKOUT] Produits sans prix actif: %v", unavailable)
c.JSON(http.StatusBadRequest, gin.H{
"error": "Certains produits de votre panier ne sont plus disponibles",
"products": unavailable,
})
return
}
// Vérification option crypto
isCrypto := req.PaymentMethod == "crypto"
if isCrypto {
@@ -429,9 +447,7 @@ func ValidateBasket(c *gin.Context) {
log.Printf("✅ [CHECKOUT] Commande %d créée", commandID)
// ============================================
// PAIEMENT CRYPTO - créer le paiement NowPayments
// ============================================
// 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))
@@ -483,14 +499,24 @@ func ValidateBasket(c *gin.Context) {
go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress)
// ============================================
// 3️⃣ Vider le panier (sans restituer le stock — déjà déduit à l'ajout)
// 3️⃣ Décrémenter le stock et vider le panier
// ============================================
err = database.ClearBasketOnCheckout(usernameStr)
if err != nil {
utils.ServerErr(c, "Impossible de vider le panier", err)
// 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] Panier vidé")
log.Printf("🧹 [CHECKOUT] Stock décrémenté et panier vidé")
// ============================================
// 4️⃣ Auto-assignation livreur (optionnel)