API — Cart¶
This page documents CartController, mounted under /api/cart. The cart is free — accessible whether logged in or not (guest mode).
Controller covered
CartController → GET /, POST /add, PUT /item/{id}, DELETE /item/{id}, DELETE /
The cart is deliberately free of access: protection chaos (Scripting chaos) applies only to the checkout journey, never to adding to the cart. This separation reflects the behavior of real e-commerce sites where adding to the cart is a "free" operation that does not warrant an anti-replay token.
Overview¶
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/cart |
None | Current cart contents (items, total, count) |
POST |
/api/cart/add |
None | Adds a product to the cart (accumulates if already present) |
PUT |
/api/cart/item/{id} |
None | Updates the quantity of an item |
DELETE |
/api/cart/item/{id} |
None | Removes an item from the cart |
DELETE |
/api/cart |
None | Empties the cart entirely |
Guest vs. logged-in sessions¶
The cart is bound to the HttpSession, not to the user. Consequences:
- A non-logged-in user (guest) can build a cart — it is associated with their
JSESSIONID. - On login, the
CartServiceautomatically merges the guest cart with the user's. - On logout, the user's cart remains attached to the session until the cookie expires (30 min by default) or until full disconnection.
flowchart LR
G[Anonymous visitor] -->|POST /cart/add| S1[Session JSESSIONID#1<br/>guest cart]
G -->|POST /auth/login| S2[Session with<br/>LOGGED_IN_USER]
S1 -.merge.-> S2
S2 -->|POST /cart/add| S2
S2 -->|POST /auth/logout| S3[Session emptied<br/>cart lost]
GET /api/cart¶
Returns the full contents of the cart associated with the current session. Returns an empty cart (items: []) for an anonymous visitor with no activity.
Auth: none
Controller: CartController.getCart()
Service: CartService.getCartItems(session) + CartService.getCartTotal(session)
Response — 200 OK¶
{
"items": [
{
"id": 1234,
"productId": 42,
"quantity": 2,
"price": 149.99,
"addedAt": "2026-04-08T10:30:00Z",
"productName": "Bluetooth audio headset",
"productImage": "/images/products/42.jpg",
"productPrice": 149.99
}
],
"total": 299.98,
"itemCount": 1
}
| Field | Description |
|---|---|
items[] |
List of cart lines |
items[].id |
CartItem identifier (DB) |
items[].productId |
Product identifier |
items[].quantity |
Requested quantity |
items[].price |
Price frozen at the time of addition (useful if the product price changes) |
items[].addedAt |
ISO timestamp of the addition |
items[].productName, productImage, productPrice |
Product snapshot (for display) |
total |
Sum Σ (price × quantity) |
itemCount |
Number of distinct lines (not the total quantity) |
This endpoint never returns an error under normal operation.
POST /api/cart/add¶
Adds a product to the cart. If the product is already present, the quantity is incremented (not replaced).
Auth: none
Controller: CartController.addToCart()
DTO: AddToCartRequest
Service: CartService.addToCart()
Request¶
| Field | Type | Required | Constraint |
|---|---|---|---|
productId |
Long | yes | @NotNull — existing product identifier |
quantity |
Integer | yes | @NotNull, @Min(1) — at least 1 |
Response — 200 OK¶
{
"success": true,
"message": "Item added to cart",
"cartItem": {
"id": 1234,
"productId": 42,
"quantity": 2,
"price": 149.99,
"addedAt": "2026-04-08T10:30:00Z"
}
}
The localized message comes from the cart.item.added key.
Error codes¶
| Code | Body | Cause |
|---|---|---|
| 400 | {"productId": "Product ID is required"} |
Missing field (Jakarta validation) |
| 400 | {"quantity": "Quantity must be at least 1"} |
Quantity < 1 |
| 404 | — | Product not found (propagated from CartItemNotFoundException) |
PUT /api/cart/item/{id}¶
Updates the quantity of an existing item in the cart.
Auth: none (but session ownership is verified)
Controller: CartController.updateCartItem()
Service: CartService.updateCartItem() + verifyCartItemOwnership()
Parameters¶
| Parameter | Location | Type | Description |
|---|---|---|---|
id |
path | Long | CartItem identifier |
quantity |
query | Integer | New quantity (≥ 1) |
Request¶
Response — 200 OK¶
{
"success": true,
"message": "Cart item updated",
"cartItem": {
"id": 1234,
"productId": 42,
"quantity": 5,
"price": 149.99,
"addedAt": "2026-04-08T10:30:00Z"
}
}
Error codes¶
| Code | Body | Cause | Java exception |
|---|---|---|---|
| 400 | {"error": "Quantity must be at least 1"} |
quantity == null or < 1 |
— |
| 403 | {"error": "Unauthorized access to cart item"} |
The item does not belong to the current session | CartAccessDeniedException |
| 404 | {"error": "Cart item not found: 1234"} |
Unknown ID | CartItemNotFoundException |
Business exceptions are intercepted by type in the controller, and converted to the corresponding HTTP codes. See the files com.perfshop.exception.CartItemNotFoundException and CartAccessDeniedException.
DELETE /api/cart/item/{id}¶
Removes an item from the cart.
Auth: none (ownership verification)
Controller: CartController.removeFromCart()
Service: CartService.removeFromCart()
Request¶
Response — 200 OK¶
Error codes¶
Same as PUT /api/cart/item/{id} (except 400 quantity):
| Code | Cause |
|---|---|
| 403 | Item belonging to another session |
| 404 | Item not found |
DELETE /api/cart¶
Empties the cart associated with the current session entirely.
Auth: none
Controller: CartController.clearCart()
Service: CartService.clearCart()
Response — 200 OK¶
This endpoint never returns an error — even on an empty cart it responds 200 OK. The message comes from the cart.cleared key.
curl example¶
# Fetch the current cart (creates a session if no cookie)
curl -c /tmp/cookies.txt http://localhost:9080/api/cart
# Add 2 units of product 42
curl -b /tmp/cookies.txt \
-H "Content-Type: application/json" \
-d '{"productId":42,"quantity":2}' \
http://localhost:9080/api/cart/add
# Update the quantity of item 1234
curl -b /tmp/cookies.txt -X PUT \
"http://localhost:9080/api/cart/item/1234?quantity=5"
# Remove item 1234
curl -b /tmp/cookies.txt -X DELETE \
http://localhost:9080/api/cart/item/1234
# Empty the cart
curl -b /tmp/cookies.txt -X DELETE http://localhost:9080/api/cart
Points of attention¶
The cart escapes chaos
Unlike /api/orders (checkout), no chaos applies to the cart:
- No Scripting chaos protection
- No falsified price injection (S5 is only active on
POST /api/orders) - No SQL injection (S1 is only active on
/api/products/search)
This is a deliberate choice: the cart is a "comfort zone" that lets the student build an order without fighting anti-replay tokens, then see chaos kick in at the critical checkout moment.
No cross-session persistence
The cart lives in the backend HTTP session — it does not survive a backend restart or cookie expiration. There is no persistent "abandoned cart" mechanism in the database for logged-in users.
Related links¶
- Checkout journey — next step after adding to the cart
- Business exceptions —
CartItemNotFoundException,CartAccessDeniedException