API — Panier¶
Cette page documente CartController, monté sous /api/cart. Le panier est libre — accessible connecté ou non connecté (mode invité).
Contrôleur couvert
CartController → GET /, 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
CartServicefusionne 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¶
| 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¶
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¶
Réponse — 200 OK¶
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¶
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¶
- Parcours checkout — étape suivante après l'ajout au panier
- Exceptions métier —
CartItemNotFoundException,CartAccessDeniedException