From 6be9ae7efe2861b471c162a13fe524da184b125f Mon Sep 17 00:00:00 2001 From: pricelees Date: Tue, 7 Oct 2025 22:28:02 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20&=20=EC=98=88?= =?UTF-8?q?=EC=95=BD=20=ED=99=95=EC=A0=95=20=EA=B3=BC=EC=A0=95=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/business/OrderValidator.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 service/src/main/kotlin/com/sangdol/roomescape/order/business/OrderValidator.kt diff --git a/service/src/main/kotlin/com/sangdol/roomescape/order/business/OrderValidator.kt b/service/src/main/kotlin/com/sangdol/roomescape/order/business/OrderValidator.kt new file mode 100644 index 00000000..e475f260 --- /dev/null +++ b/service/src/main/kotlin/com/sangdol/roomescape/order/business/OrderValidator.kt @@ -0,0 +1,76 @@ +package com.sangdol.roomescape.order.business + +import com.sangdol.common.utils.KoreaDateTime +import com.sangdol.roomescape.order.exception.OrderErrorCode +import com.sangdol.roomescape.order.exception.OrderException +import com.sangdol.roomescape.order.infrastructure.persistence.PaymentAttemptRepository +import com.sangdol.roomescape.reservation.dto.ReservationStateResponse +import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus +import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse +import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Component +import java.time.Instant +import java.time.LocalDateTime + +private val log: KLogger = KotlinLogging.logger {} + +@Component +class OrderValidator( + private val paymentAttemptRepository: PaymentAttemptRepository +) { + fun validateCanConfirm( + reservation: ReservationStateResponse, + schedule: ScheduleStateResponse + ) { + if (paymentAttemptRepository.isSuccessAttemptExists(reservation.id)) { + log.info { "[validateCanConfirm] 이미 결제 완료된 예약: id=${reservation.id}" } + throw OrderException(OrderErrorCode.BOOKING_ALREADY_COMPLETED) + } + + validateReservationStatus(reservation) + validateScheduleStatus(schedule) + } + + private fun validateReservationStatus(reservation: ReservationStateResponse) { + when (reservation.status) { + ReservationStatus.CONFIRMED -> { + log.info { "[validateCanConfirm] 이미 확정된 예약: id=${reservation.id}" } + throw OrderException(OrderErrorCode.BOOKING_ALREADY_COMPLETED) + } + ReservationStatus.EXPIRED -> { + log.info { "[validateCanConfirm] 만료된 예약 예약: id=${reservation.id}" } + throw OrderException(OrderErrorCode.EXPIRED_RESERVATION) + } + ReservationStatus.CANCELED -> { + log.info { "[validateCanConfirm] 취소된 예약 예약: id=${reservation.id}" } + throw OrderException(OrderErrorCode.CANCELED_RESERVATION) + } + ReservationStatus.PENDING -> { + val pendingExpiredAt = reservation.createdAt.plusSeconds(5 * 60) + val now = Instant.now() + + if (now.isAfter(pendingExpiredAt)) { + log.info { "[validateCanConfirm] Pending 예약 시간 내 미결제로 인한 실패: id=${reservation.id}, expiredAt=${pendingExpiredAt}, now=${now}" } + throw OrderException(OrderErrorCode.BOOKING_PAYMENT_TIMEOUT) + } + } + else -> {} + } + } + + private fun validateScheduleStatus(schedule: ScheduleStateResponse) { + if (schedule.status != ScheduleStatus.HOLD) { + log.info { "[validateScheduleStatus] 일정 상태 오류: status=${schedule.status}" } + throw OrderException(OrderErrorCode.EXPIRED_RESERVATION) + } + + val scheduleDateTime = LocalDateTime.of(schedule.date, schedule.startFrom) + val nowDateTime = KoreaDateTime.now() + if (scheduleDateTime.isBefore(nowDateTime)) { + log.info { "[validateScheduleStatus] 과거 시간인 일정으로 인한 실패: scheduleDateTime=${scheduleDateTime}(KST), now=${nowDateTime}(KST)" } + throw OrderException(OrderErrorCode.PAST_SCHEDULE) + } + } +}