generated from pricelees/issue-pr-template
refactor: PaymentService 내 결제 확정 로직에 이벤트 발행 추가 및 테스트
This commit is contained in:
parent
dbd2b9fb0c
commit
c1eb1aa2b4
@ -8,9 +8,11 @@ import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
|||||||
import com.sangdol.roomescape.payment.exception.PaymentException
|
import com.sangdol.roomescape.payment.exception.PaymentException
|
||||||
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
||||||
|
import com.sangdol.roomescape.payment.mapper.toEvent
|
||||||
import com.sangdol.roomescape.payment.mapper.toResponse
|
import com.sangdol.roomescape.payment.mapper.toResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@ -24,12 +26,14 @@ class PaymentService(
|
|||||||
private val canceledPaymentRepository: CanceledPaymentRepository,
|
private val canceledPaymentRepository: CanceledPaymentRepository,
|
||||||
private val paymentWriter: PaymentWriter,
|
private val paymentWriter: PaymentWriter,
|
||||||
private val transactionExecutionUtil: TransactionExecutionUtil,
|
private val transactionExecutionUtil: TransactionExecutionUtil,
|
||||||
|
private val eventPublisher: ApplicationEventPublisher
|
||||||
) {
|
) {
|
||||||
fun requestConfirm(request: PaymentConfirmRequest): PaymentGatewayResponse {
|
fun requestConfirm(reservationId: Long, request: PaymentConfirmRequest): PaymentGatewayResponse {
|
||||||
log.info { "[requestConfirm] 결제 요청 시작: paymentKey=${request.paymentKey}" }
|
log.info { "[requestConfirm] 결제 요청 시작: paymentKey=${request.paymentKey}" }
|
||||||
try {
|
try {
|
||||||
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount).also {
|
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount).also {
|
||||||
log.info { "[requestConfirm] 결제 완료: paymentKey=${request.paymentKey}" }
|
eventPublisher.publishEvent(it.toEvent(reservationId))
|
||||||
|
log.info { "[requestConfirm] 결제 및 이벤트 발행 완료: paymentKey=${request.paymentKey}" }
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when(e) {
|
when(e) {
|
||||||
|
|||||||
@ -0,0 +1,125 @@
|
|||||||
|
package com.sangdol.roomescape.payment
|
||||||
|
|
||||||
|
import com.ninjasquad.springmockk.MockkBean
|
||||||
|
import com.sangdol.roomescape.payment.business.PaymentService
|
||||||
|
import com.sangdol.roomescape.payment.business.domain.PaymentMethod
|
||||||
|
import com.sangdol.roomescape.payment.business.domain.UserFacingPaymentErrorCode
|
||||||
|
import com.sangdol.roomescape.payment.business.event.PaymentEvent
|
||||||
|
import com.sangdol.roomescape.payment.business.event.PaymentEventListener
|
||||||
|
import com.sangdol.roomescape.payment.exception.ExternalPaymentException
|
||||||
|
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
||||||
|
import com.sangdol.roomescape.payment.exception.PaymentException
|
||||||
|
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
||||||
|
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
||||||
|
import com.sangdol.roomescape.supports.PaymentFixture
|
||||||
|
import io.kotest.assertions.assertSoftly
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.mockk.*
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
|
||||||
|
class PaymentServiceTest(
|
||||||
|
private val paymentService: PaymentService,
|
||||||
|
@MockkBean(relaxed = true) private val paymentEventListener: PaymentEventListener,
|
||||||
|
@MockkBean(relaxed = true) private val tosspayClient: TosspayClient
|
||||||
|
) : FunSpecSpringbootTest() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
afterTest {
|
||||||
|
clearAllMocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
context("결제를 승인한다.") {
|
||||||
|
val request = PaymentFixture.confirmRequest
|
||||||
|
|
||||||
|
test("결제 정상 승인 및 이벤트 발행 확인") {
|
||||||
|
val tosspayAPIResponse = PaymentFixture.confirmResponse(
|
||||||
|
paymentKey = request.paymentKey,
|
||||||
|
amount = request.amount,
|
||||||
|
orderId = request.orderId,
|
||||||
|
method = PaymentMethod.CARD
|
||||||
|
)
|
||||||
|
|
||||||
|
val paymentEventSlot = slot<PaymentEvent>()
|
||||||
|
|
||||||
|
every {
|
||||||
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
|
} returns tosspayAPIResponse
|
||||||
|
|
||||||
|
every {
|
||||||
|
paymentEventListener.handlePaymentEvent(capture(paymentEventSlot))
|
||||||
|
} just runs
|
||||||
|
|
||||||
|
paymentService.requestConfirm(12345L, request)
|
||||||
|
|
||||||
|
assertSoftly(paymentEventSlot.captured) {
|
||||||
|
this.paymentKey shouldBe request.paymentKey
|
||||||
|
this.orderId shouldBe request.orderId
|
||||||
|
this.totalAmount shouldBe request.amount
|
||||||
|
this.method shouldBe PaymentMethod.CARD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context("외부 API 요청 과정에서 예외가 발생하면, 예외를 PaymentException으로 변환한 뒤 던진다.") {
|
||||||
|
|
||||||
|
test("외부 API가 4xx 응답을 보내면 ${PaymentErrorCode.PAYMENT_CLIENT_ERROR}로 변환하여 예외를 던진다.") {
|
||||||
|
val expectedErrorCode = PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
||||||
|
val exception = ExternalPaymentException(400, "INVALID_REQUEST", "잘못된 요청입니다.")
|
||||||
|
every {
|
||||||
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
|
} throws exception
|
||||||
|
|
||||||
|
assertThrows<PaymentException> {
|
||||||
|
paymentService.requestConfirm(12345L, request)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe expectedErrorCode
|
||||||
|
it.message shouldBe expectedErrorCode.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("외부 API가 5xx 응답을 보내면 ${PaymentErrorCode.PAYMENT_PROVIDER_ERROR}로 변환하여 예외를 던진다.") {
|
||||||
|
val expectedErrorCode = PaymentErrorCode.PAYMENT_PROVIDER_ERROR
|
||||||
|
val exception = ExternalPaymentException(500, "UNKNOWN_PAYMENT_ERROR", "결제에 실패했어요. 같은 문제가 반복된다면 은행이나 카드사로 문의해주세요.")
|
||||||
|
every {
|
||||||
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
|
} throws exception
|
||||||
|
|
||||||
|
assertThrows<PaymentException> {
|
||||||
|
paymentService.requestConfirm(12345L, request)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe expectedErrorCode
|
||||||
|
it.message shouldBe expectedErrorCode.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("외부 API의 에러코드가 ${UserFacingPaymentErrorCode::class.simpleName}에 있으면 해당 예외 메시지를 담아 던진다.") {
|
||||||
|
val expectedErrorCode = PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
||||||
|
val exception = ExternalPaymentException(400, "EXCEED_MAX_CARD_INSTALLMENT_PLAN", "설정 가능한 최대 할부 개월 수를 초과했습니다.")
|
||||||
|
every {
|
||||||
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
|
} throws exception
|
||||||
|
|
||||||
|
assertThrows<PaymentException> {
|
||||||
|
paymentService.requestConfirm(12345L, request)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe expectedErrorCode
|
||||||
|
it.message shouldBe "${expectedErrorCode.message}(${exception.message})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("외부 API에서 예상치 못한 예외가 발생한 경우 ${PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR}로 변환한다.") {
|
||||||
|
val expectedErrorCode = PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR
|
||||||
|
every {
|
||||||
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
|
} throws Exception("unexpected")
|
||||||
|
|
||||||
|
assertThrows<PaymentException> {
|
||||||
|
paymentService.requestConfirm(12345L, request)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe expectedErrorCode
|
||||||
|
it.message shouldBe expectedErrorCode.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user