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) + } + } +}