[#20] 도메인별 예외 분리 #21

Merged
pricelees merged 37 commits from refactor/#20 into main 2025-07-24 02:48:53 +00:00
4 changed files with 74 additions and 67 deletions
Showing only changes of commit 498340f173 - Show all commits

View File

@ -1,10 +1,9 @@
package roomescape.payment.business package roomescape.payment.business
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType import roomescape.payment.exception.PaymentErrorCode
import roomescape.common.exception.RoomescapeException import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.client.PaymentApproveResponse import roomescape.payment.infrastructure.client.PaymentApproveResponse
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
@ -24,44 +23,45 @@ class PaymentService(
) { ) {
@Transactional @Transactional
fun createPayment( fun createPayment(
paymentResponse: PaymentApproveResponse, approveResponse: PaymentApproveResponse,
reservation: ReservationEntity reservation: ReservationEntity
): PaymentCreateResponse = PaymentEntity( ): PaymentCreateResponse {
orderId = paymentResponse.orderId, val payment = PaymentEntity(
paymentKey = paymentResponse.paymentKey, orderId = approveResponse.orderId,
totalAmount = paymentResponse.totalAmount, paymentKey = approveResponse.paymentKey,
totalAmount = approveResponse.totalAmount,
reservation = reservation, reservation = reservation,
approvedAt = paymentResponse.approvedAt approvedAt = approveResponse.approvedAt
).also { )
paymentRepository.save(it)
}.toCreateResponse() return paymentRepository.save(payment).toCreateResponse()
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun isReservationPaid( fun isReservationPaid(reservationId: Long): Boolean = paymentRepository.existsByReservationId(reservationId)
reservationId: Long
): Boolean = paymentRepository.existsByReservationId(reservationId)
@Transactional @Transactional
fun createCanceledPayment( fun createCanceledPayment(
cancelInfo: PaymentCancelResponse, cancelInfo: PaymentCancelResponse,
approvedAt: OffsetDateTime, approvedAt: OffsetDateTime,
paymentKey: String paymentKey: String
): CanceledPaymentEntity = CanceledPaymentEntity( ): CanceledPaymentEntity {
val canceledPayment = CanceledPaymentEntity(
paymentKey = paymentKey, paymentKey = paymentKey,
cancelReason = cancelInfo.cancelReason, cancelReason = cancelInfo.cancelReason,
cancelAmount = cancelInfo.cancelAmount, cancelAmount = cancelInfo.cancelAmount,
approvedAt = approvedAt, approvedAt = approvedAt,
canceledAt = cancelInfo.canceledAt canceledAt = cancelInfo.canceledAt
).also { canceledPaymentRepository.save(it) } )
return canceledPaymentRepository.save(canceledPayment)
}
@Transactional @Transactional
fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest { fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest {
val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId) val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId)
?: throw RoomescapeException( ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
ErrorType.PAYMENT_NOT_FOUND,
"[reservationId: $reservationId]",
HttpStatus.NOT_FOUND
)
// 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다. // 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다.
val canceled: CanceledPaymentEntity = cancelPayment(paymentKey) val canceled: CanceledPaymentEntity = cancelPayment(paymentKey)
@ -73,23 +73,19 @@ class PaymentService(
cancelReason: String = "고객 요청", cancelReason: String = "고객 요청",
canceledAt: OffsetDateTime = OffsetDateTime.now() canceledAt: OffsetDateTime = OffsetDateTime.now()
): CanceledPaymentEntity { ): CanceledPaymentEntity {
val paymentEntity: PaymentEntity = paymentRepository.findByPaymentKey(paymentKey) val payment: PaymentEntity = paymentRepository.findByPaymentKey(paymentKey)
?.also { paymentRepository.delete(it) } ?.also { paymentRepository.delete(it) }
?: throw RoomescapeException( ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
ErrorType.PAYMENT_NOT_FOUND,
"[paymentKey: $paymentKey]",
HttpStatus.NOT_FOUND
)
return CanceledPaymentEntity( val canceledPayment = CanceledPaymentEntity(
paymentKey = paymentKey, paymentKey = paymentKey,
cancelReason = cancelReason, cancelReason = cancelReason,
cancelAmount = paymentEntity.totalAmount, cancelAmount = payment.totalAmount,
approvedAt = paymentEntity.approvedAt, approvedAt = payment.approvedAt,
canceledAt = canceledAt canceledAt = canceledAt
).also { )
canceledPaymentRepository.save(it)
} return canceledPaymentRepository.save(canceledPayment)
} }
@Transactional @Transactional
@ -97,12 +93,8 @@ class PaymentService(
paymentKey: String, paymentKey: String,
canceledAt: OffsetDateTime canceledAt: OffsetDateTime
) { ) {
canceledPaymentRepository.findByPaymentKey(paymentKey)?.let { canceledPaymentRepository.findByPaymentKey(paymentKey)
it.canceledAt = canceledAt ?.apply { this.canceledAt = canceledAt }
} ?: throw RoomescapeException( ?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
ErrorType.PAYMENT_NOT_FOUND,
"[paymentKey: $paymentKey]",
HttpStatus.NOT_FOUND
)
} }
} }

