diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/controller/BookingController.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/controller/BookingController.java index 2fd207af..c1dbd056 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/controller/BookingController.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/controller/BookingController.java @@ -4,6 +4,7 @@ import com.eatsfine.eatsfine.domain.booking.dto.response.BookingResponseDTO; import com.eatsfine.eatsfine.domain.booking.service.BookingCommandService; import com.eatsfine.eatsfine.domain.booking.service.BookingQueryService; +import com.eatsfine.eatsfine.domain.booking.status.BookingSuccessStatus; import com.eatsfine.eatsfine.domain.user.entity.User; import com.eatsfine.eatsfine.domain.user.repository.UserRepository; import com.eatsfine.eatsfine.global.apiPayload.ApiResponse; @@ -60,15 +61,42 @@ public ApiResponse createBooking( return ApiResponse.onSuccess(bookingCommandService.createBooking(user, storeId, dto)); } - @Operation(summary = "결제 완료 처리", - description = "결제 완료 후 결제 정보를 입력받아 예약 상태를 업데이트합니다.") - @PatchMapping("/bookings/{bookingId}/payments-confirm") - public ApiResponse confirmPayment( - @PathVariable Long bookingId, - @RequestBody @Valid BookingRequestDTO.PaymentConfirmDTO dto - ) { + //불필요한 api 삭제 +// @Operation(summary = "예약 완료 처리", +// description = "결제 완료 후 결제 정보를 입력받아 예약 상태를 업데이트합니다. 주의) 외부에서 이 API를 호출하지 않고 " + +// "POST /api/v1/payments/confirm API 호출 후 내부적으로 이 API의 로직을 실행합니다.") +// @PatchMapping("/bookings/{bookingId}/payments-confirm") +// public ApiResponse confirmPayment( +// @PathVariable Long bookingId, +// @RequestBody @Valid BookingRequestDTO.PaymentConfirmDTO dto +// ) { +// +// return ApiResponse.onSuccess(bookingCommandService.confirmPayment(bookingId,dto)); +// } - return ApiResponse.onSuccess(bookingCommandService.confirmPayment(bookingId,dto)); + @Operation(summary = "예약 취소", + description = "예약을 취소하고 환불을 진행합니다.") + @PatchMapping("/bookings/{bookingId}/cancel") + public ApiResponse cancelBooking( + @PathVariable Long bookingId, + @RequestBody @Valid BookingRequestDTO.CancelBookingDTO dto + ) { + return ApiResponse.of(BookingSuccessStatus._BOOKING_CANCELED, + bookingCommandService.cancelBooking(bookingId, dto)); } + + @Operation(summary = "예약 내역 조회", + description = "마이페이지에서 나의 예약 내역을 조회합니다.") + @GetMapping("/users/bookings") + public ApiResponse getMyBookings( + @RequestParam(name = "status", required = false) String status, + @RequestParam(name = "page", defaultValue = "1") Integer page + ) { + User user = userRepository.findById(1L).orElseThrow(); // 임시로 임의의 유저 사용 + + // 서비스 호출 시 page - 1을 넘겨서 0-based index로 맞춰줍니다. + return ApiResponse.of(BookingSuccessStatus._BOOKING_FOUND, + bookingQueryService.getBookingList(user, status, page-1)); + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/converter/BookingConverter.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/converter/BookingConverter.java index cfd9e42b..a3107088 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/converter/BookingConverter.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/converter/BookingConverter.java @@ -1,4 +1,32 @@ package com.eatsfine.eatsfine.domain.booking.converter; +import com.eatsfine.eatsfine.domain.booking.dto.response.BookingResponseDTO; +import com.eatsfine.eatsfine.domain.booking.entity.Booking; +import com.eatsfine.eatsfine.domain.payment.dto.response.PaymentResponseDTO; +import com.eatsfine.eatsfine.domain.store.entity.Store; + +import java.math.BigDecimal; +import java.util.List; + public class BookingConverter { + + public static BookingResponseDTO.CreateBookingResultDTO toCreateBookingResultDTO( + Booking booking, Store store, BigDecimal totalDeposit, + List resultTableDTOS, + PaymentResponseDTO.PaymentRequestResultDTO paymentInfo) { + + return BookingResponseDTO.CreateBookingResultDTO.builder() + .bookingId(booking.getId()) + .storeName(store.getStoreName()) + .date(booking.getBookingDate()) + .time(booking.getBookingTime()) + .partySize(booking.getPartySize()) + .status(booking.getStatus().name()) + .totalDeposit(totalDeposit) + .createdAt(booking.getCreatedAt()) + .tables(resultTableDTOS) + .paymentId(paymentInfo.paymentId()) + .orderId(paymentInfo.orderId()) + .build(); + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java index 6616d469..42288939 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java @@ -35,7 +35,13 @@ public record CreateBookingDTO( @NotNull @DateTimeFormat(pattern = "HH:mm") LocalTime time, @NotNull @Min(1) Integer partySize, @NotNull List tableIds, - @NotNull boolean isSplitAccepted + @NotNull boolean isSplitAccepted, + @NotNull List menuItems + ){} + + public record MenuOrderDto( + @NotNull Long menuId, + @NotNull @Min(1) Integer quantity ){} public record PaymentConfirmDTO( @@ -43,4 +49,9 @@ public record PaymentConfirmDTO( @NotNull Integer amount //실제 결제 금액 ){} + public record CancelBookingDTO( + @NotBlank String reason //예약 취소 사유 + + ){} + } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/response/BookingResponseDTO.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/response/BookingResponseDTO.java index ad1bf084..a3333706 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/response/BookingResponseDTO.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/response/BookingResponseDTO.java @@ -1,7 +1,9 @@ package com.eatsfine.eatsfine.domain.booking.dto.response; +import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus; import lombok.Builder; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -42,9 +44,11 @@ public record CreateBookingResultDTO( LocalDate date, LocalTime time, Integer partySize, - Integer totalDeposit, + BigDecimal totalDeposit, List tables, - LocalDateTime createdAt // 예약 생성 시간 + LocalDateTime createdAt, // 예약 생성 시간 + Long paymentId, // 결제 ID + String orderId // 주문 ID ){} @Builder @@ -61,6 +65,40 @@ public record ConfirmPaymentResultDTO( Long bookingId, String status, // CONFIRMED String paymentKey, // PG사 결제 키 - Integer amount // 최종 결제 금액 + BigDecimal amount // 최종 결제 금액 + ){} + + @Builder + public record CancelBookingResultDTO( + Long bookingId, + String status, // CANCELED + String cancelReason, // 취소 사유 + LocalDateTime canceledAt, // 취소 시간 + BigDecimal refundAmount // 환불 금액 + ){} + + @Builder + public record BookingPreviewListDTO( + List bookingList, + Integer listSize, + Integer totalPage, + Long totalElements, + Boolean isFirst, + Boolean isLast + + ){} + + @Builder + public record BookingPreviewDTO( + Long bookingId, + String storeName, + String storeAddress, + LocalDate bookingDate, + LocalTime bookingTime, + Integer partySize, + String tableNumbers, + BigDecimal amount, + String paymentMethod, + String status ){} } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/Booking.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/Booking.java index 386d7644..42f94925 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/Booking.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/Booking.java @@ -1,8 +1,12 @@ package com.eatsfine.eatsfine.domain.booking.entity; +import com.eatsfine.eatsfine.domain.booking.entity.mapping.BookingMenu; import com.eatsfine.eatsfine.domain.booking.entity.mapping.BookingTable; import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus; import com.eatsfine.eatsfine.domain.payment.entity.Payment; +import com.eatsfine.eatsfine.domain.payment.enums.PaymentStatus; +import com.eatsfine.eatsfine.domain.payment.exception.PaymentException; +import com.eatsfine.eatsfine.domain.payment.status.PaymentErrorStatus; import com.eatsfine.eatsfine.domain.store.entity.Store; import com.eatsfine.eatsfine.domain.storetable.entity.StoreTable; import com.eatsfine.eatsfine.domain.user.entity.User; @@ -10,6 +14,7 @@ import jakarta.persistence.*; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalTime; import java.util.ArrayList; @@ -60,8 +65,20 @@ public class Booking extends BaseEntity { private LocalTime bookingTime; @Enumerated(EnumType.STRING) + @Column(name = "status", length = 20, nullable = false) private BookingStatus status; + @Builder.Default + @OneToMany(mappedBy = "booking", cascade = CascadeType.ALL, orphanRemoval = true) + private List bookingMenus = new ArrayList<>(); + + public void addBookingMenu(BookingMenu bookingMenu) { + this.bookingMenus.add(bookingMenu); + if (bookingMenu.getBooking() != this) { + bookingMenu.confirmBooking(this); + } + } + public void addBookingTable(StoreTable storeTable) { BookingTable bookingTable = BookingTable.builder() .booking(this) @@ -70,10 +87,30 @@ public void addBookingTable(StoreTable storeTable) { this.bookingTables.add(bookingTable); } - private Integer depositAmount; + private BigDecimal depositAmount; + + private String cancelReason; public void confirm() { this.status = BookingStatus.CONFIRMED; } + public void cancel(String cancelReason) + { + this.status = BookingStatus.CANCELED; + this.cancelReason = cancelReason; + } + + //예약과 관련된 결제 중 결제 완료된 결제키 조회 + public String getSuccessPaymentKey() { + return this.payments.stream() + .filter(p -> p.getPaymentStatus() == PaymentStatus.COMPLETED) + .map(Payment::getPaymentKey) + .findFirst() + .orElseThrow(() -> new PaymentException(PaymentErrorStatus._PAYMENT_NOT_FOUND)); + } + + public void setDepositAmount(BigDecimal totalDeposit) { + this.depositAmount = totalDeposit; + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/mapping/BookingMenu.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/mapping/BookingMenu.java new file mode 100644 index 00000000..aefc0374 --- /dev/null +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/entity/mapping/BookingMenu.java @@ -0,0 +1,34 @@ +package com.eatsfine.eatsfine.domain.booking.entity.mapping; + +import com.eatsfine.eatsfine.domain.booking.entity.Booking; +import com.eatsfine.eatsfine.domain.menu.entity.Menu; +import jakarta.persistence.*; +import lombok.*; + +import java.math.BigDecimal; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Getter +@Builder +public class BookingMenu { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Integer quantity; + + private BigDecimal price; + + @ManyToOne(fetch = FetchType.LAZY) + private Booking booking; + + @ManyToOne(fetch = FetchType.LAZY) + private Menu menu; + + public void confirmBooking(Booking booking) { + this.booking = booking; + } + +} diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/enums/BookingStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/enums/BookingStatus.java index 404fafc2..c0a94af6 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/enums/BookingStatus.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/enums/BookingStatus.java @@ -2,5 +2,5 @@ public enum BookingStatus { - PENDING, CONFIRMED, COMPLETED, CANCELLED, NOSHOW + PENDING, CONFIRMED, COMPLETED, CANCELED, NOSHOW } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java index 5b12c355..efc5a43c 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/repository/BookingRepository.java @@ -1,14 +1,20 @@ package com.eatsfine.eatsfine.domain.booking.repository; import com.eatsfine.eatsfine.domain.booking.entity.Booking; +import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus; +import com.eatsfine.eatsfine.domain.user.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; +@Repository public interface BookingRepository extends JpaRepository { @@ -35,6 +41,13 @@ public interface BookingRepository extends JpaRepository { "AND b.status IN ('CONFIRMED', 'PENDING')") boolean existsBookingByTableAndDateTime(@Param("tableId") Long tableId, @Param("date") LocalDate date, @Param("time") LocalTime time); + + // 1. 특정 유저의 모든 예약을 최신순으로 페이징 조회 + @Query("select b from Booking b join fetch b.store where b.user = :user") + Page findAllByUser(@Param("user") User user, Pageable pageable); + + @Query("Select b from Booking b join fetch b.store where b.user = :user and b.status = :status") + Page findAllByUserAndStatus(@Param("user") User user, @Param("status") BookingStatus status, Pageable pageable); @Query("SELECT COUNT(bt) > 0 FROM BookingTable bt " + "JOIN bt.booking b " + "WHERE bt.storeTable.id = :tableId " + diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandService.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandService.java index 5eacc66b..0928674a 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandService.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandService.java @@ -14,4 +14,7 @@ public interface BookingCommandService { BookingResponseDTO.CreateBookingResultDTO createBooking(User user, Long storeId, BookingRequestDTO.CreateBookingDTO dto); BookingResponseDTO.ConfirmPaymentResultDTO confirmPayment(Long BookingId, BookingRequestDTO.PaymentConfirmDTO dto); + + BookingResponseDTO.CancelBookingResultDTO cancelBooking(Long bookingId, BookingRequestDTO.CancelBookingDTO dto); + } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandServiceImpl.java index e41b4371..75e0a02f 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandServiceImpl.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingCommandServiceImpl.java @@ -1,18 +1,30 @@ package com.eatsfine.eatsfine.domain.booking.service; +import com.eatsfine.eatsfine.domain.booking.converter.BookingConverter; import com.eatsfine.eatsfine.domain.booking.dto.request.BookingRequestDTO; import com.eatsfine.eatsfine.domain.booking.dto.response.BookingResponseDTO; import com.eatsfine.eatsfine.domain.booking.entity.Booking; +import com.eatsfine.eatsfine.domain.booking.entity.mapping.BookingMenu; import com.eatsfine.eatsfine.domain.booking.entity.mapping.BookingTable; import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus; import com.eatsfine.eatsfine.domain.booking.exception.BookingException; import com.eatsfine.eatsfine.domain.booking.repository.BookingRepository; import com.eatsfine.eatsfine.domain.booking.status.BookingErrorStatus; +import com.eatsfine.eatsfine.domain.menu.entity.Menu; +import com.eatsfine.eatsfine.domain.menu.repository.MenuRepository; +import com.eatsfine.eatsfine.domain.payment.dto.request.PaymentRequestDTO; +import com.eatsfine.eatsfine.domain.payment.dto.response.PaymentResponseDTO; +import com.eatsfine.eatsfine.domain.payment.entity.Payment; +import com.eatsfine.eatsfine.domain.payment.enums.PaymentStatus; +import com.eatsfine.eatsfine.domain.payment.exception.PaymentException; +import com.eatsfine.eatsfine.domain.payment.service.PaymentService; +import com.eatsfine.eatsfine.domain.payment.status.PaymentErrorStatus; import com.eatsfine.eatsfine.domain.store.entity.Store; import com.eatsfine.eatsfine.domain.store.exception.StoreException; import com.eatsfine.eatsfine.domain.store.repository.StoreRepository; import com.eatsfine.eatsfine.domain.store.status.StoreErrorStatus; import com.eatsfine.eatsfine.domain.storetable.entity.StoreTable; +import com.eatsfine.eatsfine.domain.storetable.exception.status.StoreTableErrorStatus; import com.eatsfine.eatsfine.domain.storetable.repository.StoreTableRepository; import com.eatsfine.eatsfine.domain.user.entity.User; import com.eatsfine.eatsfine.domain.user.repository.UserRepository; @@ -20,9 +32,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; @Service @@ -32,6 +47,8 @@ public class BookingCommandServiceImpl implements BookingCommandService{ private final StoreRepository storeRepository; private final StoreTableRepository storeTableRepository; private final BookingRepository bookingRepository; + private final PaymentService paymentService; + private final MenuRepository menuRepository; @Override @Transactional @@ -42,6 +59,11 @@ public BookingResponseDTO.CreateBookingResultDTO createBooking(User user, Long s List selectedTables = storeTableRepository.findAllByIdWithLock(dto.tableIds()); + // 요청한 ID 개수와 조회된 데이터 개수가 다르면, 존재하지 않는 ID가 포함된 것 + if (selectedTables.size() != dto.tableIds().size()) { + throw new StoreException(StoreTableErrorStatus._TABLE_NOT_FOUND); + } + //이미 예약된 테이블 있는지 최종 점검 List reservedTableIds = bookingRepository.findReservedTableIds(storeId, dto.date(), dto.time()); for (StoreTable storeTable : selectedTables) { @@ -50,11 +72,8 @@ public BookingResponseDTO.CreateBookingResultDTO createBooking(User user, Long s } } - int totalDeposit = store.getMinPrice() * dto.partySize(); // 자세한 예약금 로직은 추후 수정 - Booking booking = Booking.builder() - .depositAmount(totalDeposit) .bookingDate(dto.date()) .bookingTime(dto.time()) .partySize(dto.partySize()) @@ -66,7 +85,40 @@ public BookingResponseDTO.CreateBookingResultDTO createBooking(User user, Long s selectedTables.forEach(booking::addBookingTable); + + // 예약한 메뉴들 저장 및 총 메뉴 가격 계산 + BigDecimal itemTotalPrice = BigDecimal.ZERO; + for (BookingRequestDTO.MenuOrderDto menuItem : dto.menuItems()) { + Menu menu = menuRepository.findById(menuItem.menuId()) + .orElseThrow(() -> new StoreException(StoreErrorStatus._STORE_NOT_FOUND));//차후 수정 + + BookingMenu bookingMenu = BookingMenu.builder() + .quantity(menuItem.quantity()) + .menu(menu) + .booking(booking) + .price(menu.getPrice()) + .build(); + + booking.addBookingMenu(bookingMenu); + + BigDecimal itemQuantity = BigDecimal.valueOf(menuItem.quantity()); + itemTotalPrice = itemTotalPrice.add(menu.getPrice().multiply(itemQuantity)); + } + + // 총 예약금 계산 ( 전체 메뉴 가격 * 가게의 예약금 비율 ) + BigDecimal depositRate = BigDecimal.valueOf(store.getDepositRate().getPercent()); + BigDecimal hundred = BigDecimal.valueOf(100); + BigDecimal totalDeposit = itemTotalPrice + .multiply(depositRate) + .divide(hundred, 0, RoundingMode.HALF_UP); + booking.setDepositAmount(totalDeposit); + Booking savedBooking = bookingRepository.save(booking); + bookingRepository.flush(); + + // 결제 대기 데이터 생성 (내부 서비스 호출) + PaymentRequestDTO.RequestPaymentDTO paymentRequest = new PaymentRequestDTO.RequestPaymentDTO(savedBooking.getId()); + PaymentResponseDTO.PaymentRequestResultDTO paymentInfo = paymentService.requestPayment(paymentRequest); //BookingResponseDTO.BookingResultTableDTO로 변환 @@ -80,17 +132,8 @@ public BookingResponseDTO.CreateBookingResultDTO createBooking(User user, Long s .build()) .toList(); - return BookingResponseDTO.CreateBookingResultDTO.builder() - .bookingId(savedBooking.getId()) - .storeName(store.getStoreName()) - .date(savedBooking.getBookingDate()) - .time(savedBooking.getBookingTime()) - .partySize(savedBooking.getPartySize()) - .status(savedBooking.getStatus().name()) - .totalDeposit(totalDeposit) - .createdAt(savedBooking.getCreatedAt()) - .tables(resultTableDTOS) - .build(); + + return BookingConverter.toCreateBookingResultDTO(savedBooking,store,totalDeposit, resultTableDTOS,paymentInfo); } @Override @@ -113,7 +156,6 @@ public BookingResponseDTO.ConfirmPaymentResultDTO confirmPayment(Long bookingId, //예약 상태 확정으로 변경 booking.confirm(); - return BookingResponseDTO.ConfirmPaymentResultDTO.builder() .bookingId(booking.getId()) .status(booking.getStatus().name()) @@ -121,4 +163,27 @@ public BookingResponseDTO.ConfirmPaymentResultDTO confirmPayment(Long bookingId, .amount(booking.getDepositAmount()) .build(); } + + @Override + @Transactional + public BookingResponseDTO.CancelBookingResultDTO cancelBooking(Long bookingId, BookingRequestDTO.CancelBookingDTO dto) { + Booking booking = bookingRepository.findById(bookingId) + .orElseThrow(() -> new BookingException(BookingErrorStatus._BOOKING_NOT_FOUND)); + + // 예약 중 결제 완료된 결제의 결제키 이용 환불 로직 진행 + if(booking.getStatus() == BookingStatus.CONFIRMED) { + PaymentRequestDTO.CancelPaymentDTO cancelDto = new PaymentRequestDTO.CancelPaymentDTO(dto.reason()); + paymentService.cancelPayment(booking.getSuccessPaymentKey(), cancelDto); + } + + + //예약 상태 취소로 변경 + booking.cancel(dto.reason()); + + return BookingResponseDTO.CancelBookingResultDTO.builder() + .bookingId(booking.getId()) + .status(booking.getStatus().name()) + .refundAmount(booking.getDepositAmount()) + .build(); + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryService.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryService.java index cc9df066..02d05e27 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryService.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryService.java @@ -2,6 +2,7 @@ import com.eatsfine.eatsfine.domain.booking.dto.request.BookingRequestDTO; import com.eatsfine.eatsfine.domain.booking.dto.response.BookingResponseDTO; +import com.eatsfine.eatsfine.domain.user.entity.User; import java.time.LocalDate; import java.time.LocalTime; @@ -11,4 +12,6 @@ public interface BookingQueryService { BookingResponseDTO.TimeSlotListDTO getAvailableTimeSlots(Long storeId, BookingRequestDTO.GetAvailableTimeDTO dto); BookingResponseDTO.AvailableTableListDTO getAvailableTables(Long storeId, BookingRequestDTO.GetAvailableTableDTO dto); + + BookingResponseDTO.BookingPreviewListDTO getBookingList(User user, String status, Integer page); } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryServiceImpl.java index 62192a5d..f522e724 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryServiceImpl.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/service/BookingQueryServiceImpl.java @@ -2,17 +2,25 @@ import com.eatsfine.eatsfine.domain.booking.dto.request.BookingRequestDTO; import com.eatsfine.eatsfine.domain.booking.dto.response.BookingResponseDTO; +import com.eatsfine.eatsfine.domain.booking.entity.Booking; +import com.eatsfine.eatsfine.domain.booking.enums.BookingStatus; import com.eatsfine.eatsfine.domain.booking.exception.BookingException; import com.eatsfine.eatsfine.domain.booking.repository.BookingRepository; import com.eatsfine.eatsfine.domain.booking.status.BookingErrorStatus; import com.eatsfine.eatsfine.domain.businesshours.entity.BusinessHours; +import com.eatsfine.eatsfine.domain.payment.entity.Payment; +import com.eatsfine.eatsfine.domain.payment.enums.PaymentStatus; import com.eatsfine.eatsfine.domain.store.entity.Store; import com.eatsfine.eatsfine.domain.store.repository.StoreRepository; import com.eatsfine.eatsfine.domain.store.status.StoreErrorStatus; import com.eatsfine.eatsfine.domain.storetable.entity.StoreTable; import com.eatsfine.eatsfine.domain.table_layout.entity.TableLayout; import com.eatsfine.eatsfine.domain.table_layout.repository.TableLayoutRepository; +import com.eatsfine.eatsfine.domain.user.entity.User; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,6 +28,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -127,4 +136,55 @@ public BookingResponseDTO.AvailableTableListDTO getAvailableTables(Long storeId, .tables(availableTables) .build(); } + + @Override + public BookingResponseDTO.BookingPreviewListDTO getBookingList(User user, String status, Integer page) { + PageRequest pageRequest = PageRequest.of(page, 10, Sort.by("bookingDate").descending()); + + Page bookingPage; + + if(status == null || status.equals("ALL")) { + bookingPage = bookingRepository.findAllByUser(user, pageRequest); + } else { + BookingStatus bookingStatus = BookingStatus.valueOf(status); + bookingPage = bookingRepository.findAllByUserAndStatus(user, bookingStatus, pageRequest); + } + + List bookingPreviewDTOList = bookingPage.getContent().stream() + .map(booking -> { + + // 성공한 결제 정보 추출 (1:N 대응) + Payment successPayment = booking.getPayments().stream() + .filter(p -> p.getPaymentStatus() == PaymentStatus.COMPLETED || p.getPaymentStatus() == PaymentStatus.REFUNDED) + .findFirst() + .orElse(null); + + // 테이블 번호들을 하나의 문자열로 합치기 + String tableNumbers = booking.getBookingTables().stream() + .map(bt -> bt.getStoreTable().getTableNumber().toString()) + .collect(Collectors.joining(", ")); + + return BookingResponseDTO.BookingPreviewDTO.builder() + .bookingId(booking.getId()) + .storeName(booking.getStore().getStoreName()) + .storeAddress(booking.getStore().getAddress()) + .bookingDate(booking.getBookingDate()) + .bookingTime(booking.getBookingTime()) + .partySize(booking.getPartySize()) + .tableNumbers(tableNumbers + "번") + .amount(successPayment != null ? successPayment.getAmount() : booking.getDepositAmount()) + .paymentMethod(successPayment != null ? successPayment.getPaymentMethod().name() : "미결제") + .status(booking.getStatus().name()) + .build(); + }).collect(Collectors.toList()); + + return BookingResponseDTO.BookingPreviewListDTO.builder() + .isLast(bookingPage.isLast()) + .isFirst(bookingPage.isFirst()) + .totalPage(bookingPage.getTotalPages()) + .totalElements(bookingPage.getTotalElements()) + .listSize(bookingPreviewDTOList.size()) + .bookingList(bookingPreviewDTOList) + .build(); + } } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingErrorStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingErrorStatus.java index b5690932..7ebe586c 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingErrorStatus.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingErrorStatus.java @@ -17,8 +17,9 @@ public enum BookingErrorStatus implements BaseErrorCode { _INVALID_PARTY_SIZE(HttpStatus.BAD_REQUEST, "BOOKING4001", "인원 설정이 잘못되었습니다."), _ALREADY_RESERVED_TABLE(HttpStatus.CONFLICT, "BOOKING4091", "선택하신 테이블 중 이미 예약된 테이블이 포함되어 있습니다."), _ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST,"BOOKING4002", "이미 확정된 예약입니다."), - _PAYMENT_AMOUNT_MISMATCH(HttpStatus.BAD_REQUEST, "BOOKING4003", "결제 금액이 일치하지 않습니다."); - + _PAYMENT_AMOUNT_MISMATCH(HttpStatus.BAD_REQUEST, "BOOKING4003", "결제 금액이 일치하지 않습니다."), + _ALREADY_CANCELED(HttpStatus.BAD_REQUEST,"BOOKING4004", "이미 취소된 예약입니다."), + ; private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingSuccessStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingSuccessStatus.java index 1e6b1e3e..0454cec3 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingSuccessStatus.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/status/BookingSuccessStatus.java @@ -13,6 +13,12 @@ public enum BookingSuccessStatus implements BaseCode { _BOOKING_FOUND(HttpStatus.OK, "BOOKING200", "성공적으로 예약을 조회 했습니다."), _BOOKING_DETAIL_FOUND(HttpStatus.FOUND, "BOOKING_DETAIL200", "성공적으로 예약 상세 내역을 조회했습니다."), + + _BOOKING_CREATED(HttpStatus.CREATED, "BOOKING201", "성공적으로 예약이 생성되었습니다."), + + _BOOKING_CONFIRMED(HttpStatus.OK, "BOOKING2001", "성공적으로 예약이 확정되었습니다."), + + _BOOKING_CANCELED(HttpStatus.OK, "BOOKING2002", "성공적으로 예약이 취소되었습니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/request/PaymentConfirmDTO.java b/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/request/PaymentConfirmDTO.java index 863c4a69..9c936a68 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/request/PaymentConfirmDTO.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/request/PaymentConfirmDTO.java @@ -3,9 +3,11 @@ import jakarta.validation.constraints.NotNull; import lombok.Builder; +import java.math.BigDecimal; + @Builder public record PaymentConfirmDTO( @NotNull String paymentKey, @NotNull String orderId, - @NotNull Integer amount) { + @NotNull BigDecimal amount) { } diff --git a/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/response/PaymentResponseDTO.java b/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/response/PaymentResponseDTO.java index 385f5533..8c690061 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/response/PaymentResponseDTO.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/payment/dto/response/PaymentResponseDTO.java @@ -1,6 +1,7 @@ package com.eatsfine.eatsfine.domain.payment.dto.response; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -10,7 +11,7 @@ public record PaymentRequestResultDTO( Long paymentId, Long bookingId, String orderId, - Integer amount, + BigDecimal amount, LocalDateTime requestedAt) { } @@ -26,7 +27,7 @@ public record PaymentHistoryResultDTO( Long paymentId, Long bookingId, String storeName, - Integer amount, + BigDecimal amount, String paymentType, String paymentMethod, String paymentProvider, @@ -51,7 +52,7 @@ public record PaymentDetailResultDTO( String storeName, String paymentMethod, String paymentProvider, - Integer amount, + BigDecimal amount, String paymentType, String status, LocalDateTime requestedAt, @@ -65,7 +66,7 @@ public record PaymentSuccessResultDTO( String status, LocalDateTime approvedAt, String orderId, - Integer amount, + BigDecimal amount, String paymentMethod, String paymentProvider, String receiptUrl) { diff --git a/src/main/java/com/eatsfine/eatsfine/domain/payment/entity/Payment.java b/src/main/java/com/eatsfine/eatsfine/domain/payment/entity/Payment.java index 6e8b8360..710c504d 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/payment/entity/Payment.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/payment/entity/Payment.java @@ -9,6 +9,7 @@ import jakarta.persistence.*; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; @Entity @@ -32,7 +33,7 @@ public class Payment extends BaseEntity { private String orderId; @Column(name = "amount", nullable = false) - private Integer amount; + private BigDecimal amount; @Column(name = "payment_key") private String paymentKey; diff --git a/src/main/java/com/eatsfine/eatsfine/domain/payment/service/PaymentService.java b/src/main/java/com/eatsfine/eatsfine/domain/payment/service/PaymentService.java index ec092c0e..44d38aa8 100644 --- a/src/main/java/com/eatsfine/eatsfine/domain/payment/service/PaymentService.java +++ b/src/main/java/com/eatsfine/eatsfine/domain/payment/service/PaymentService.java @@ -23,6 +23,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestClient; + +import java.math.BigDecimal; import org.springframework.data.domain.PageRequest; import java.time.LocalDateTime; import java.util.UUID; @@ -47,7 +49,7 @@ public PaymentResponseDTO.PaymentRequestResultDTO requestPayment(PaymentRequestD String orderId = UUID.randomUUID().toString(); // 예약금 검증 - if (booking.getDepositAmount() == null || booking.getDepositAmount() <= 0) { + if (booking.getDepositAmount() == null || booking.getDepositAmount().compareTo(BigDecimal.ZERO) <= 0) { throw new PaymentException(PaymentErrorStatus._PAYMENT_INVALID_DEPOSIT); } @@ -75,11 +77,10 @@ public PaymentResponseDTO.PaymentSuccessResultDTO confirmPayment(PaymentConfirmD Payment payment = paymentRepository.findByOrderId(dto.orderId()) .orElseThrow(() -> new PaymentException(PaymentErrorStatus._PAYMENT_NOT_FOUND)); - if (!payment.getAmount().equals(dto.amount())) { + if (payment.getAmount().compareTo(dto.amount()) != 0) { payment.failPayment(); throw new PaymentException(PaymentErrorStatus._PAYMENT_INVALID_AMOUNT); } - // 토스 API 호출 TossPaymentResponse response; try { diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6feee07c..a5a7c16b 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -27,3 +27,10 @@ spring: payment: toss: widget-secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6 + +cloud: + aws: + region: ap-northeast-2 + s3: + bucket: eatsfine-images + base-url: https://eatsfine-images.s3.ap-northeast-2.amazonaws.com \ No newline at end of file