diff --git a/src/main/kotlin/roomescape/payment/business/PaymentService.kt b/src/main/kotlin/roomescape/payment/business/PaymentService.kt index b560ec06..2219bbbc 100644 --- a/src/main/kotlin/roomescape/payment/business/PaymentService.kt +++ b/src/main/kotlin/roomescape/payment/business/PaymentService.kt @@ -1,99 +1,112 @@ package roomescape.payment.business +import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import roomescape.payment.implement.PaymentFinder -import roomescape.payment.implement.PaymentWriter -import roomescape.payment.infrastructure.client.PaymentApproveResponse +import roomescape.common.util.TransactionExecutionUtil +import roomescape.payment.exception.PaymentErrorCode +import roomescape.payment.exception.PaymentException +import roomescape.payment.infrastructure.client.PaymentClientCancelResponse +import roomescape.payment.infrastructure.client.PaymentClientConfirmResponse +import roomescape.payment.infrastructure.client.TosspaymentClient import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity +import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository +import roomescape.payment.infrastructure.persistence.PaymentDetailEntity +import roomescape.payment.infrastructure.persistence.PaymentDetailRepository import roomescape.payment.infrastructure.persistence.PaymentEntity +import roomescape.payment.infrastructure.persistence.PaymentRepository import roomescape.payment.web.PaymentCancelRequest -import roomescape.payment.web.PaymentCancelResponse +import roomescape.payment.web.PaymentConfirmRequest import roomescape.payment.web.PaymentCreateResponse -import roomescape.payment.web.toCreateResponse -import roomescape.reservation.infrastructure.persistence.ReservationEntity -import java.time.OffsetDateTime +import roomescape.payment.web.PaymentRetrieveResponse +import roomescape.payment.web.toCancelDetailResponse +import roomescape.payment.web.toPaymentDetailResponse +import roomescape.payment.web.toRetrieveResponse -private val log = KotlinLogging.logger {} +private val log: KLogger = KotlinLogging.logger {} @Service class PaymentService( - private val paymentFinder: PaymentFinder, - private val paymentWriter: PaymentWriter + private val paymentClient: TosspaymentClient, + private val paymentRepository: PaymentRepository, + private val paymentDetailRepository: PaymentDetailRepository, + private val canceledPaymentRepository: CanceledPaymentRepository, + private val paymentWriter: PaymentWriter, + private val transactionExecutionUtil: TransactionExecutionUtil, ) { - @Transactional(readOnly = true) - fun existsByReservationId(reservationId: Long): Boolean { - log.debug { "[PaymentService.existsByReservationId] 시작: reservationId=$reservationId" } - - return paymentFinder.existsPaymentByReservationId(reservationId) - .also { log.info { "[PaymentService.existsByReservationId] 완료: reservationId=$reservationId, isPaid=$it" } } - } - - @Transactional - fun createPayment( - approvedPaymentInfo: PaymentApproveResponse, - reservation: ReservationEntity, - ): PaymentCreateResponse { - log.debug { "[PaymentService.createPayment] 시작: paymentKey=${approvedPaymentInfo.paymentKey}, reservationId=${reservation.id}" } - - val created: PaymentEntity = paymentWriter.create( - paymentKey = approvedPaymentInfo.paymentKey, - orderId = approvedPaymentInfo.orderId, - totalAmount = approvedPaymentInfo.totalAmount, - approvedAt = approvedPaymentInfo.approvedAt, - reservation = reservation + fun confirm(reservationId: Long, request: PaymentConfirmRequest): PaymentCreateResponse { + val clientConfirmResponse: PaymentClientConfirmResponse = paymentClient.confirm( + paymentKey = request.paymentKey, + orderId = request.orderId, + amount = request.amount, ) - return created.toCreateResponse() - .also { log.info { "[PaymentService.createPayment] 완료: paymentKey=${it.paymentKey}, reservationId=${reservation.id}, paymentId=${it.id}" } } - } + return transactionExecutionUtil.withNewTransaction(isReadOnly = false) { + val payment: PaymentEntity = paymentWriter.createPayment( + reservationId = reservationId, + orderId = request.orderId, + paymentType = request.paymentType, + paymentClientConfirmResponse = clientConfirmResponse + ) + val detail: PaymentDetailEntity = paymentWriter.createDetail(clientConfirmResponse, payment.id) - @Transactional - fun createCanceledPayment( - canceledPaymentInfo: PaymentCancelResponse, - approvedAt: OffsetDateTime, - paymentKey: String, - ): CanceledPaymentEntity { - log.debug { "[PaymentService.createCanceledPayment] 시작: paymentKey=$paymentKey" } - - val created: CanceledPaymentEntity = paymentWriter.createCanceled( - cancelReason = canceledPaymentInfo.cancelReason, - cancelAmount = canceledPaymentInfo.cancelAmount, - canceledAt = canceledPaymentInfo.canceledAt, - approvedAt = approvedAt, - paymentKey = paymentKey - ) - - return created.also { - log.info { "[PaymentService.createCanceledPayment] 완료: paymentKey=${paymentKey}, canceledPaymentId=${it.id}" } + PaymentCreateResponse(paymentId = payment.id, detailId = detail.id) } } - @Transactional - fun createCanceledPayment(reservationId: Long): PaymentCancelRequest { - log.debug { "[PaymentService.createCanceledPayment] 시작: reservationId=$reservationId" } + fun cancel(memberId: Long, request: PaymentCancelRequest) { + val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId) - val payment: PaymentEntity = paymentFinder.findByReservationId(reservationId) - val canceled: CanceledPaymentEntity = paymentWriter.createCanceled( - payment = payment, - cancelReason = "예약 취소", - canceledAt = OffsetDateTime.now(), + val clientCancelResponse: PaymentClientCancelResponse = paymentClient.cancel( + paymentKey = payment.paymentKey, + amount = payment.totalAmount, + cancelReason = request.cancelReason ) - return PaymentCancelRequest(canceled.paymentKey, canceled.cancelAmount, canceled.cancelReason) - .also { log.info { "[PaymentService.createCanceledPayment] 완료: reservationId=$reservationId, paymentKey=${it.paymentKey}" } } + transactionExecutionUtil.withNewTransaction(isReadOnly = false) { + paymentWriter.cancel( + memberId = memberId, + payment = payment, + requestedAt = request.requestedAt, + cancelResponse = clientCancelResponse + ) + }.also { + log.info { "[PaymentService.cancel] 결제 취소 완료: paymentId=${payment.id}" } + } } - @Transactional - fun updateCanceledTime( - paymentKey: String, - canceledAt: OffsetDateTime, - ) { - log.debug { "[PaymentService.updateCanceledTime] 시작: paymentKey=$paymentKey, canceledAt=$canceledAt" } + @Transactional(readOnly = true) + fun findDetailByReservationId(reservationId: Long): PaymentRetrieveResponse { + val payment: PaymentEntity = findByReservationIdOrThrow(reservationId) + val paymentDetail: PaymentDetailEntity = findDetailByPaymentIdOrThrow(payment.id) + val cancelDetail: CanceledPaymentEntity? = canceledPaymentRepository.findByPaymentId(payment.id) - paymentFinder.findCanceledByKey(paymentKey).apply { this.canceledAt = canceledAt } + return payment.toRetrieveResponse( + detail = paymentDetail.toPaymentDetailResponse(), + cancel = cancelDetail?.toCancelDetailResponse() + ) + } - log.info { "[PaymentService.updateCanceledTime] 완료: paymentKey=$paymentKey, canceledAt=$canceledAt" } + private fun findByReservationIdOrThrow(reservationId: Long): PaymentEntity { + log.info { "[PaymentService.findByReservationIdOrThrow] 결제 정보 조회 시작: reservationId=: $reservationId" } + + return paymentRepository.findByReservationId(reservationId) + ?.also { log.info { "[PaymentService.findByReservationIdOrThrow] 결제 정보 조회 완료: reservationId=$reservationId, paymentId=${it.id}" } } + ?: run { + log.warn { "[PaymentService.findByReservationIdOrThrow] 결제 정보 조회 실패: reservationId=$reservationId" } + throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + } + } + + private fun findDetailByPaymentIdOrThrow(paymentId: Long): PaymentDetailEntity { + log.info { "[PaymentService.findDetailByPaymentIdOrThrow] 결제 상세 정보 조회 시작: paymentId=$paymentId" } + + return paymentDetailRepository.findByPaymentId(paymentId) + ?.also { log.info { "[PaymentService.findDetailByPaymentIdOrThrow] 결제 상세 정보 조회 완료: paymentId=$paymentId, detailId=${it.id}}" } } + ?: run { + log.warn { "[PaymentService.findDetailByPaymentIdOrThrow] 결제 상세 정보 조회 실패: paymentId=$paymentId" } + throw PaymentException(PaymentErrorCode.PAYMENT_DETAIL_NOT_FOUND) + } } }