package se.bilhalsning.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import se.bilhalsning.entity.Order; import se.bilhalsning.entity.OrderStatus; import se.bilhalsning.exception.InvalidOrderStateException; import se.bilhalsning.exception.OrderNotFoundException; import se.bilhalsning.repository.OrderRepository; import java.util.List; import java.util.UUID; @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final OrderNotificationService orderNotificationService; public Order createOrder(UUID userId, String plate, String letterText) { Order order = new Order(); order.setUserId(userId); order.setPlate(plate.toUpperCase().trim()); order.setLetterText(letterText); order.setStatus(OrderStatus.PENDING_PAYMENT); return orderRepository.save(order); } /** * Guest checkout — creates an order with no registered user. The * {@code guestToken} is generated in {@link Order#onCreate()} so the * caller does not need to handle token creation logic. Returned * order's {@link Order#getGuestToken()} is the only credential the * client receives. */ public Order createGuestOrder(String plate, String letterText, String email) { Order order = new Order(); // userId stays null — guest order. guestToken auto-generated in PrePersist. order.setGuestEmail(email == null ? null : email.trim().toLowerCase()); order.setPlate(plate.toUpperCase().trim()); order.setLetterText(letterText); order.setStatus(OrderStatus.PENDING_PAYMENT); return orderRepository.save(order); } /** * Guest-path order lookup by opaque token. Never exposes user-owned * orders via this path: if a guest token resolves to an order with a * non-null {@code userId} (corrupted data, manual SQL insert, etc.), * treat as not-found rather than leak the order. */ public Order getOrderByGuestToken(UUID guestToken) { Order order = orderRepository.findByGuestToken(guestToken) .orElseThrow(() -> new OrderNotFoundException(guestToken)); if (order.getUserId() != null) { // Token points at a user-owned order — refuse to serve it. throw new OrderNotFoundException(guestToken); } return order; } /** * Honor-system payment confirmation for guest orders. Mirrors * {@link #confirmPayment(UUID, UUID)} but authenticates via the * guest token instead of {@code userId}. */ public Order confirmGuestPayment(UUID guestToken) { Order order = getOrderByGuestToken(guestToken); if (order.getStatus() != OrderStatus.PENDING_PAYMENT) { throw new InvalidOrderStateException( "Beställningen kan inte ändras i detta tillstånd"); } order.setStatus(OrderStatus.PROCESSING); Order saved = orderRepository.save(order); orderNotificationService.notifyOrderProcessing(saved); return saved; } public List getOrdersByUserId(UUID userId) { return orderRepository.findByUserIdOrderByCreatedAtDesc(userId); } public Order getOrderById(UUID id) { return orderRepository.findById(id) .orElseThrow(() -> new OrderNotFoundException(id)); } public List getAllOrders() { return orderRepository.findAllByOrderByCreatedAtDesc(); } public Order confirmPayment(UUID orderId, UUID userId) { Order order = requirePendingOwnedBy(orderId, userId); order.setStatus(OrderStatus.PROCESSING); Order saved = orderRepository.save(order); orderNotificationService.notifyOrderProcessing(saved); return saved; } public Order cancelOrder(UUID orderId, UUID userId) { Order order = requirePendingOwnedBy(orderId, userId); order.setStatus(OrderStatus.CANCELLED); return orderRepository.save(order); } public Order updatePendingOrder(UUID orderId, UUID userId, String letterText) { Order order = requirePendingOwnedBy(orderId, userId); order.setLetterText(letterText); return orderRepository.save(order); } private Order requirePendingOwnedBy(UUID orderId, UUID userId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException(orderId)); if (!order.getUserId().equals(userId)) { throw new OrderNotFoundException(orderId); } if (order.getStatus() != OrderStatus.PENDING_PAYMENT) { throw new InvalidOrderStateException( "Beställningen kan inte ändras i detta tillstånd"); } return order; } }