refactor: 결제 & 예약 확정 로직에서 검증 과정을 두 개의 트랜잭션 -> 하나의 트랜잭션으로 통합 및 쿼리 순서 수정

This commit is contained in:
이상진 2025-10-14 09:48:59 +09:00
parent 5f2e44bb11
commit 768c47f1ae
3 changed files with 26 additions and 15 deletions

View File

@ -36,14 +36,18 @@ class OrderService(
) { ) {
fun confirm(reservationId: Long, paymentConfirmRequest: PaymentConfirmRequest) { fun confirm(reservationId: Long, paymentConfirmRequest: PaymentConfirmRequest) {
val trial = paymentAttemptRepository.countByReservationId(reservationId) var trial: Long = 0
val paymentKey = paymentConfirmRequest.paymentKey val paymentKey = paymentConfirmRequest.paymentKey
log.info { "[confirm] 결제 및 예약 확정 시작: reservationId=${reservationId}, paymentKey=${paymentKey}" } log.info { "[confirm] 결제 및 예약 확정 시작: reservationId=${reservationId}, paymentKey=${paymentKey}" }
try { try {
transactionExecutionUtil.withNewTransaction(isReadOnly = false) { trial = transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
validateAndMarkInProgress(reservationId) getTrialAfterValidateCanConfirm(reservationId).also {
reservationService.markInProgress(reservationId)
}
} ?: run {
log.warn { "[confirm] 모든 paymentAttempts 조회 과정에서의 예상치 못한 null 응답: reservationId=${reservationId}" }
throw OrderException(OrderErrorCode.BOOKING_UNEXPECTED_ERROR)
} }
val paymentClientResponse: PaymentGatewayResponse = val paymentClientResponse: PaymentGatewayResponse =
@ -61,20 +65,32 @@ class OrderService(
} }
} }
private fun validateAndMarkInProgress(reservationId: Long) { private fun getTrialAfterValidateCanConfirm(reservationId: Long): Long {
log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 시작: reservationId=${reservationId}" } log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 시작: reservationId=${reservationId}" }
val reservation: ReservationStateResponse = reservationService.findStatusWithLock(reservationId) val reservation: ReservationStateResponse = reservationService.findStatusWithLock(reservationId)
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(reservation.scheduleId) val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(reservation.scheduleId)
try { try {
orderValidator.validateCanConfirm(reservation, schedule) orderValidator.validateCanConfirm(reservation, schedule)
return getTrialIfSuccessAttemptNotExists(reservationId).also {
log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 완료: reservationId=${reservationId}" } log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 완료: reservationId=${reservationId}" }
}
} catch (e: OrderException) { } catch (e: OrderException) {
val errorCode = OrderErrorCode.NOT_CONFIRMABLE val errorCode = OrderErrorCode.NOT_CONFIRMABLE
throw OrderException(errorCode, e.message) throw OrderException(errorCode, e.message)
} }
}
reservationService.markInProgress(reservationId) private fun getTrialIfSuccessAttemptNotExists(reservationId: Long): Long {
val paymentAttempts: List<PaymentAttemptEntity> = paymentAttemptRepository.findAllByReservationId(reservationId)
if (paymentAttempts.any { it.result == AttemptResult.SUCCESS }) {
log.info { "[validateCanConfirm] 이미 결제 완료된 예약: id=${reservationId}" }
throw OrderException(OrderErrorCode.BOOKING_ALREADY_COMPLETED)
}
return paymentAttempts.size.toLong()
} }
private fun requestConfirmPayment( private fun requestConfirmPayment(

View File

@ -16,18 +16,11 @@ import java.time.LocalDateTime
private val log: KLogger = KotlinLogging.logger {} private val log: KLogger = KotlinLogging.logger {}
@Component @Component
class OrderValidator( class OrderValidator {
private val paymentAttemptRepository: PaymentAttemptRepository
) {
fun validateCanConfirm( fun validateCanConfirm(
reservation: ReservationStateResponse, reservation: ReservationStateResponse,
schedule: ScheduleStateResponse schedule: ScheduleStateResponse
) { ) {
if (paymentAttemptRepository.isSuccessAttemptExists(reservation.id)) {
log.info { "[validateCanConfirm] 이미 결제 완료된 예약: id=${reservation.id}" }
throw OrderException(OrderErrorCode.BOOKING_ALREADY_COMPLETED)
}
validateReservationStatus(reservation) validateReservationStatus(reservation)
validateScheduleStatus(schedule) validateScheduleStatus(schedule)
} }

View File

@ -23,4 +23,6 @@ interface PaymentAttemptRepository: JpaRepository<PaymentAttemptEntity, Long> {
""" """
) )
fun isSuccessAttemptExists(reservationId: Long): Boolean fun isSuccessAttemptExists(reservationId: Long): Boolean
fun findAllByReservationId(reservationId: Long): List<PaymentAttemptEntity>
} }