View File

@ -0,0 +1,16 @@
package roomescape.payment.exception
import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorCode
enum class PaymentErrorCode(
override val httpStatus: HttpStatus,
override val errorCode: String,
override val message: String
) : ErrorCode {
PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "P001", "결제 정보를 찾을 수 없어요."),
CANCELED_PAYMENT_NOT_FOUND(HttpStatus.NOT_FOUND, "P002", "취소된 결제 정보를 찾을 수 없어요."),
PAYMENT_CLIENT_ERROR(HttpStatus.BAD_REQUEST, "P003", "결제에 실패했어요. 결제 수단을 확인한 후 다시 시도해주세요."),
PAYMENT_PROVIDER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "P999", "시스템에 일시적인 오류가 발생했어요. 잠시 후 다시 시도해주세요.")
}

View File

@ -0,0 +1,8 @@
package roomescape.payment.exception
import roomescape.common.exception.RoomescapeExceptionV2
class PaymentException(
override val errorCode: PaymentErrorCode,
override val message: String = errorCode.message
) : RoomescapeExceptionV2(errorCode, message)

View File

@ -11,6 +11,8 @@ import io.mockk.runs
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorType import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException import roomescape.common.exception.RoomescapeException
import roomescape.payment.exception.PaymentErrorCode
import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
import roomescape.payment.infrastructure.persistence.PaymentRepository import roomescape.payment.infrastructure.persistence.PaymentRepository
import roomescape.payment.web.PaymentCancelRequest import roomescape.payment.web.PaymentCancelRequest
@ -23,19 +25,15 @@ class PaymentServiceTest : FunSpec({
val paymentService = PaymentService(paymentRepository, canceledPaymentRepository) val paymentService = PaymentService(paymentRepository, canceledPaymentRepository)
context("cancelPaymentByAdmin") { context("createCanceledPaymentByReservationId") {
val reservationId = 1L val reservationId = 1L
test("reservationId로 paymentKey를 찾을 수 없으면 예외를 던진다.") { test("reservationId로 paymentKey를 찾을 수 없으면 예외를 던진다.") {
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
val exception = shouldThrow<RoomescapeException> { val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId) paymentService.createCanceledPaymentByReservationId(reservationId)
} }
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
} }
context("reservationId로 paymentKey를 찾고난 후") { context("reservationId로 paymentKey를 찾고난 후") {
@ -50,14 +48,10 @@ class PaymentServiceTest : FunSpec({
paymentRepository.findByPaymentKey(paymentKey) paymentRepository.findByPaymentKey(paymentKey)
} returns null } returns null
val exception = shouldThrow<RoomescapeException> { val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId) paymentService.createCanceledPaymentByReservationId(reservationId)
} }
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
} }
test("해당 paymentKey로 paymentEntity를 찾고, cancelPaymentEntity를 저장한다.") { test("해당 paymentKey로 paymentEntity를 찾고, cancelPaymentEntity를 저장한다.") {
@ -76,6 +70,7 @@ class PaymentServiceTest : FunSpec({
} returns PaymentFixture.createCanceled( } returns PaymentFixture.createCanceled(
id = 1L, id = 1L,
paymentKey = paymentKey, paymentKey = paymentKey,
cancelReason = "Test",
cancelAmount = paymentEntity.totalAmount, cancelAmount = paymentEntity.totalAmount,
) )
@ -84,7 +79,7 @@ class PaymentServiceTest : FunSpec({
assertSoftly(result) { assertSoftly(result) {
this.paymentKey shouldBe paymentKey this.paymentKey shouldBe paymentKey
this.amount shouldBe paymentEntity.totalAmount this.amount shouldBe paymentEntity.totalAmount
this.cancelReason shouldBe "고객 요청" this.cancelReason shouldBe "Test"
} }
} }
} }
@ -99,14 +94,10 @@ class PaymentServiceTest : FunSpec({
canceledPaymentRepository.findByPaymentKey(paymentKey) canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns null } returns null
val exception = shouldThrow<RoomescapeException> { val exception = shouldThrow<PaymentException> {
paymentService.updateCanceledTime(paymentKey, canceledAt) paymentService.updateCanceledTime(paymentKey, canceledAt)
} }
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
} }
test("paymentKey로 canceledPaymentEntity를 찾고, canceledAt을 업데이트한다.") { test("paymentKey로 canceledPaymentEntity를 찾고, canceledAt을 업데이트한다.") {