diff --git a/src/main/kotlin/roomescape/payment/business/PaymentService.kt b/src/main/kotlin/roomescape/payment/business/PaymentService.kt index 4f2d9c83..236bcbf3 100644 --- a/src/main/kotlin/roomescape/payment/business/PaymentService.kt +++ b/src/main/kotlin/roomescape/payment/business/PaymentService.kt @@ -1,5 +1,6 @@ package roomescape.payment.business +import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import roomescape.payment.exception.PaymentErrorCode @@ -16,85 +17,127 @@ import roomescape.payment.web.toCreateResponse import roomescape.reservation.infrastructure.persistence.ReservationEntity import java.time.OffsetDateTime +private val log = KotlinLogging.logger {} + @Service class PaymentService( - private val paymentRepository: PaymentRepository, - private val canceledPaymentRepository: CanceledPaymentRepository + private val paymentRepository: PaymentRepository, + private val canceledPaymentRepository: CanceledPaymentRepository, ) { @Transactional fun createPayment( - approveResponse: PaymentApproveResponse, - reservation: ReservationEntity + approveResponse: PaymentApproveResponse, + reservation: ReservationEntity, ): PaymentCreateResponse { + log.debug { "[PaymentService.createPayment] 결제 정보 저장 시작: request=$approveResponse, reservationId=${reservation.id}" } val payment = PaymentEntity( - orderId = approveResponse.orderId, - paymentKey = approveResponse.paymentKey, - totalAmount = approveResponse.totalAmount, - reservation = reservation, - approvedAt = approveResponse.approvedAt + orderId = approveResponse.orderId, + paymentKey = approveResponse.paymentKey, + totalAmount = approveResponse.totalAmount, + reservation = reservation, + approvedAt = approveResponse.approvedAt ) - return paymentRepository.save(payment).toCreateResponse() + return paymentRepository.save(payment) + .toCreateResponse() + .also { log.info { "[PaymentService.createPayment] 결제 정보 저장 완료: paymentId=${it.id}, reservationId=${reservation.id}" } } } @Transactional(readOnly = true) - fun isReservationPaid(reservationId: Long): Boolean = paymentRepository.existsByReservationId(reservationId) + fun isReservationPaid(reservationId: Long): Boolean { + log.debug { "[PaymentService.isReservationPaid] 예약 결제 여부 확인 시작: reservationId=$reservationId" } + + return paymentRepository.existsByReservationId(reservationId) + .also { log.info { "[PaymentService.isReservationPaid] 예약 결제 여부 확인 완료: reservationId=$reservationId, isPaid=$it" } } + } @Transactional fun createCanceledPayment( - cancelInfo: PaymentCancelResponse, - approvedAt: OffsetDateTime, - paymentKey: String + cancelInfo: PaymentCancelResponse, + approvedAt: OffsetDateTime, + paymentKey: String, ): CanceledPaymentEntity { + log.debug { + "[PaymentService.createCanceledPayment] 결제 취소 정보 저장 시작: paymentKey=$paymentKey" + + ", cancelInfo=$cancelInfo" + } val canceledPayment = CanceledPaymentEntity( - paymentKey = paymentKey, - cancelReason = cancelInfo.cancelReason, - cancelAmount = cancelInfo.cancelAmount, - approvedAt = approvedAt, - canceledAt = cancelInfo.canceledAt + paymentKey = paymentKey, + cancelReason = cancelInfo.cancelReason, + cancelAmount = cancelInfo.cancelAmount, + approvedAt = approvedAt, + canceledAt = cancelInfo.canceledAt ) return canceledPaymentRepository.save(canceledPayment) + .also { + log.info { + "[PaymentService.createCanceledPayment] 결제 취소 정보 생성 완료: canceledPaymentId=${it.id}" + + ", paymentKey=${paymentKey}, amount=${cancelInfo.cancelAmount}, canceledAt=${it.canceledAt}" + } + } } @Transactional fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest { + log.debug { "[PaymentService.createCanceledPaymentByReservationId] 예약 삭제 & 결제 취소 정보 저장 시작: reservationId=$reservationId" } val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId) - ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + ?: run { + log.warn { "[PaymentService.createCanceledPaymentByReservationId] 예약 조회 실패: reservationId=$reservationId" } + throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + } - // 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다. val canceled: CanceledPaymentEntity = cancelPayment(paymentKey) return PaymentCancelRequest(paymentKey, canceled.cancelAmount, canceled.cancelReason) + .also { log.info { "[PaymentService.createCanceledPaymentByReservationId] 예약 ID로 결제 취소 완료: reservationId=$reservationId" } } } private fun cancelPayment( - paymentKey: String, - cancelReason: String = "고객 요청", - canceledAt: OffsetDateTime = OffsetDateTime.now() + paymentKey: String, + cancelReason: String = "고객 요청", + canceledAt: OffsetDateTime = OffsetDateTime.now(), ): CanceledPaymentEntity { + log.debug { "[PaymentService.cancelPayment] 결제 취소 정보 저장 시작: paymentKey=$paymentKey" } val payment: PaymentEntity = paymentRepository.findByPaymentKey(paymentKey) - ?.also { paymentRepository.delete(it) } - ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + ?.also { + paymentRepository.delete(it) + log.info { "[PaymentService.cancelPayment] 결제 정보 삭제 완료: paymentId=${it.id}, paymentKey=$paymentKey" } + } + ?: run { + log.warn { "[PaymentService.cancelPayment] 결제 정보 조회 실패: paymentKey=$paymentKey" } + throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + } val canceledPayment = CanceledPaymentEntity( - paymentKey = paymentKey, - cancelReason = cancelReason, - cancelAmount = payment.totalAmount, - approvedAt = payment.approvedAt, - canceledAt = canceledAt + paymentKey = paymentKey, + cancelReason = cancelReason, + cancelAmount = payment.totalAmount, + approvedAt = payment.approvedAt, + canceledAt = canceledAt ) return canceledPaymentRepository.save(canceledPayment) + .also { log.info { "[PaymentService.cancelPayment] 결제 취소 정보 저장 완료: canceledPaymentId=${it.id}" } } } @Transactional fun updateCanceledTime( - paymentKey: String, - canceledAt: OffsetDateTime + paymentKey: String, + canceledAt: OffsetDateTime, ) { + log.debug { "[PaymentService.updateCanceledTime] 취소 시간 업데이트 시작: paymentKey=$paymentKey, canceledAt=$canceledAt" } canceledPaymentRepository.findByPaymentKey(paymentKey) - ?.apply { this.canceledAt = canceledAt } - ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + ?.apply { this.canceledAt = canceledAt } + ?.also { + log.info { + "[PaymentService.updateCanceledTime] 취소 시간 업데이트 완료: paymentKey=$paymentKey" + + ", canceledAt=$canceledAt" + } + } + ?: run { + log.warn { "[PaymentService.updateCanceledTime] 결제 정보 조회 실패: paymentKey=$paymentKey" } + throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND) + } } } diff --git a/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt index baa6594e..ae56e451 100644 --- a/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt +++ b/src/main/kotlin/roomescape/payment/infrastructure/client/TossPaymentClient.kt @@ -15,11 +15,12 @@ import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelResponse import java.util.Map +private val log: KLogger = KotlinLogging.logger {} + @Component class TossPaymentClient( - private val log: KLogger = KotlinLogging.logger {}, - private val objectMapper: ObjectMapper, - tossPaymentClientBuilder: RestClient.Builder, + private val objectMapper: ObjectMapper, + tossPaymentClientBuilder: RestClient.Builder, ) { companion object { private const val CONFIRM_URL: String = "/v1/payments/confirm" @@ -32,16 +33,19 @@ class TossPaymentClient( logPaymentInfo(paymentRequest) return tossPaymentClient.post() - .uri(CONFIRM_URL) - .contentType(MediaType.APPLICATION_JSON) - .body(paymentRequest) - .retrieve() - .onStatus( - { status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError }, - { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) } - ) - .body(PaymentApproveResponse::class.java) - ?: throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) + .uri(CONFIRM_URL) + .contentType(MediaType.APPLICATION_JSON) + .body(paymentRequest) + .retrieve() + .onStatus( + { status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError }, + { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res, "confirm") } + ) + .body(PaymentApproveResponse::class.java) + ?: run { + log.error { "[TossPaymentClient] 응답 변환 오류" } + throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) + } } fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse { @@ -49,47 +53,49 @@ class TossPaymentClient( val param = Map.of("cancelReason", cancelRequest.cancelReason) return tossPaymentClient.post() - .uri(CANCEL_URL, cancelRequest.paymentKey) - .contentType(MediaType.APPLICATION_JSON) - .body(param) - .retrieve() - .onStatus( - { status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError }, - { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res) } - ) - .body(PaymentCancelResponse::class.java) - ?: throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) + .uri(CANCEL_URL, cancelRequest.paymentKey) + .contentType(MediaType.APPLICATION_JSON) + .body(param) + .retrieve() + .onStatus( + { status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError }, + { req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res, "cancel") } + ) + .body(PaymentCancelResponse::class.java) + ?: run { + log.error { "[TossPaymentClient] 응답 변환 오류" } + throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR) + } } private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) { log.info { - "결제 승인 요청: paymentKey=${paymentRequest.paymentKey}, orderId=${paymentRequest.orderId}, " + - "amount=${paymentRequest.amount}, paymentType=${paymentRequest.paymentType}" + "[TossPaymentClient.confirm] 결제 승인 요청: request: $paymentRequest" } } private fun logPaymentCancelInfo(cancelRequest: PaymentCancelRequest) { log.info { - "결제 취소 요청: paymentKey=${cancelRequest.paymentKey}, amount=${cancelRequest.amount}, " + - "cancelReason=${cancelRequest.cancelReason}" + "[TossPaymentClient.cancel] 결제 취소 요청: request: $cancelRequest" } } private fun handlePaymentError( - res: ClientHttpResponse + res: ClientHttpResponse, + calledBy: String ): Nothing { getErrorCodeByHttpStatus(res.statusCode).also { - logTossPaymentError(res) + logTossPaymentError(res, calledBy) throw PaymentException(it) } } - private fun logTossPaymentError(res: ClientHttpResponse): TossPaymentErrorResponse { + private fun logTossPaymentError(res: ClientHttpResponse, calledBy: String): TossPaymentErrorResponse { val body = res.body val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java) body.close() - log.error { "결제 실패. response: $errorResponse" } + log.error { "[TossPaymentClient.$calledBy] 요청 실패: response: $errorResponse" } return errorResponse }