[#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
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.payment.exception.PaymentErrorCode
import roomescape.payment.exception.PaymentException
import roomescape.payment.infrastructure.client.PaymentApproveResponse
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
@ -24,44 +23,45 @@ class PaymentService(
) {
@Transactional
fun createPayment(
paymentResponse: PaymentApproveResponse,
approveResponse: PaymentApproveResponse,
reservation: ReservationEntity
): PaymentCreateResponse = PaymentEntity(
orderId = paymentResponse.orderId,
paymentKey = paymentResponse.paymentKey,
totalAmount = paymentResponse.totalAmount,
reservation = reservation,
approvedAt = paymentResponse.approvedAt
).also {
paymentRepository.save(it)
}.toCreateResponse()
): PaymentCreateResponse {
val payment = PaymentEntity(
orderId = approveResponse.orderId,
paymentKey = approveResponse.paymentKey,
totalAmount = approveResponse.totalAmount,
reservation = reservation,
approvedAt = approveResponse.approvedAt
)
return paymentRepository.save(payment).toCreateResponse()
}
@Transactional(readOnly = true)
fun isReservationPaid(
reservationId: Long
): Boolean = paymentRepository.existsByReservationId(reservationId)
fun isReservationPaid(reservationId: Long): Boolean = paymentRepository.existsByReservationId(reservationId)
@Transactional
fun createCanceledPayment(
cancelInfo: PaymentCancelResponse,
approvedAt: OffsetDateTime,
paymentKey: String
): CanceledPaymentEntity = CanceledPaymentEntity(
paymentKey = paymentKey,
cancelReason = cancelInfo.cancelReason,
cancelAmount = cancelInfo.cancelAmount,
approvedAt = approvedAt,
canceledAt = cancelInfo.canceledAt
).also { canceledPaymentRepository.save(it) }
): CanceledPaymentEntity {
val canceledPayment = CanceledPaymentEntity(
paymentKey = paymentKey,
cancelReason = cancelInfo.cancelReason,
cancelAmount = cancelInfo.cancelAmount,
approvedAt = approvedAt,
canceledAt = cancelInfo.canceledAt
)
return canceledPaymentRepository.save(canceledPayment)
}
@Transactional
fun createCanceledPaymentByReservationId(reservationId: Long): PaymentCancelRequest {
val paymentKey: String = paymentRepository.findPaymentKeyByReservationId(reservationId)
?: throw RoomescapeException(
ErrorType.PAYMENT_NOT_FOUND,
"[reservationId: $reservationId]",
HttpStatus.NOT_FOUND
)
?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
// 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다.
val canceled: CanceledPaymentEntity = cancelPayment(paymentKey)
@ -73,23 +73,19 @@ class PaymentService(
cancelReason: String = "고객 요청",
canceledAt: OffsetDateTime = OffsetDateTime.now()
): CanceledPaymentEntity {
val paymentEntity: PaymentEntity = paymentRepository.findByPaymentKey(paymentKey)
val payment: PaymentEntity = paymentRepository.findByPaymentKey(paymentKey)
?.also { paymentRepository.delete(it) }
?: throw RoomescapeException(
ErrorType.PAYMENT_NOT_FOUND,
"[paymentKey: $paymentKey]",
HttpStatus.NOT_FOUND
)
?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
return CanceledPaymentEntity(
val canceledPayment = CanceledPaymentEntity(
paymentKey = paymentKey,
cancelReason = cancelReason,
cancelAmount = paymentEntity.totalAmount,
approvedAt = paymentEntity.approvedAt,
cancelAmount = payment.totalAmount,
approvedAt = payment.approvedAt,
canceledAt = canceledAt
).also {
canceledPaymentRepository.save(it)
}
)
return canceledPaymentRepository.save(canceledPayment)
}
@Transactional
@ -97,12 +93,8 @@ class PaymentService(
paymentKey: String,
canceledAt: OffsetDateTime
) {
canceledPaymentRepository.findByPaymentKey(paymentKey)?.let {
it.canceledAt = canceledAt
} ?: throw RoomescapeException(
ErrorType.PAYMENT_NOT_FOUND,
"[paymentKey: $paymentKey]",
HttpStatus.NOT_FOUND
)
canceledPaymentRepository.findByPaymentKey(paymentKey)
?.apply { this.canceledAt = canceledAt }
?: throw PaymentException(PaymentErrorCode.PAYMENT_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 roomescape.common.exception.ErrorType
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.PaymentRepository
import roomescape.payment.web.PaymentCancelRequest
@ -23,19 +25,15 @@ class PaymentServiceTest : FunSpec({
val paymentService = PaymentService(paymentRepository, canceledPaymentRepository)
context("cancelPaymentByAdmin") {
context("createCanceledPaymentByReservationId") {
val reservationId = 1L
test("reservationId로 paymentKey를 찾을 수 없으면 예외를 던진다.") {
every { paymentRepository.findPaymentKeyByReservationId(reservationId) } returns null
val exception = shouldThrow<RoomescapeException> {
val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId)
}
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
context("reservationId로 paymentKey를 찾고난 후") {
@ -50,14 +48,10 @@ class PaymentServiceTest : FunSpec({
paymentRepository.findByPaymentKey(paymentKey)
} returns null
val exception = shouldThrow<RoomescapeException> {
val exception = shouldThrow<PaymentException> {
paymentService.createCanceledPaymentByReservationId(reservationId)
}
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
test("해당 paymentKey로 paymentEntity를 찾고, cancelPaymentEntity를 저장한다.") {
@ -76,6 +70,7 @@ class PaymentServiceTest : FunSpec({
} returns PaymentFixture.createCanceled(
id = 1L,
paymentKey = paymentKey,
cancelReason = "Test",
cancelAmount = paymentEntity.totalAmount,
)
@ -84,7 +79,7 @@ class PaymentServiceTest : FunSpec({
assertSoftly(result) {
this.paymentKey shouldBe paymentKey
this.amount shouldBe paymentEntity.totalAmount
this.cancelReason shouldBe "고객 요청"
this.cancelReason shouldBe "Test"
}
}
}
@ -99,14 +94,10 @@ class PaymentServiceTest : FunSpec({
canceledPaymentRepository.findByPaymentKey(paymentKey)
} returns null
val exception = shouldThrow<RoomescapeException> {
val exception = shouldThrow<PaymentException> {
paymentService.updateCanceledTime(paymentKey, canceledAt)
}
assertSoftly(exception) {
this.errorType shouldBe ErrorType.PAYMENT_NOT_FOUND
this.httpStatus shouldBe HttpStatus.NOT_FOUND
}
exception.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
}
test("paymentKey로 canceledPaymentEntity를 찾고, canceledAt을 업데이트한다.") {