Let customers cancel their own orders

Java · Spring Boot · beginner · modification

Adds self-service cancellation for orders that have not been confirmed yet. `OrderCancellationService.cancel` loads the order, checks that it belongs to the signed-in customer (404 otherwise, the same answer `GET` gives, so order ids are not confirmed), refuses anything but `PENDING` with a 409, and flips the status inside a transaction. The existing `@Version` column turns a race with the warehouse confirming the order into a 409. Unit tests cover the three outcomes and a missing order.

Shop backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL, Spring Security. The existing security configuration requires a signed-in session for every `/api/**` request, checks CSRF tokens (the SPA sends them), and at login stores a `ShopUser(Long customerId, String email)` record as the principal, which `@AuthenticationPrincipal` resolves; `customerId` is the customer's `customers.id`, a `bigint` identity starting at 1, never null. The shop has about 40,000 customers. Existing and unchanged: `OrderRepository extends JpaRepository<Order, Long>`; `OrderStatus` is `PENDING`, `CONFIRMED`, `SHIPPED`, `DELIVERED` or `CANCELLED`; the record `OrderView.from(order)` copies the order's id, status and total. Every writer of `orders` goes through the `Order` entity, so its `@Version` column catches concurrent changes, and an existing `@RestControllerAdvice` answers `ObjectOptimisticLockingFailureException` with 409.

Requirements

Files touched

--- src/main/java/com/acme/shop/orders/Order.java
         return totalCents;
     }
+
+    public void cancel() {
+        this.status = OrderStatus.CANCELLED;
+    }
 }
 
--- src/main/java/com/acme/shop/orders/OrderController.java
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
     private final OrderRepository orders;
+    private final OrderCancellationService cancellations;
 
-    public OrderController(OrderRepository orders) {
+    public OrderController(OrderRepository orders, OrderCancellationService cancellations) {
         this.orders = orders;
+        this.cancellations = cancellations;
     }
 
                 .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
     }
+
+    @PostMapping("/{id}/cancel")
+    public OrderView cancel(@PathVariable long id, @AuthenticationPrincipal ShopUser user) {

Review this PR

Java practice