Offer same-day delivery at checkout
Java · Spring Boot · intermediate · modification
Adds same-day delivery as a fourth checkout option. `DeliveryMethod` gains `SAME_DAY`; `DeliveryProperties` gains `sameDayFee`, bound from `shop.delivery.same-day-fee=9.90` and validated like the other fees, so the app does not start without it; the options endpoint gets a label for the new method. `DeliveryFees.feeFor` handles the new method; the options endpoint and order placement already go through it, so they pick it up without further changes.
Shop backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL, Spring Security. Every `/api/**` request needs a signed-in session; the principal is a `ShopUser(Long customerId, String email)` record resolved by `@AuthenticationPrincipal`, and `customerId` is never null. `@ConfigurationPropertiesScan` on the application class registers `DeliveryProperties`. `CartService.subtotal(customerId)` (existing, unchanged) returns the cart subtotal in EUR as a non-negative `BigDecimal` with scale 2 (0.00 for an empty cart). `DeliveryFees.feeFor` and the options controller's `label` are the only code that branches on `DeliveryMethod`. `Order` stores its method with `@Enumerated(EnumType.STRING)` in a `varchar(20)` column with no check constraint, so the new constant needs no migration, and the place-order request already accepts any `DeliveryMethod` constant. Handing same-day orders to the courier happens outside this service (the warehouse reads the method name from the order export). All fees are EUR.
Requirements
- Customers can choose same-day delivery at checkout. `GET /api/checkout/delivery-options` lists every delivery method with a label and the fee for the signed-in customer's current cart subtotal; the new `SAME_DAY` method appears there with the label "Same-day delivery".
- Same-day delivery costs the flat `shop.delivery.same-day-fee` (9.90 EUR) whatever the subtotal. The free-delivery threshold (`shop.delivery.free-standard-from`, 50.00) applies to `STANDARD` only. The other fees do not change: `STANDARD` 4.90 below 50.00 and free from 50.00, `EXPRESS` 7.90, `PICKUP` free.
- The fee the options endpoint shows is the fee the order is charged: `OrderService.place` (existing, unchanged) charges `DeliveryFees.feeFor(method, subtotal)` for the method the customer picked.
Files touched
- src/main/java/com/acme/shop/delivery/DeliveryMethod.java
- src/main/java/com/acme/shop/delivery/DeliveryProperties.java
- src/main/resources/application.properties
- src/main/java/com/acme/shop/delivery/DeliveryFees.java
- src/main/java/com/acme/shop/delivery/DeliveryOptionsController.java
--- src/main/java/com/acme/shop/delivery/DeliveryMethod.java
STANDARD,
EXPRESS,
- PICKUP
+ PICKUP,
+ SAME_DAY
}
--- src/main/java/com/acme/shop/delivery/DeliveryProperties.java
@NotNull @PositiveOrZero BigDecimal standardFee,
@NotNull @PositiveOrZero BigDecimal freeStandardFrom,
- @NotNull @PositiveOrZero BigDecimal expressFee) {
+ @NotNull @PositiveOrZero BigDecimal expressFee,
+ @NotNull @PositiveOrZero BigDecimal sameDayFee) {
}
--- src/main/resources/application.properties
shop.delivery.free-standard-from=50.00
shop.delivery.express-fee=7.90
+shop.delivery.same-day-fee=9.90
--- src/main/java/com/acme/shop/delivery/DeliveryFees.java
case PICKUP -> BigDecimal.ZERO;
case EXPRESS -> props.expressFee();
- case STANDARD -> standardFee(subtotal);
+ default -> standardFee(subtotal);
};
}
--- src/main/java/com/acme/shop/delivery/DeliveryOptionsController.java
case EXPRESS -> "Express delivery (next working day)";