This commit is contained in:
@@ -22,9 +22,6 @@ type BasketsRequest struct {
|
||||
Quantity float64 `json:"quantity"`
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ✅ SÉCURISÉ: AddProductsBasket
|
||||
// ============================================
|
||||
// POST /api/v1/panier/add
|
||||
func AddProductsBasket(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
@@ -90,7 +87,6 @@ func GetAllBaskets(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
|
||||
authUsername, hasAuth := c.Get("username")
|
||||
if !hasAuth {
|
||||
log.Printf("❌ [GET_PANIER] Username manquant dans JWT")
|
||||
@@ -100,7 +96,6 @@ func GetAllBaskets(c *gin.Context) {
|
||||
|
||||
authUsernameStr := authUsername.(string)
|
||||
|
||||
// ✅ SÉCURITÉ 2: Vérifier que c'est bien l'utilisateur de la session
|
||||
if username != authUsernameStr {
|
||||
log.Printf("❌ [GET_PANIER] ⚠️ TENTATIVE D'ACCÈS AU PANIER NON AUTORISÉE!")
|
||||
log.Printf(" Username du JWT: %s", authUsernameStr)
|
||||
@@ -111,10 +106,8 @@ func GetAllBaskets(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ SÉCURITÉ 3: Forcer l'utilisation du username du JWT
|
||||
username = authUsernameStr
|
||||
|
||||
// ✅ SÉCURITÉ 4: Vérifier que c'est un CLIENT
|
||||
_, err := database.GetClientByUsername(username)
|
||||
if err != nil {
|
||||
log.Printf("❌ [GET_PANIER] Client inexistant: %s", username)
|
||||
@@ -130,7 +123,7 @@ func GetAllBaskets(c *gin.Context) {
|
||||
|
||||
var totalAmount float64
|
||||
for _, item := range baskets {
|
||||
totalAmount += item.Price // price = prix total de la ligne (cumul des ajouts)
|
||||
totalAmount += item.Price
|
||||
}
|
||||
|
||||
log.Printf("✅ [GET_PANIER] Panier %s: %d articles, total=%.2f€", username, len(baskets), totalAmount)
|
||||
@@ -156,7 +149,6 @@ func DeleteProductFromBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
|
||||
authUsername, hasAuth := c.Get("username")
|
||||
if !hasAuth {
|
||||
log.Printf("❌ [DEL_PANIER] Username manquant dans JWT")
|
||||
@@ -168,7 +160,6 @@ func DeleteProductFromBasket(c *gin.Context) {
|
||||
|
||||
log.Printf("🗑️ [DEL_PANIER] Suppression article: id=%d, client=%s", req.ID, authUsernameStr)
|
||||
|
||||
// ✅ SÉCURITÉ 2: Vérifier que l'article appartient à ce client
|
||||
itemUsername, err := database.GetBasketItemOwner(req.ID)
|
||||
|
||||
if err != nil {
|
||||
@@ -187,7 +178,6 @@ func DeleteProductFromBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Supprimer l'article
|
||||
err = database.DeleteProductFromBasket(req.ID)
|
||||
if err != nil {
|
||||
utils.ServerErr(c, "Erreur lors de la suppression", err)
|
||||
@@ -205,7 +195,6 @@ func DeleteProductFromBasket(c *gin.Context) {
|
||||
func ClearBasket(c *gin.Context) {
|
||||
database := c.MustGet("database").(*db.Database)
|
||||
|
||||
// ✅ SÉCURITÉ 1: Récupérer username depuis le contexte (du JWT validé)
|
||||
authUsername, hasAuth := c.Get("username")
|
||||
if !hasAuth {
|
||||
log.Printf("❌ [CLEAR_PANIER] Username manquant dans JWT")
|
||||
@@ -262,8 +251,8 @@ func ValidateBasket(c *gin.Context) {
|
||||
var req struct {
|
||||
DeliveryAddress string `json:"delivery_address" binding:"required"`
|
||||
UseReferralBalance bool `json:"use_referral_balance"`
|
||||
PaymentMethod string `json:"payment_method"` // "cash" (défaut) ou "crypto"
|
||||
PayCurrency string `json:"pay_currency"` // ex: "btc", "eth", "ltc" (requis si crypto)
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
PayCurrency string `json:"pay_currency"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.DeliveryAddress == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Adresse de livraison requise"})
|
||||
@@ -277,7 +266,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
}
|
||||
req.DeliveryAddress = cmd.DeliveryAddress
|
||||
|
||||
// Vérifier que le client a lié son compte Telegram (seulement si les notifications sont activées)
|
||||
if services.TelegramBot != nil && services.TelegramBot.IsNotificationsEnabled() {
|
||||
if _, linked, err := database.GetClientTelegramChatID(usernameStr); err != nil || !linked {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Vous devez lier votre compte Telegram avant de commander"})
|
||||
@@ -287,9 +275,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
|
||||
log.Printf("🛒 [CHECKOUT] Début checkout pour: %s", usernameStr)
|
||||
|
||||
// ============================================
|
||||
// 1️⃣ Vérifier que le panier n'est pas vide
|
||||
// ============================================
|
||||
items, err := database.GetBasketItems(usernameStr)
|
||||
if err != nil {
|
||||
utils.ServerErr(c, "Impossible de récupérer le panier", err)
|
||||
@@ -304,9 +289,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
|
||||
log.Printf("🛒 [CHECKOUT] Panier: %d articles", len(items))
|
||||
|
||||
// ============================================
|
||||
// 1️⃣b Calculer le total et vérifier la présence d'au moins un article payant
|
||||
// ============================================
|
||||
var cartTotal float64
|
||||
for _, item := range items {
|
||||
if price, ok := item["price"].(float64); ok {
|
||||
@@ -314,7 +296,6 @@ 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 {
|
||||
@@ -322,17 +303,13 @@ func ValidateBasket(c *gin.Context) {
|
||||
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()
|
||||
|
||||
// Récupérer le solde parrainage disponible (seulement si le système est activé)
|
||||
var referralBalance float64
|
||||
if req.UseReferralBalance && appSettings.ReferralEnabled {
|
||||
referralBalance, _ = database.GetClientReferralBalance(usernameStr)
|
||||
@@ -366,8 +343,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Règle parrainage : après déduction du crédit, le client doit toujours payer au minimum le seuil de zone.
|
||||
// Ex : zone 50€, crédit 50€ → panier doit être >= 100€
|
||||
var referralUsed float64
|
||||
if req.UseReferralBalance && referralBalance > 0 {
|
||||
effectivePayment := cartTotal - referralBalance
|
||||
@@ -399,7 +374,6 @@ 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)
|
||||
@@ -414,7 +388,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Vérification option crypto
|
||||
isCrypto := req.PaymentMethod == "crypto"
|
||||
if isCrypto {
|
||||
npRaw, npExists := c.Get("nowpayments")
|
||||
@@ -464,7 +437,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
}
|
||||
payResp, err := np.CreatePayment(payReq)
|
||||
if err != nil {
|
||||
// Annuler la commande et restaurer le panier / parrainage
|
||||
_ = database.CancelCryptoCommand(commandID)
|
||||
if referralUsed > 0 {
|
||||
_ = database.CreditClientReferral(usernameStr, referralUsed)
|
||||
@@ -474,7 +446,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Passer la commande en 'pending_payment' (attente confirmation)
|
||||
if _, err := database.DB.Exec(`UPDATE commandes SET status = 'pending_payment', payment_method = 'crypto', updated_at = NOW() WHERE id = $1`, commandID); err != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Erreur mise à jour statut pending_payment: %v", err)
|
||||
}
|
||||
@@ -507,12 +478,8 @@ func ValidateBasket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Notifier immédiatement tous les admins et agents cabine
|
||||
go database.NotifyAllAdminCabine(commandID, usernameStr, req.DeliveryAddress)
|
||||
|
||||
// ============================================
|
||||
// 3️⃣ Auto-assignation livreur (optionnel)
|
||||
// ============================================
|
||||
var assigned bool
|
||||
var assignInfo gin.H
|
||||
|
||||
@@ -532,7 +499,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
if err == nil {
|
||||
log.Printf("👤 [CHECKOUT] Livreur le plus proche: %s (%.2f km)", nearest.Username, nearest.Distance)
|
||||
|
||||
// ✅ CORRECTION: Utiliser CalculateETAWithTomTom au lieu de GetETAWithTraffic
|
||||
travelTime, distance, err := services.CalculateETAWithTomTom(
|
||||
nearest.Location,
|
||||
services.Coordinates{
|
||||
@@ -550,7 +516,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
|
||||
log.Printf("⏱️ [CHECKOUT] ETA calculé: %d min, distance: %.2f km", travelTime, distance)
|
||||
|
||||
// Assigner la commande au livreur
|
||||
err = database.AssignCommandToDeliverymanQueueWithCoords(
|
||||
commandID,
|
||||
nearest.Username,
|
||||
@@ -563,13 +528,10 @@ func ValidateBasket(c *gin.Context) {
|
||||
if err != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Erreur assignation: %v", err)
|
||||
} else {
|
||||
// Mettre à jour le statut du livreur
|
||||
err = database.SetDeliveryPersonStatus(nearest.Username, "busy", commandID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Erreur mise à jour statut livreur: %v", err)
|
||||
}
|
||||
|
||||
// Notifier le livreur de la nouvelle commande
|
||||
notifMsg := fmt.Sprintf("Nouvelle commande #%d assignée - Livraison dans ~%d min (%.2f km)", commandID, travelTime, distance)
|
||||
if referralUsed > 0 {
|
||||
notifMsg += fmt.Sprintf(" | Parrainage client: -%.2f€", referralUsed)
|
||||
@@ -577,8 +539,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
if notifErr := database.NotifyLivreur(nearest.Username, commandID, "new_assignment", notifMsg); notifErr != nil {
|
||||
log.Printf("⚠️ [CHECKOUT] Erreur notification livreur: %v", notifErr)
|
||||
}
|
||||
|
||||
// Notifier le client
|
||||
clientOrderID := database.GetClientOrderID(commandID)
|
||||
clientMsg := fmt.Sprintf("Ta commande #%d est prise en compte ! Merci de rester branché et vigilant sur les notifs à venir.", clientOrderID)
|
||||
database.NotifyClient(usernameStr, commandID, "assigned", clientMsg)
|
||||
@@ -601,9 +561,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
log.Printf("⚠️ [CHECKOUT] Erreur géocodage: %v", err)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 5️⃣ Réponse
|
||||
// ============================================
|
||||
newBalance, _ := database.GetClientReferralBalance(usernameStr)
|
||||
resp := gin.H{
|
||||
"success": true,
|
||||
@@ -630,7 +587,6 @@ func ValidateBasket(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, resp)
|
||||
}
|
||||
|
||||
// getBaseURL construit l'URL de base depuis la requête en cours
|
||||
func getBaseURL(c *gin.Context) string {
|
||||
scheme := "https"
|
||||
if c.Request.TLS == nil && c.GetHeader("X-Forwarded-Proto") != "https" {
|
||||
|
||||
Reference in New Issue
Block a user