generated from pricelees/issue-pr-template
[#56] 예약 & 결제 프로세스 및 패키지 구조 재정의 #57
@ -0,0 +1,130 @@
|
|||||||
|
package com.sangdol.roomescape.order.business
|
||||||
|
|
||||||
|
import com.sangdol.common.persistence.IDGenerator
|
||||||
|
import com.sangdol.common.persistence.TransactionExecutionUtil
|
||||||
|
import com.sangdol.common.types.exception.ErrorCode
|
||||||
|
import com.sangdol.common.types.exception.RoomescapeException
|
||||||
|
import com.sangdol.roomescape.order.exception.OrderErrorCode
|
||||||
|
import com.sangdol.roomescape.order.exception.OrderException
|
||||||
|
import com.sangdol.roomescape.order.infrastructure.persistence.AttemptResult
|
||||||
|
import com.sangdol.roomescape.order.infrastructure.persistence.PaymentAttemptEntity
|
||||||
|
import com.sangdol.roomescape.order.infrastructure.persistence.PaymentAttemptRepository
|
||||||
|
import com.sangdol.roomescape.payment.business.PaymentService
|
||||||
|
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
|
||||||
|
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
|
||||||
|
import com.sangdol.roomescape.payment.exception.PaymentException
|
||||||
|
import com.sangdol.roomescape.reservation.business.ReservationService
|
||||||
|
import com.sangdol.roomescape.reservation.dto.ReservationStateResponse
|
||||||
|
import com.sangdol.roomescape.schedule.business.ScheduleService
|
||||||
|
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
|
||||||
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class OrderService(
|
||||||
|
private val idGenerator: IDGenerator,
|
||||||
|
private val reservationService: ReservationService,
|
||||||
|
private val scheduleService: ScheduleService,
|
||||||
|
private val paymentService: PaymentService,
|
||||||
|
private val transactionExecutionUtil: TransactionExecutionUtil,
|
||||||
|
private val orderValidator: OrderValidator,
|
||||||
|
private val paymentAttemptRepository: PaymentAttemptRepository,
|
||||||
|
private val orderPostProcessorService: OrderPostProcessorService
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun confirm(reservationId: Long, paymentConfirmRequest: PaymentConfirmRequest) {
|
||||||
|
val trial = paymentAttemptRepository.countByReservationId(reservationId)
|
||||||
|
val paymentKey = paymentConfirmRequest.paymentKey
|
||||||
|
|
||||||
|
log.info { "[confirm] 결제 및 예약 확정 시작: reservationId=${reservationId}, paymentKey=${paymentKey}" }
|
||||||
|
|
||||||
|
try {
|
||||||
|
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
|
validateAndMarkInProgress(reservationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentClientResponse: PaymentGatewayResponse =
|
||||||
|
requestConfirmPayment(reservationId, paymentConfirmRequest)
|
||||||
|
|
||||||
|
orderPostProcessorService.processAfterPaymentConfirmation(reservationId, paymentClientResponse)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val errorCode: ErrorCode = if (e is RoomescapeException) {
|
||||||
|
e.errorCode
|
||||||
|
} else {
|
||||||
|
OrderErrorCode.BOOKING_UNEXPECTED_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
throw OrderException(errorCode, e.message ?: errorCode.message, trial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAndMarkInProgress(reservationId: Long) {
|
||||||
|
log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 시작: reservationId=${reservationId}" }
|
||||||
|
val reservation: ReservationStateResponse = reservationService.findStatusWithLock(reservationId)
|
||||||
|
val schedule: ScheduleStateResponse = scheduleService.findStateWithLock(reservation.scheduleId)
|
||||||
|
|
||||||
|
try {
|
||||||
|
orderValidator.validateCanConfirm(reservation, schedule)
|
||||||
|
log.info { "[validateAndMarkInProgress] 예약 확정 가능 여부 검증 완료: reservationId=${reservationId}" }
|
||||||
|
} catch (e: OrderException) {
|
||||||
|
val errorCode = OrderErrorCode.NOT_CONFIRMABLE
|
||||||
|
throw OrderException(errorCode, e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
reservationService.markInProgress(reservationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestConfirmPayment(
|
||||||
|
reservationId: Long,
|
||||||
|
paymentConfirmRequest: PaymentConfirmRequest
|
||||||
|
): PaymentGatewayResponse {
|
||||||
|
log.info { "[requestConfirmPayment] 결제 및 이력 저장 시작: reservationId=${reservationId}, paymentKey=${paymentConfirmRequest.paymentKey}" }
|
||||||
|
val paymentResponse: PaymentGatewayResponse
|
||||||
|
var attempt: PaymentAttemptEntity? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
paymentResponse = paymentService.requestConfirm(paymentConfirmRequest)
|
||||||
|
|
||||||
|
attempt = PaymentAttemptEntity(
|
||||||
|
id = idGenerator.create(),
|
||||||
|
reservationId = reservationId,
|
||||||
|
result = AttemptResult.SUCCESS,
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val errorCode: String = if (e is PaymentException) {
|
||||||
|
log.info { "[requestConfirmPayment] 결제 요청 실패: reservationId=${reservationId}, paymentKey=${paymentConfirmRequest.paymentKey}" }
|
||||||
|
e.errorCode.name
|
||||||
|
} else {
|
||||||
|
log.warn {
|
||||||
|
"[requestConfirmPayment] 결제 요청 실패: reservationId=${reservationId}, paymentKey=${paymentConfirmRequest.paymentKey}"
|
||||||
|
}
|
||||||
|
OrderErrorCode.BOOKING_UNEXPECTED_ERROR.name
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt = PaymentAttemptEntity(
|
||||||
|
id = idGenerator.create(),
|
||||||
|
reservationId = reservationId,
|
||||||
|
result = AttemptResult.FAILED,
|
||||||
|
errorCode = errorCode,
|
||||||
|
message = e.message
|
||||||
|
)
|
||||||
|
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
val savedAttempt: PaymentAttemptEntity? = attempt?.let {
|
||||||
|
log.info { "[requestPayment] 결제 요청 이력 저장 시작: id=${it.id}, reservationId=${it.reservationId}, result=${it.result}, errorCode=${it.errorCode}, message=${it.message}" }
|
||||||
|
paymentAttemptRepository.save(it)
|
||||||
|
}
|
||||||
|
savedAttempt?.also {
|
||||||
|
log.info { "[requestPayment] 결제 요청 이력 저장 완료: id=${savedAttempt.id}" }
|
||||||
|
} ?: run {
|
||||||
|
log.info { "[requestPayment] 결제 요청 이력 저장 실패: reservationId=${reservationId}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.sangdol.roomescape.order.docs
|
||||||
|
|
||||||
|
import com.sangdol.common.types.web.CommonApiResponse
|
||||||
|
import com.sangdol.roomescape.auth.web.support.UserOnly
|
||||||
|
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
|
||||||
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
|
||||||
|
interface OrderAPI {
|
||||||
|
|
||||||
|
@UserOnly
|
||||||
|
@Operation(summary = "결제 및 예약 완료 처리")
|
||||||
|
@ApiResponses(ApiResponse(responseCode = "200"))
|
||||||
|
fun confirm(
|
||||||
|
@PathVariable("reservationId") reservationId: Long,
|
||||||
|
@RequestBody request: PaymentConfirmRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>>
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.sangdol.roomescape.order.web
|
||||||
|
|
||||||
|
import com.sangdol.common.types.web.CommonApiResponse
|
||||||
|
import com.sangdol.roomescape.order.business.OrderService
|
||||||
|
import com.sangdol.roomescape.order.docs.OrderAPI
|
||||||
|
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.*
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/orders")
|
||||||
|
class OrderController(
|
||||||
|
private val orderService: OrderService
|
||||||
|
) : OrderAPI {
|
||||||
|
|
||||||
|
@PostMapping("/{reservationId}/confirm")
|
||||||
|
override fun confirm(
|
||||||
|
@PathVariable("reservationId") reservationId: Long,
|
||||||
|
@RequestBody request: PaymentConfirmRequest
|
||||||
|
): ResponseEntity<CommonApiResponse<Unit>> {
|
||||||
|
orderService.confirm(reservationId, request)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,8 +31,11 @@ class PaymentService(
|
|||||||
private val transactionExecutionUtil: TransactionExecutionUtil,
|
private val transactionExecutionUtil: TransactionExecutionUtil,
|
||||||
) {
|
) {
|
||||||
fun requestConfirm(request: PaymentConfirmRequest): PaymentGatewayResponse {
|
fun requestConfirm(request: PaymentConfirmRequest): PaymentGatewayResponse {
|
||||||
|
log.info { "[requestConfirm] 결제 요청 시작: paymentKey=${request.paymentKey}" }
|
||||||
try {
|
try {
|
||||||
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount)
|
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount).also {
|
||||||
|
log.info { "[requestConfirm] 결제 완료: paymentKey=${request.paymentKey}" }
|
||||||
|
}
|
||||||
} catch (e: ExternalPaymentException) {
|
} catch (e: ExternalPaymentException) {
|
||||||
val errorCode = if (e.httpStatusCode in 400..<500) {
|
val errorCode = if (e.httpStatusCode in 400..<500) {
|
||||||
PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user