API — Commandes et checkout¶
Cette page documente l'ensemble du parcours checkout PerfShop, qui se déroule en 5 étapes réparties sur deux contrôleurs.
Contrôleurs couverts
CheckoutController(/api/checkout/*) → étapes 2 à 4 (adresse, livraison, paiement) + codes promoOrderController(/api/orders/*) → étape 5 (validation finale) + consultation, annulation, facture
L'étape 1 (login) est documentée dans auth.md.
Les 5 étapes du parcours checkout¶
sequenceDiagram
autonumber
participant F as Frontend React
participant B as Backend
participant DB as MySQL
Note over F: Panier rempli
F->>B: POST /api/auth/login
B-->>F: 200 { securityToken, tokens scripting }
F->>B: POST /api/checkout/address
Note right of B: Validation stricte<br/>ValidationService
B-->>F: 200 { step: "address", nextStep: "shipping" }
F->>B: POST /api/checkout/shipping
Note right of B: standard / express / premium
B-->>F: 200 { step: "shipping", deliveryDays, nextStep: "payment" }
F->>B: POST /api/checkout/payment
Note right of B: Luhn + CVV jeté immédiatement<br/>CVV jamais logué
B-->>F: 200 { step: "payment", summary, nextStep: "confirm" }
F->>B: POST /api/orders { securityToken, items }
B->>DB: INSERT INTO orders, order_items, UPDATE stock
B-->>F: 201 { success, orderNumber, orderId, totalAmount }
Chaque étape verrouille l'étape suivante : une tentative de POST /api/checkout/payment sans avoir d'abord soumis l'adresse retourne un 400 Bad Request. Les états intermédiaires sont stockés en HttpSession sous les clés checkout_address, checkout_shipping, etc.
Contrôle Chaos Scripting¶
Le parcours checkout est le seul chemin protégé par Chaos Scripting. Chaque étape valide des headers anti-rejeu dont la liste et la complexité dépendent du niveau actif :
| Niveau | Nom | Headers requis à chaque étape |
|---|---|---|
| 0 | Désactivé | Aucun |
| 1 | Junior | X-Session-Token, X-Request-ID |
| 2 | Confirmé | + X-Action-Token (renouvelé à chaque étape) |
| 3 | Expert | + X-CSRF-Token, X-Step-Token, X-Signature (HMAC) |
| 4 | Maestro | Idem + clé HMAC dérivée du sessionToken |
Le détail des algorithmes est dans Chaos Scripting. Dans les exemples ci-dessous, on reste en niveau 0 (pas de headers).
Contrôleur CheckoutController¶
POST /api/checkout/address¶
Soumet l'adresse de livraison. C'est l'étape 2 du parcours (après le login).
Auth : session utilisateur + securityToken valide
Contrôleur : CheckoutController.submitAddress()
Validation : ValidationService.validateProfile()
Requête¶
{
"street": "12 rue de la Paix",
"postalCode": "75001",
"city": "Paris",
"region": "Île-de-France",
"country": "FR",
"phone": "0145678901"
}
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
street |
string | oui | 5–200 caractères |
postalCode |
string | oui | Format national (FR : 5 chiffres) |
city |
string | oui | 2–100 caractères, sans accents refusés |
region |
string | non | 2–100 caractères |
country |
string | oui | Code ISO 3166-1 alpha-2 |
phone |
string | oui | Format national, mobiles FR 06/07 rejetés pour livraison |
Réponse — 200 OK¶
En Chaos Scripting niveau ≥ 1, la réponse inclut aussi des headers de rotation (X-Session-Token, etc.) à réutiliser pour l'étape suivante.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Champ obligatoire manquant (clés checkout.error.*_required) |
| 401 | Non connecté (clé auth.error.not_authenticated) |
| 422 | Validation métier échouée (format téléphone, code postal, pays). Corps : {"error": "Données invalides", "fields": {...}} |
Effets de bord en session¶
L'adresse complète est sérialisée et stockée dans HttpSession sous :
checkout_address(concaténation formatée)checkout_city,checkout_country,checkout_phonecheckout_street,checkout_postalCode,checkout_region
Ces valeurs sont relues par OrderController.createOrder() à l'étape finale.
POST /api/checkout/shipping¶
Sélectionne le mode de livraison. Étape 3.
Auth : session + securityToken
Prérequis : checkout_address présent en session
Requête¶
| Valeur | Description |
|---|---|
standard |
Livraison standard (3-5 jours) |
express |
Livraison rapide (1-2 jours) |
premium |
Livraison le lendemain avant 13h |
Réponse — 200 OK¶
{
"success": true,
"step": "shipping",
"nextStep": "payment",
"shippingMethod": "express",
"deliveryDays": 2,
"deliveryLabel": "2 jour(s) ouvré(s)",
"workingDays": true
}
A7 — Jours calendaires au lieu de jours ouvrés
En Chaos Métier niveau 2 et au-delà, le calcul du délai de livraison passe en jours calendaires au lieu de jours ouvrés. Les week-ends et jours fériés ne sont plus exclus. Une livraison « standard 3 jours ouvrés » peut tomber un dimanche. Les champs deliveryDays, deliveryLabel et workingDays reflètent le calcul corrompu. Voir A7.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Mode de livraison invalide (clé checkout.error.shipping_invalid) |
| 400 | Adresse non soumise (clé checkout.error.address_first) |
| 401 | Non connecté |
POST /api/checkout/payment¶
Soumet les informations de paiement. Étape 4.
Auth : session + securityToken
Prérequis : checkout_address et checkout_shipping présents en session
Validation : ValidationService.validateCard() (Luhn, expiration) + validateCvv()
Requête¶
{
"cardHolder": "Alice Durand",
"cardNumber": "4532015112830366",
"expiryMonth": "12",
"expiryYear": "2027",
"cvv": "123"
}
| Champ | Validation |
|---|---|
cardHolder |
Non vide |
cardNumber |
16 chiffres + algorithme de Luhn |
expiryMonth |
1–12 |
expiryYear |
Future, ≤ 2029 |
cvv |
3 chiffres (ou 4 pour Amex) |
Sécurité CVV
Le CVV est validé localement par ValidationService.validateCvv() puis immédiatement jeté. Il n'est :
- jamais stocké en session
- jamais logué, ni en clair ni haché
- jamais transmis à
OrderService
Seul un flag booléen cvvVerified est propagé à l'étape suivante, accompagné des 4 derniers chiffres de la carte (cardLast4) pour l'affichage de confirmation. C'est le comportement attendu en production — PerfShop l'implémente correctement malgré sa nature pédagogique.
Réponse — 200 OK¶
{
"success": true,
"step": "payment",
"nextStep": "confirm",
"summary": {
"address": "12 rue de la Paix, 75001 Paris, Île-de-France, FR",
"city": "Paris",
"country": "FR",
"shippingMethod": "express",
"paymentMethod": "CARD",
"cardHolder": "Alice Durand",
"cardLast4": "0366"
}
}
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Étapes précédentes manquantes (adresse ou livraison) |
| 401 | Non connecté |
| 422 | Carte invalide (Luhn, expiration, format) ou CVV invalide |
Le corps des 422 suit la structure {"error": "...", "field": "..."}, par exemple {"error": "Numéro de carte invalide (échec vérification Luhn)", "field": "cardNumber"}.
POST /api/checkout/promo¶
Valide un code promotionnel et retourne le pourcentage de réduction applicable.
Auth : session + securityToken
Requête¶
Réponse — 200 OK (code valide)¶
{
"valid": true,
"code": "PROMO10",
"discountPercent": 10,
"message": "Code promo valide: PROMO10 -> 10% de reduction"
}
Réponse — 200 OK (code invalide, comportement nominal)¶
{
"valid": false,
"code": "INEXISTANT",
"discountPercent": 0,
"message": "Code promo invalide: INEXISTANT"
}
A6 — Code invalide silencieusement accepté
En Chaos Métier niveau 2 et au-delà, un code promo invalide est accepté avec valid: true et discountPercent: 0. L'utilisateur voit « Code promo appliqué » mais n'obtient aucune réduction. Aucun message d'erreur n'est retourné — l'anomalie est uniquement visible en comparant le total avant/après. Voir A6.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Code manquant (checkout.error.promo_required) |
| 401 | Non connecté |
GET /api/checkout/session¶
Retourne l'état courant de la session checkout — utile pour réhydrater un panier abandonné en milieu de parcours.
Auth : session + securityToken
Réponse — 200 OK¶
{
"address": "12 rue de la Paix, 75001 Paris, Île-de-France, FR",
"city": "Paris",
"country": "FR",
"shippingMethod": "express",
"paymentMethod": "CARD",
"cardHolder": "Alice Durand",
"cardLast4": "0366"
}
Les champs non encore remplis valent null.
Contrôleur OrderController¶
GET /api/orders/checkout/verify¶
Vérifie que l'utilisateur courant peut accéder au parcours checkout. Utilisé par le frontend pour décider s'il faut rediriger vers le login.
Auth : session (optionnelle — retourne le statut dans tous les cas)
Réponse — 200 OK¶
| Champ | Description |
|---|---|
canCheckout |
true si authenticated et hasToken |
authenticated |
Session utilisateur active |
hasToken |
securityToken checkout encore valide |
POST /api/orders¶
Étape 5 — validation finale de la commande. Crée la commande en base, décrémente les stocks, envoie la confirmation, et (si applicable) injecte le code agent pédagogique.
Auth : session utilisateur + securityToken valide dans le body
Contrôleur : OrderController.createOrder()
Service : OrderService.createOrderFromItems()
Requête¶
{
"securityToken": "a7f1c2e8-4b9d-4f11-9c8e-1a2b3c4d5e6f",
"items": [
{ "productId": 42, "quantity": 2 }
],
"shippingAddress": "12 rue de la Paix, 75001 Paris",
"shippingMethod": "express",
"paymentMethod": "CARD"
}
| Champ | Type | Requis | Note |
|---|---|---|---|
securityToken |
string (UUID) | oui | Généré par POST /api/auth/login |
items[] |
array | oui | Au moins un article |
items[].productId |
Long | oui | |
items[].quantity |
Integer | oui | ≥ 1 |
items[].unitPrice |
Decimal | non | Ignoré en comportement nominal — voir S5 |
shippingAddress |
string | non | Relu depuis la session si absent |
shippingMethod |
string | non | Relu depuis la session si absent |
paymentMethod |
string | non | CARD par défaut |
Si les champs d'adresse/livraison/paiement sont absents du body, le contrôleur les relit depuis la session (définis aux étapes précédentes). Cela permet un flux strict étape-par-étape OU un flux « one-shot » où le frontend envoie tout d'un coup.
Headers de requête¶
| Header | Usage |
|---|---|
X-Student-Token |
Optionnel — si présent, le code agent pédagogique associé est injecté dans la réponse |
| Headers Chaos Scripting | Selon le niveau actif |
Réponse — 201 Created¶
Avec un X-Student-Token valide associé à une session pédagogique, la réponse inclut aussi agentCode :
{
"success": true,
"orderNumber": "PS-2026-000123",
"orderId": 789,
"totalAmount": 299.98,
"agentCode": "NEPTUNE-1284"
}
Le agentCode est calculé par PedagogiqueSessionService.getAgentCodeForToken() selon le niveau BAC actif. Voir Code agent dynamique.
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Panier vide (clé order.error.cart_empty) |
| 401 | Non authentifié — retourne aussi redirectTo: /login |
| 403 | Token invalide — retourne aussi redirectTo: /login |
| 500 | Erreur serveur ou chaos fonctionnel actif |
S4 — XSS stocké via shippingAddress
En Chaos Sécurité niveau 2 et au-delà, le champ shippingAddress est stocké sans échappement HTML. Un payload <script>alert(1)</script> est persisté tel quel en base et ré-exécuté dans l'interface admin qui affiche les détails de commande. Voir S4.
S5 — Falsification du prix client
En Chaos Sécurité niveau 2 et au-delà, le champ unitPrice du body est accepté : si l'utilisateur envoie {"productId": 42, "quantity": 1, "unitPrice": 0.01}, la commande est enregistrée à 1 centime. L'écart entre le prix client et le prix réel en base est loggé mais n'empêche pas la commande. Voir S5.
GET /api/orders¶
Liste les commandes de l'utilisateur connecté, triées de la plus récente à la plus ancienne.
Auth : session
Contrôleur : OrderController.getUserOrders()
Réponse — 200 OK¶
{
"orders": [
{
"id": 789,
"orderNumber": "PS-2026-000123",
"totalAmount": 299.98,
"status": "CONFIRMED",
"createdAt": "2026-04-08T10:30:00Z",
"itemCount": 1
}
],
"historyTotal": 1247.50
}
| Champ | Description |
|---|---|
orders[] |
Liste des commandes |
historyTotal |
Somme cumulée de toutes les commandes |
Chaos actifs¶
Deadlock DB possible
L'endpoint invoque orderService.applyDeadlockChaosPublic() avant la lecture — en Chaos Performance actif, un deadlock peut faire remonter un 500 Internal Server Error avec le message Deadlock base de données détecté — transaction annulée.
A15 — Corruption silencieuse de l'historique
En Chaos Métier niveau 4, les totalAmount sont multipliés par 1.1 à l'affichage sans toucher aux valeurs en base. Le champ historyTotal suit la même corruption. Une commande à 100 € apparaît à 110 € dans la liste mais GET /api/orders/{id} retourne le bon montant si chargé depuis la DB. Voir A15.
A10 — Total historique faux
En Chaos Métier niveau 3, historyTotal est calculé avec un arrondi cumulé par ligne — le résultat diffère de quelques centimes de la somme réelle.
GET /api/orders/{id}¶
Retourne les infos d'une commande.
Auth : session — l'utilisateur ne peut voir que ses propres commandes
Réponse — 200 OK¶
L'entité Order sérialisée avec les relations (user, items).
Codes d'erreur¶
| Code | Cause |
|---|---|
| 401 | Non authentifié |
| 403 | Commande appartenant à un autre utilisateur (clé order.error.access_denied) |
| 404 | Commande introuvable |
S2 — IDOR Commandes
En Chaos Sécurité niveau 1 et au-delà, la vérification d'appartenance est désactivée. Un utilisateur A peut lire les commandes de l'utilisateur B en devinant ou énumérant les IDs. L'accès cross-user est loggé (recordIdorAccess) mais n'est pas bloqué — la commande est retournée normalement. Voir S2.
GET /api/orders/{id}/details¶
Version enrichie de GET /api/orders/{id} — retourne les lignes de commande avec les noms des produits, quantités, prix et sous-totaux, dans une structure aplatie adaptée à l'affichage.
Auth : session (même règle IDOR que l'endpoint ci-dessus)
Réponse — 200 OK¶
{
"id": 789,
"orderNumber": "PS-2026-000123",
"totalAmount": 299.98,
"status": "CONFIRMED",
"createdAt": "2026-04-08T10:30:00Z",
"shippingAddress": "12 rue de la Paix, 75001 Paris",
"shippingMethod": "express",
"paymentMethod": "CARD",
"items": [
{
"productName": "Casque audio Bluetooth",
"quantity": 2,
"unitPrice": 149.99,
"subtotal": 299.98
}
]
}
Codes d'erreur¶
Identiques à GET /api/orders/{id}. S2 (IDOR) s'applique également.
GET /api/orders/{id}/invoice¶
Endpoint de génération de facture simulée. Utilisé en pédagogie pour illustrer S8 (Path Traversal).
Auth : session
Paramètres¶
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
format (query) |
string | pdf |
Format de la facture : pdf ou csv |
Comportement nominal (niveau Sécurité 0-2)¶
Valide strictement format ∈ {"pdf", "csv"}. Retourne un contenu simulé :
{
"orderId": 789,
"format": "pdf",
"content": "FACTURE #789\nPerfShop SAS — 123 rue du Commerce — 75001 Paris\nMerci pour votre commande."
}
Codes d'erreur (nominal)¶
| Code | Cause |
|---|---|
| 400 | Format invalide — seuls pdf et csv sont autorisés (clé order.error.format_invalid) |
| 401 | Non authentifié |
S8 — Path Traversal
En Chaos Sécurité niveau 3 et au-delà, le paramètre format n'est plus validé. Un étudiant peut passer format=../../../etc/passwd et la simulation retourne un contenu marqué « PATH TRAVERSAL SIMULE » :
{
"orderId": 789,
"format": "/etc/passwd",
"content": "root:x:0:0:root:/root:/bin/bash\n...",
"warning": "PATH TRAVERSAL SIMULE — contenu non reel (pedagogique)"
}
Aucun fichier réel du système hôte n'est lu — le contenu est une simulation statique, mais le chemin de code démontre la vulnérabilité. Voir S8.
POST /api/orders/{id}/cancel¶
Annule une commande. Autorisée uniquement sur les statuts PENDING ou CONFIRMED.
Auth : session — vérification stricte d'appartenance
Réponse — 200 OK¶
{
"success": true,
"message": "Commande annulée avec succès",
"orderNumber": "PS-2026-000123",
"status": "CANCELLED"
}
Effets de bord — restocking¶
En fonctionnement nominal, l'annulation recrédit le stock de chaque article vers le produit parent (UPDATE products.stock += items[].quantity).
Codes d'erreur¶
| Code | Cause |
|---|---|
| 400 | Statut non annulable — {"error": "Impossible d'annuler une commande en statut SHIPPED", "status": "SHIPPED"} |
| 401 | Non authentifié |
| 403 | Commande d'un autre utilisateur |
| 404 | Commande introuvable |
A16 — Annulation sans restocking
En Chaos Métier niveau 4, le restocking est supprimé : la commande passe bien en CANCELLED mais le stock n'est pas remis à niveau. Les articles sont « perdus » silencieusement. Aucune erreur retournée. Voir A16.
Exemple curl — parcours complet¶
# 1. Login
TOKEN=$(curl -s -c /tmp/c.txt \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"alice123"}' \
http://localhost:9080/api/auth/login | jq -r '.securityToken')
# 2. Ajouter au panier
curl -b /tmp/c.txt -H "Content-Type: application/json" \
-d '{"productId":42,"quantity":2}' \
http://localhost:9080/api/cart/add
# 3. Adresse
curl -b /tmp/c.txt -H "Content-Type: application/json" \
-d '{"street":"12 rue de la Paix","postalCode":"75001","city":"Paris","country":"FR","phone":"0145678901"}' \
http://localhost:9080/api/checkout/address
# 4. Livraison
curl -b /tmp/c.txt -H "Content-Type: application/json" \
-d '{"shippingMethod":"express"}' \
http://localhost:9080/api/checkout/shipping
# 5. Paiement (CVV validé puis jeté)
curl -b /tmp/c.txt -H "Content-Type: application/json" \
-d '{"cardHolder":"Alice","cardNumber":"4532015112830366","expiryMonth":"12","expiryYear":"2027","cvv":"123"}' \
http://localhost:9080/api/checkout/payment
# 6. Validation finale
curl -b /tmp/c.txt -H "Content-Type: application/json" \
-d "{\"securityToken\":\"$TOKEN\",\"items\":[{\"productId\":42,\"quantity\":2}]}" \
http://localhost:9080/api/orders
Liens associés¶
auth.md— login et génération dusecurityTokencart.md— construction du panier en amont- Chaos Sécurité — S2, S4, S5, S8
- Chaos Métier — A6, A7, A10, A15, A16
- Chaos Scripting — protection headers du checkout
- Code agent pédagogique — injection dans la réponse de commande