diff --git a/service/src/main/kotlin/com/sangdol/roomescape/order/infrastructure/persistence/PaymentAttemptRepository.kt b/service/src/main/kotlin/com/sangdol/roomescape/order/infrastructure/persistence/PaymentAttemptRepository.kt new file mode 100644 index 00000000..3c5e8666 --- /dev/null +++ b/service/src/main/kotlin/com/sangdol/roomescape/order/infrastructure/persistence/PaymentAttemptRepository.kt @@ -0,0 +1,26 @@ +package com.sangdol.roomescape.order.infrastructure.persistence + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query + +interface PaymentAttemptRepository: JpaRepository { + + fun countByReservationId(reservationId: Long): Long + + @Query( + """ + SELECT + CASE + WHEN COUNT(pa) > 0 + THEN TRUE + ELSE FALSE + END + FROM + PaymentAttemptEntity pa + WHERE + pa.reservationId = :reservationId + AND pa.result = com.sangdol.roomescape.order.infrastructure.persistence.AttemptResult.SUCCESS + """ + ) + fun isSuccessAttemptExists(reservationId: Long): Boolean +} diff --git a/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt b/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt index c980ad28..773912ea 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt @@ -1,378 +1,379 @@ -//package com.sangdol.roomescape.payment -// -//import com.ninjasquad.springmockk.MockkBean -//import com.sangdol.common.types.web.HttpStatus -//import com.sangdol.roomescape.auth.exception.AuthErrorCode -//import com.sangdol.roomescape.payment.business.PaymentService -//import com.sangdol.roomescape.payment.exception.PaymentErrorCode -//import com.sangdol.roomescape.payment.infrastructure.client.CardDetail -//import com.sangdol.roomescape.payment.infrastructure.client.EasyPayDetail -//import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient -//import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail -//import com.sangdol.roomescape.payment.infrastructure.common.* -//import com.sangdol.roomescape.payment.infrastructure.persistence.* -//import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest -//import com.sangdol.roomescape.payment.web.PaymentCreateResponse -//import com.sangdol.roomescape.supports.* -//import io.kotest.matchers.shouldBe -//import io.mockk.every -//import org.springframework.data.repository.findByIdOrNull -//import org.springframework.http.HttpMethod -// -//class PaymentAPITest( -// @MockkBean -// private val tosspayClient: TosspayClient, -// private val paymentService: PaymentService, -// private val paymentRepository: PaymentRepository, -// private val paymentDetailRepository: PaymentDetailRepository, -// private val canceledPaymentRepository: CanceledPaymentRepository -//) : FunSpecSpringbootTest() { -// init { -// context("결제를 승인한다.") { -// context("권한이 없으면 접근할 수 없다.") { -// val endpoint = "/payments?reservationId=$INVALID_PK" -// -// test("비회원") { -// runExceptionTest( -// method = HttpMethod.POST, -// endpoint = endpoint, -// expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND -// ) -// } -// -// test("관리자") { -// runExceptionTest( -// token = testAuthUtil.defaultHqAdminLogin().second, -// method = HttpMethod.POST, -// endpoint = endpoint, -// expectedErrorCode = AuthErrorCode.ACCESS_DENIED -// ) -// } -// } -// -// val amount = 100_000 -// context("간편결제 + 카드로 ${amount}원을 결제한다.") { -// context("일시불") { -// test("토스페이 + 토스뱅크카드(신용)") { -// runConfirmTest( -// amount = amount, -// cardDetail = PaymentFixture.cardDetail( -// amount = amount, -// issuerCode = CardIssuerCode.TOSS_BANK, -// cardType = CardType.CREDIT, -// ), -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = 0, -// provider = EasyPayCompanyCode.TOSSPAY -// ) -// ) -// } -// -// test("삼성페이 + 삼성카드(법인)") { -// runConfirmTest( -// amount = amount, -// cardDetail = PaymentFixture.cardDetail( -// amount = amount, -// issuerCode = CardIssuerCode.SAMSUNG, -// cardType = CardType.CREDIT, -// ownerType = CardOwnerType.CORPORATE -// ), -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = 0, -// provider = EasyPayCompanyCode.SAMSUNGPAY -// ) -// ) -// } -// } -// -// context("할부") { -// val installmentPlanMonths = 12 -// test("네이버페이 + 신한카드 / 12개월") { -// runConfirmTest( -// amount = amount, -// cardDetail = PaymentFixture.cardDetail( -// amount = amount, -// issuerCode = CardIssuerCode.SHINHAN, -// installmentPlanMonths = installmentPlanMonths -// ), -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = 0, -// provider = EasyPayCompanyCode.NAVERPAY -// ) -// ) -// } -// } -// -// context("간편결제사 포인트 일부 사용") { -// val point = (amount * 0.1).toInt() -// test("토스페이 + 국민카드(체크) / 일시불 / $point 포인트 사용") { -// runConfirmTest( -// amount = amount, -// cardDetail = PaymentFixture.cardDetail( -// amount = (amount - point), -// issuerCode = CardIssuerCode.KOOKMIN, -// cardType = CardType.CHECK -// ), -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = 0, -// provider = EasyPayCompanyCode.TOSSPAY, -// discountAmount = point -// ) -// ) -// } -// } -// } -// -// context("간편결제사의 선불 충전금액으로 ${amount}원을 결제한다.") { -// test("토스페이 + 토스페이머니 / 전액") { -// runConfirmTest( -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = amount, -// provider = EasyPayCompanyCode.TOSSPAY -// ) -// ) -// } -// -// val point = (amount * 0.05).toInt() -// -// test("카카오페이 + 카카오페이머니 / $point 사용") { -// runConfirmTest( -// easyPayDetail = PaymentFixture.easypayDetail( -// amount = (amount - point), -// provider = EasyPayCompanyCode.KAKAOPAY, -// discountAmount = point -// ) -// ) -// } -// } -// -// context("계좌이체로 결제한다.") { -// test("토스뱅크") { -// runConfirmTest( -// transferDetail = PaymentFixture.transferDetail(bankCode = BankCode.TOSS_BANK) -// ) -// } -// } -// -// context("지원하지 않는 결제수단으로 요청시 실패한다.") { -// val supportedMethod = listOf( -// PaymentMethod.CARD, -// PaymentMethod.EASY_PAY, -// PaymentMethod.TRANSFER, -// ) -// -// PaymentMethod.entries.filter { it !in supportedMethod }.forEach { -// test("결제 수단: ${it.koreanName}") { -// val (user, token) = testAuthUtil.defaultUserLogin() -// val reservation = dummyInitializer.createConfirmReservation(user = user) -// -// val request = PaymentFixture.confirmRequest -// -// every { -// tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) -// } returns PaymentFixture.confirmResponse( -// paymentKey = request.paymentKey, -// amount = request.amount, -// method = it, -// cardDetail = null, -// easyPayDetail = null, -// transferDetail = null, -// ) -// -// runExceptionTest( -// token = token, -// method = HttpMethod.POST, -// endpoint = "/payments?reservationId=${reservation.id}", -// requestBody = PaymentFixture.confirmRequest, -// expectedErrorCode = PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE -// ) -// } -// } -// } -// } -// -// context("결제를 취소한다.") { -// context("권한이 없으면 접근할 수 없다.") { -// val endpoint = "/payments/cancel" -// -// test("비회원") { -// runExceptionTest( -// method = HttpMethod.POST, -// endpoint = endpoint, -// requestBody = PaymentFixture.cancelRequest, -// expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND -// ) -// } -// -// test("관리자") { -// runExceptionTest( -// token = testAuthUtil.defaultHqAdminLogin().second, -// method = HttpMethod.POST, -// endpoint = endpoint, -// requestBody = PaymentFixture.cancelRequest, -// expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND -// ) -// } -// } -// -// test("정상 취소") { -// val (user, token) = testAuthUtil.defaultUserLogin() -// val reservation = dummyInitializer.createConfirmReservation(user = user) -// val confirmRequest = PaymentFixture.confirmRequest -// -// val paymentCreateResponse = createPayment( -// request = confirmRequest, -// reservationId = reservation.id -// ) -// -// every { -// tosspayClient.cancel( -// confirmRequest.paymentKey, -// confirmRequest.amount, -// cancelReason = "cancelReason" -// ) -// } returns PaymentFixture.cancelResponse(confirmRequest.amount) -// -// val requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id) -// -// runTest( -// token = token, -// using = { -// body(requestBody) -// }, -// on = { -// post("/payments/cancel") -// }, -// expect = { -// statusCode(HttpStatus.OK.value()) -// } -// ).also { -// val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId) -// ?: throw AssertionError("Unexpected Exception Occurred.") -// val canceledPayment = -// canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId) -// ?: throw AssertionError("Unexpected Exception Occurred.") -// -// payment.status shouldBe PaymentStatus.CANCELED -// canceledPayment.paymentId shouldBe payment.id -// canceledPayment.cancelAmount shouldBe payment.totalAmount -// } -// } -// -// test("예약에 대한 결제 정보가 없으면 실패한다.") { -// val (user, token) = testAuthUtil.defaultUserLogin() -// val reservation = dummyInitializer.createConfirmReservation(user = user) -// -// runExceptionTest( -// token = token, -// method = HttpMethod.POST, -// endpoint = "/payments/cancel", -// requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id), -// expectedErrorCode = PaymentErrorCode.PAYMENT_NOT_FOUND -// ) -// } -// } -// } -// -// private fun createPayment( -// request: PaymentConfirmRequest, -// reservationId: Long, -// ): PaymentCreateResponse { -// every { -// tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) -// } returns PaymentFixture.confirmResponse( -// request.paymentKey, -// request.amount, -// method = PaymentMethod.CARD, -// cardDetail = PaymentFixture.cardDetail(request.amount), -// easyPayDetail = null, -// transferDetail = null, -// ) -// -// return paymentService.confirm(reservationId, request) -// } -// -// fun runConfirmTest( -// cardDetail: CardDetail? = null, -// easyPayDetail: EasyPayDetail? = null, -// transferDetail: TransferDetail? = null, -// paymentKey: String = "paymentKey", -// amount: Int = 10000, -// ) { -// val (user, token) = testAuthUtil.defaultUserLogin() -// val reservation = dummyInitializer.createConfirmReservation(user = user) -// val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount) -// -// val method = if (easyPayDetail != null) { -// PaymentMethod.EASY_PAY -// } else if (cardDetail != null) { -// PaymentMethod.CARD -// } else if (transferDetail != null) { -// PaymentMethod.TRANSFER -// } else { -// throw AssertionError("결제타입 확인 필요.") -// } -// -// val clientResponse = PaymentFixture.confirmResponse( -// paymentKey, amount, method, cardDetail, easyPayDetail, transferDetail -// ) -// -// every { -// tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) -// } returns clientResponse -// -// runTest( -// token = token, -// using = { -// body(request) -// }, -// on = { -// post("/payments?reservationId=${reservation.id}") -// }, -// expect = { -// statusCode(HttpStatus.OK.value()) -// } -// ).also { -// val createdPayment = paymentRepository.findByIdOrNull(it.extract().path("data.paymentId")) -// ?: throw AssertionError("Unexpected Exception Occurred.") -// val createdPaymentDetail = -// paymentDetailRepository.findByIdOrNull(it.extract().path("data.detailId")) -// ?: throw AssertionError("Unexpected Exception Occurred.") -// -// createdPayment.status shouldBe clientResponse.status -// createdPayment.method shouldBe clientResponse.method -// createdPayment.reservationId shouldBe reservation.id -// -// when (createdPaymentDetail) { -// is PaymentCardDetailEntity -> { -// createdPaymentDetail.issuerCode shouldBe clientResponse.card!!.issuerCode -// createdPaymentDetail.cardType shouldBe clientResponse.card.cardType -// createdPaymentDetail.cardNumber shouldBe clientResponse.card.number -// createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount -// createdPaymentDetail.vat shouldBe clientResponse.vat -// createdPaymentDetail.amount shouldBe (clientResponse.totalAmount - (clientResponse.easyPay?.discountAmount -// ?: 0)) -// clientResponse.easyPay?.let { easypay -> -// createdPaymentDetail.easypayProviderCode shouldBe easypay.provider -// createdPaymentDetail.easypayDiscountAmount shouldBe easypay.discountAmount -// } -// } -// -// is PaymentBankTransferDetailEntity -> { -// createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount -// createdPaymentDetail.vat shouldBe clientResponse.vat -// createdPaymentDetail.bankCode shouldBe clientResponse.transfer!!.bankCode -// createdPaymentDetail.settlementStatus shouldBe clientResponse.transfer.settlementStatus -// } -// -// is PaymentEasypayPrepaidDetailEntity -> { -// createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount -// createdPaymentDetail.vat shouldBe clientResponse.vat -// createdPaymentDetail.easypayProviderCode shouldBe clientResponse.easyPay!!.provider -// createdPaymentDetail.amount shouldBe clientResponse.easyPay.amount -// createdPaymentDetail.discountAmount shouldBe clientResponse.easyPay.discountAmount -// } -// } -// } -// } -//} +package com.sangdol.roomescape.payment + +import com.ninjasquad.springmockk.MockkBean +import com.sangdol.common.types.web.HttpStatus +import com.sangdol.roomescape.auth.exception.AuthErrorCode +import com.sangdol.roomescape.payment.business.PaymentService +import com.sangdol.roomescape.payment.business.domain.* +import com.sangdol.roomescape.payment.dto.* +import com.sangdol.roomescape.payment.exception.ExternalPaymentException +import com.sangdol.roomescape.payment.exception.PaymentErrorCode +import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient +import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentRepository +import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentRepository +import com.sangdol.roomescape.supports.FunSpecSpringbootTest +import com.sangdol.roomescape.supports.PaymentFixture +import com.sangdol.roomescape.supports.runExceptionTest +import com.sangdol.roomescape.supports.runTest +import io.kotest.matchers.shouldBe +import io.mockk.clearMocks +import io.mockk.every +import org.hamcrest.CoreMatchers.containsString +import org.hamcrest.CoreMatchers.equalTo +import org.springframework.data.repository.findByIdOrNull +import org.springframework.http.HttpMethod + +class PaymentAPITest( + @MockkBean + private val tosspayClient: TosspayClient, + private val paymentService: PaymentService, + private val paymentRepository: PaymentRepository, + private val canceledPaymentRepository: CanceledPaymentRepository +) : FunSpecSpringbootTest() { + init { + context("결제를 승인한다.") { + context("권한이 없으면 접근할 수 없다.") { + val endpoint = "/payments/confirm" + + test("비회원") { + runExceptionTest( + method = HttpMethod.POST, + endpoint = endpoint, + expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND + ) + } + + test("관리자") { + runExceptionTest( + token = testAuthUtil.defaultHqAdminLogin().second, + method = HttpMethod.POST, + endpoint = endpoint, + expectedErrorCode = AuthErrorCode.ACCESS_DENIED + ) + } + } + + val amount = 100_000 + context("간편결제 + 카드로 ${amount}원을 결제한다.") { + context("일시불") { + test("토스페이 + 토스뱅크카드(신용)") { + runConfirmTest( + amount = amount, + cardDetail = PaymentFixture.cardDetail( + amount = amount, + issuerCode = CardIssuerCode.TOSS_BANK, + cardType = CardType.CREDIT, + ), + easyPayDetail = PaymentFixture.easypayDetail( + amount = 0, + provider = EasyPayCompanyCode.TOSSPAY + ) + ) + } + + test("삼성페이 + 삼성카드(법인)") { + runConfirmTest( + amount = amount, + cardDetail = PaymentFixture.cardDetail( + amount = amount, + issuerCode = CardIssuerCode.SAMSUNG, + cardType = CardType.CREDIT, + ownerType = CardOwnerType.CORPORATE + ), + easyPayDetail = PaymentFixture.easypayDetail( + amount = 0, + provider = EasyPayCompanyCode.SAMSUNGPAY + ) + ) + } + } + + context("할부") { + val installmentPlanMonths = 12 + test("네이버페이 + 신한카드 / 12개월") { + runConfirmTest( + amount = amount, + cardDetail = PaymentFixture.cardDetail( + amount = amount, + issuerCode = CardIssuerCode.SHINHAN, + installmentPlanMonths = installmentPlanMonths + ), + easyPayDetail = PaymentFixture.easypayDetail( + amount = 0, + provider = EasyPayCompanyCode.NAVERPAY + ) + ) + } + } + + context("간편결제사 포인트 일부 사용") { + val point = (amount * 0.1).toInt() + test("토스페이 + 국민카드(체크) / 일시불 / $point 포인트 사용") { + runConfirmTest( + amount = amount, + cardDetail = PaymentFixture.cardDetail( + amount = (amount - point), + issuerCode = CardIssuerCode.KOOKMIN, + cardType = CardType.CHECK + ), + easyPayDetail = PaymentFixture.easypayDetail( + amount = 0, + provider = EasyPayCompanyCode.TOSSPAY, + discountAmount = point + ) + ) + } + } + } + + context("간편결제사의 선불 충전금액으로 ${amount}원을 결제한다.") { + test("토스페이 + 토스페이머니 / 전액") { + runConfirmTest( + easyPayDetail = PaymentFixture.easypayDetail( + amount = amount, + provider = EasyPayCompanyCode.TOSSPAY + ) + ) + } + + val point = (amount * 0.05).toInt() + + test("카카오페이 + 카카오페이머니 / $point 사용") { + runConfirmTest( + easyPayDetail = PaymentFixture.easypayDetail( + amount = (amount - point), + provider = EasyPayCompanyCode.KAKAOPAY, + discountAmount = point + ) + ) + } + } + + context("계좌이체로 결제한다.") { + test("토스뱅크") { + runConfirmTest( + transferDetail = PaymentFixture.transferDetail(bankCode = BankCode.TOSS_BANK) + ) + } + } + + context("결제 처리중 오류가 발생한다.") { + lateinit var token: String + val commonRequest = PaymentFixture.confirmRequest + + beforeTest { + token = testAuthUtil.defaultUserLogin().second + } + + afterTest { + clearMocks(tosspayClient) + } + + test("예외 코드가 UserFacingPaymentErrorCode에 있으면 결제 실패 메시지를 같이 담는다.") { + val statusCode = HttpStatus.BAD_REQUEST.value() + val message = "거래금액 한도를 초과했습니다." + + every { + tosspayClient.confirm(commonRequest.paymentKey, commonRequest.orderId, commonRequest.amount) + } throws ExternalPaymentException( + httpStatusCode = statusCode, + errorCode = UserFacingPaymentErrorCode.EXCEED_MAX_AMOUNT.name, + message = message + ) + + runTest( + token = token, + using = { + body(commonRequest) + }, + on = { + post("/payments/confirm") + }, + expect = { + statusCode(statusCode) + body("code", equalTo(PaymentErrorCode.PAYMENT_CLIENT_ERROR.errorCode)) + body("message", containsString(message)) + } + ) + } + + context("예외 코드가 UserFacingPaymentErrorCode에 없으면 Client의 상태 코드에 따라 다르게 처리한다.") { + mapOf( + HttpStatus.BAD_REQUEST.value() to PaymentErrorCode.PAYMENT_CLIENT_ERROR, + HttpStatus.INTERNAL_SERVER_ERROR.value() to PaymentErrorCode.PAYMENT_PROVIDER_ERROR + ).forEach { (statusCode, expectedErrorCode) -> + test("statusCode=${statusCode}") { + val message = "잘못된 시크릿키 연동 정보 입니다." + + every { + tosspayClient.confirm(commonRequest.paymentKey, commonRequest.orderId, commonRequest.amount) + } throws ExternalPaymentException( + httpStatusCode = statusCode, + errorCode = "INVALID_API_KEY", + message = message + ) + + runTest( + token = token, + using = { + body(commonRequest) + }, + on = { + post("/payments/confirm") + }, + expect = { + statusCode(statusCode) + body("code", equalTo(expectedErrorCode.errorCode)) + body("message", equalTo(expectedErrorCode.message)) + } + ) + } + } + } + } + } + + context("결제를 취소한다.") { + context("권한이 없으면 접근할 수 없다.") { + val endpoint = "/payments/cancel" + + test("비회원") { + runExceptionTest( + method = HttpMethod.POST, + endpoint = endpoint, + requestBody = PaymentFixture.cancelRequest, + expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND + ) + } + + test("관리자") { + runExceptionTest( + token = testAuthUtil.defaultHqAdminLogin().second, + method = HttpMethod.POST, + endpoint = endpoint, + requestBody = PaymentFixture.cancelRequest, + expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND + ) + } + } + + test("정상 취소") { + val (user, token) = testAuthUtil.defaultUserLogin() + val reservation = dummyInitializer.createConfirmReservation(user = user) + val confirmRequest = PaymentFixture.confirmRequest + + val paymentCreateResponse = createPayment( + request = confirmRequest, + reservationId = reservation.id + ) + + every { + tosspayClient.cancel( + confirmRequest.paymentKey, + confirmRequest.amount, + cancelReason = "cancelReason" + ) + } returns PaymentFixture.cancelResponse(confirmRequest.amount) + + val requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id) + + runTest( + token = token, + using = { + body(requestBody) + }, + on = { + post("/payments/cancel") + }, + expect = { + statusCode(HttpStatus.OK.value()) + } + ).also { + val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId) + ?: throw AssertionError("Unexpected Exception Occurred.") + val canceledPayment = + canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId) + ?: throw AssertionError("Unexpected Exception Occurred.") + + payment.status shouldBe PaymentStatus.CANCELED + canceledPayment.paymentId shouldBe payment.id + canceledPayment.cancelAmount shouldBe payment.totalAmount + } + } + + test("예약에 대한 결제 정보가 없으면 실패한다.") { + val (user, token) = testAuthUtil.defaultUserLogin() + val reservation = dummyInitializer.createConfirmReservation(user = user) + + runExceptionTest( + token = token, + method = HttpMethod.POST, + endpoint = "/payments/cancel", + requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id), + expectedErrorCode = PaymentErrorCode.PAYMENT_NOT_FOUND + ) + } + } + } + + private fun createPayment( + request: PaymentConfirmRequest, + reservationId: Long, + ): PaymentCreateResponse { + every { + tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) + } returns PaymentFixture.confirmResponse( + request.paymentKey, + request.amount, + method = PaymentMethod.CARD, + cardDetail = PaymentFixture.cardDetail(request.amount), + easyPayDetail = null, + transferDetail = null, + ) + + val paymentResponse = paymentService.requestConfirm(request) + return paymentService.savePayment(reservationId, paymentResponse) + } + + fun runConfirmTest( + cardDetail: CardDetailResponse? = null, + easyPayDetail: EasyPayDetailResponse? = null, + transferDetail: TransferDetailResponse? = null, + paymentKey: String = "paymentKey", + amount: Int = 10000, + ) { + val token = testAuthUtil.defaultUserLogin().second + val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount) + + val method = if (easyPayDetail != null) { + PaymentMethod.EASY_PAY + } else if (cardDetail != null) { + PaymentMethod.CARD + } else if (transferDetail != null) { + PaymentMethod.TRANSFER + } else { + throw AssertionError("결제타입 확인 필요.") + } + + val clientResponse = PaymentFixture.confirmResponse( + paymentKey, amount, method, cardDetail, easyPayDetail, transferDetail + ) + + every { + tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) + } returns clientResponse + + runTest( + token = token, + using = { + body(request) + }, + on = { + post("/payments/confirm") + }, + expect = { + statusCode(HttpStatus.OK.value()) + } + ) + } +}