Aller au contenu

API — Panier

Cette page documente CartController, monté sous /api/cart. Le panier est libre — accessible connecté ou non connecté (mode invité).

Contrôleur couvert

CartControllerGET /, POST /add, PUT /item/{id}, DELETE /item/{id}, DELETE /

Le panier est volontairement libre d'accès : les chaos de protection (Chaos Scripting) ne s'appliquent qu'au parcours checkout, jamais à l'ajout au panier. Cette séparation reflète le comportement des sites e-commerce réels où ajouter au panier est une opération « gratuite » qui ne mérite pas de token anti-rejeu.


Vue d'ensemble

Méthode Endpoint Auth Description
GET /api/cart Aucune Contenu du panier courant (items, total, count)
POST /api/cart/add Aucune Ajoute un produit au panier (cumule si déjà présent)
PUT /api/cart/item/{id} Aucune Met à jour la quantité d'un article
DELETE /api/cart/item/{id} Aucune Retire un article du panier
DELETE /api/cart Aucune Vide entièrement le panier

Sessions invité vs. connecté

Le panier est lié à la HttpSession, pas à l'utilisateur. Conséquences :

  • Un utilisateur non connecté (guest) peut constituer un panier — il est associé à son JSESSIONID.
  • Lors du login, le service CartService fusionne automatiquement le panier invité avec celui de l'utilisateur.
  • Lors du logout, le panier utilisateur reste attaché à la session jusqu'à expiration du cookie (30 min par défaut) ou à la déconnexion complète.
flowchart LR
  G[Visiteur anonyme] -->|POST /cart/add| S1[Session JSESSIONID#1<br/>panier guest]
  G -->|POST /auth/login| S2[Session avec<br/>LOGGED_IN_USER]
  S1 -.fusion.-> S2
  S2 -->|POST /cart/add| S2
  S2 -->|POST /auth/logout| S3[Session vidée<br/>panier perdu]

GET /api/cart

Retourne le contenu complet du panier associé à la session courante. Retourne un panier vide (items: []) pour un visiteur anonyme sans activité.

Auth : aucune Contrôleur : CartController.getCart() Service : CartService.getCartItems(session) + CartService.getCartTotal(session)

Réponse — 200 OK

{
  "items": [
    {
      "id": 1234,
      "productId": 42,
      "quantity": 2,
      "price": 149.99,
      "addedAt": "2026-04-08T10:30:00Z",
      "productName": "Casque audio Bluetooth",
      "productImage": "/images/products/42.jpg",
      "productPrice": 149.99
    }
  ],
  "total": 299.98,
  "itemCount": 1
}
Champ Description
items[] Liste des lignes de panier
items[].id Identifiant du CartItem (DB)
items[].productId Identifiant du produit
items[].quantity Quantité demandée
items[].price Prix figé au moment de l'ajout (utile si le prix produit change)
items[].addedAt Timestamp ISO de l'ajout
items[].productName, productImage, productPrice Snapshot produit (pour l'affichage)
total Somme Σ (price × quantity)
itemCount Nombre de lignes distinctes (pas la quantité totale)

Cet endpoint ne retourne jamais d'erreur en fonctionnement normal.


POST /api/cart/add

Ajoute un produit au panier. Si le produit est déjà présent, la quantité est incrémentée (pas remplacée).

Auth : aucune Contrôleur : CartController.addToCart() DTO : AddToCartRequest Service : CartService.addToCart()

Requête

POST /api/cart/add HTTP/1.1
Content-Type: application/json

{
  "productId": 42,
  "quantity": 2
}
Champ Type Requis Contrainte
productId Long oui @NotNull — identifiant produit existant
quantity Integer oui @NotNull, @Min(1) — au moins 1

Réponse — 200 OK

{
  "success": true,
  "message": "Article ajouté au panier",
  "cartItem": {
    "id": 1234,
    "productId": 42,
    "quantity": 2,
    "price": 149.99,
    "addedAt": "2026-04-08T10:30:00Z"
  }
}

Le message localisé provient de la clé cart.item.added.

Codes d'erreur

Code Corps Cause
400 {"productId": "Product ID is required"} Champ manquant (validation Jakarta)
400 {"quantity": "Quantity must be at least 1"} Quantité < 1
404 Produit introuvable (propagé depuis CartItemNotFoundException)

PUT /api/cart/item/{id}

Met à jour la quantité d'un article existant dans le panier.

Auth : aucune (mais vérification de propriété de la session) Contrôleur : CartController.updateCartItem() Service : CartService.updateCartItem() + verifyCartItemOwnership()

Paramètres

Paramètre Emplacement Type Description
id path Long Identifiant du CartItem
quantity query Integer Nouvelle quantité (≥ 1)

Requête

PUT /api/cart/item/1234?quantity=5 HTTP/1.1
Cookie: JSESSIONID=...

Réponse — 200 OK

{
  "success": true,
  "message": "Article du panier mis à jour",
  "cartItem": {
    "id": 1234,
    "productId": 42,
    "quantity": 5,
    "price": 149.99,
    "addedAt": "2026-04-08T10:30:00Z"
  }
}

Codes d'erreur

Code Corps Cause Exception Java
400 {"error": "La quantité doit être au moins 1"} quantity == null ou < 1
403 {"error": "Accès non autorisé à cet article du panier"} L'article n'appartient pas à la session courante CartAccessDeniedException
404 {"error": "Article du panier introuvable : 1234"} ID inconnu CartItemNotFoundException

Les exceptions métier sont interceptées par type dans le contrôleur, et converties en codes HTTP correspondants. Voir les fichiers com.perfshop.exception.CartItemNotFoundException et CartAccessDeniedException.


DELETE /api/cart/item/{id}

Retire un article du panier.

Auth : aucune (vérification de propriété) Contrôleur : CartController.removeFromCart() Service : CartService.removeFromCart()

Requête

DELETE /api/cart/item/1234 HTTP/1.1
Cookie: JSESSIONID=...

Réponse — 200 OK

{
  "success": true,
  "message": "Article retiré du panier"
}

Codes d'erreur

Identiques à PUT /api/cart/item/{id} (hors 400 quantité) :

Code Cause
403 Article appartenant à une autre session
404 Article introuvable

DELETE /api/cart

Vide entièrement le panier associé à la session courante.

Auth : aucune Contrôleur : CartController.clearCart() Service : CartService.clearCart()

Réponse — 200 OK

{
  "success": true,
  "message": "Panier vidé"
}

Cet endpoint ne retourne jamais d'erreur — même sur un panier vide, il répond 200 OK. Le message provient de la clé cart.cleared.


Exemple curl

# Récupérer le panier courant (crée une session si pas de cookie)
curl -c /tmp/cookies.txt http://localhost:9080/api/cart

# Ajouter 2 exemplaires du produit 42
curl -b /tmp/cookies.txt \
     -H "Content-Type: application/json" \
     -d '{"productId":42,"quantity":2}' \
     http://localhost:9080/api/cart/add

# Mettre à jour la quantité de l'item 1234
curl -b /tmp/cookies.txt -X PUT \
     "http://localhost:9080/api/cart/item/1234?quantity=5"

# Retirer l'item 1234
curl -b /tmp/cookies.txt -X DELETE \
     http://localhost:9080/api/cart/item/1234

# Vider le panier
curl -b /tmp/cookies.txt -X DELETE http://localhost:9080/api/cart

Points d'attention

Le panier échappe aux chaos

Contrairement à /api/orders (checkout), aucun chaos ne s'applique au panier :

  • Pas de protection Chaos Scripting
  • Pas d'injection de prix falsifié (S5 n'est actif qu'en POST /api/orders)
  • Pas d'injection SQL (S1 n'est actif que sur /api/products/search)

C'est un choix délibéré : le panier est une « zone de confort » qui permet à l'étudiant de construire une commande sans se battre avec des tokens anti-rejeu, puis de voir les chaos s'activer au moment critique du checkout.

Pas de persistance inter-sessions

Le panier vit dans la session HTTP côté backend — il ne survit pas au redémarrage du backend ni à l'expiration du cookie. Il n'y a pas de mécanisme « panier abandonné » persistant en base pour les utilisateurs connectés.


Liens associés