From c3330e5652df6dddcf8fb68ba0f1c35683926615 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 16 Oct 2025 13:55:13 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EA=B2=B0=EC=A0=9C=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=ED=99=94=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20?= =?UTF-8?q?API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 결제 확정 API 및 테스트 제거(취소는 유지) - PaymentWriter 제거 - 테스트 코드 반영 --- .../payment/business/PaymentService.kt | 34 +-- .../payment/business/PaymentWriter.kt | 80 ----- .../roomescape/payment/docs/PaymentAPI.kt | 10 - .../PaymentClientDTOMappingExtensions.kt | 74 +---- .../payment/web/PaymentController.kt | 17 +- .../roomescape/payment/PaymentAPITest.kt | 275 ++---------------- .../roomescape/supports/DummyInitializer.kt | 55 ++-- .../roomescape/supports/KotestConfig.kt | 33 ++- 8 files changed, 73 insertions(+), 505 deletions(-) delete mode 100644 service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentWriter.kt diff --git a/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentService.kt b/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentService.kt index 0f5ff705..3998f46e 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentService.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentService.kt @@ -1,5 +1,6 @@ package com.sangdol.roomescape.payment.business +import com.sangdol.common.persistence.IDGenerator import com.sangdol.common.persistence.TransactionExecutionUtil import com.sangdol.roomescape.payment.business.domain.UserFacingPaymentErrorCode import com.sangdol.roomescape.payment.dto.* @@ -8,6 +9,7 @@ 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.payment.infrastructure.persistence.* +import com.sangdol.roomescape.payment.mapper.toEntity import com.sangdol.roomescape.payment.mapper.toEvent import com.sangdol.roomescape.payment.mapper.toResponse import io.github.oshai.kotlinlogging.KLogger @@ -20,11 +22,11 @@ private val log: KLogger = KotlinLogging.logger {} @Service class PaymentService( + private val idGenerator: IDGenerator, private val paymentClient: TosspayClient, private val paymentRepository: PaymentRepository, private val paymentDetailRepository: PaymentDetailRepository, private val canceledPaymentRepository: CanceledPaymentRepository, - private val paymentWriter: PaymentWriter, private val transactionExecutionUtil: TransactionExecutionUtil, private val eventPublisher: ApplicationEventPublisher ) { @@ -60,19 +62,6 @@ class PaymentService( } } - fun savePayment( - reservationId: Long, - paymentGatewayResponse: PaymentGatewayResponse - ): PaymentCreateResponse { - val payment: PaymentEntity = paymentWriter.createPayment( - reservationId = reservationId, - paymentGatewayResponse = paymentGatewayResponse - ) - val detail: PaymentDetailEntity = paymentWriter.createDetail(paymentGatewayResponse, payment.id) - - return PaymentCreateResponse(paymentId = payment.id, detailId = detail.id) - } - fun cancel(userId: Long, request: PaymentCancelRequest) { val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId) @@ -83,12 +72,17 @@ class PaymentService( ) transactionExecutionUtil.withNewTransaction(isReadOnly = false) { - paymentWriter.cancel( - userId = userId, - payment = payment, - requestedAt = request.requestedAt, - cancelResponse = clientCancelResponse - ) + val payment = findByReservationIdOrThrow(request.reservationId).apply { this.cancel() } + + clientCancelResponse.cancels.toEntity( + id = idGenerator.create(), + paymentId = payment.id, + cancelRequestedAt = request.requestedAt, + canceledBy = userId + ).also { + canceledPaymentRepository.save(it) + log.debug { "[cancel] 결제 취소 정보 저장 완료: payment.id=${payment.id}" } + } }.also { log.info { "[cancel] 결제 취소 완료: paymentId=${payment.id}" } } diff --git a/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentWriter.kt b/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentWriter.kt deleted file mode 100644 index 948e9b4b..00000000 --- a/service/src/main/kotlin/com/sangdol/roomescape/payment/business/PaymentWriter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.sangdol.roomescape.payment.business - -import com.sangdol.common.persistence.IDGenerator -import com.sangdol.roomescape.payment.exception.PaymentErrorCode -import com.sangdol.roomescape.payment.exception.PaymentException -import com.sangdol.roomescape.payment.business.domain.PaymentMethod -import com.sangdol.roomescape.payment.dto.PaymentGatewayCancelResponse -import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse -import com.sangdol.roomescape.payment.mapper.toCardDetailEntity -import com.sangdol.roomescape.payment.mapper.toEasypayPrepaidDetailEntity -import com.sangdol.roomescape.payment.mapper.toEntity -import com.sangdol.roomescape.payment.mapper.toTransferDetailEntity -import com.sangdol.roomescape.payment.infrastructure.persistence.* -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.stereotype.Component -import java.time.Instant - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class PaymentWriter( - private val paymentRepository: PaymentRepository, - private val paymentDetailRepository: PaymentDetailRepository, - private val canceledPaymentRepository: CanceledPaymentRepository, - private val idGenerator: IDGenerator, -) { - - fun createPayment( - reservationId: Long, - paymentGatewayResponse: PaymentGatewayResponse - ): PaymentEntity { - log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 시작: reservationId=${reservationId}, paymentKey=${paymentGatewayResponse.paymentKey}" } - - return paymentGatewayResponse.toEntity(id = idGenerator.create(), reservationId = reservationId).also { - paymentRepository.save(it) - log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 완료: reservationId=${reservationId}, payment.id=${it.id}" } - } - } - - fun createDetail( - paymentGatewayResponse: PaymentGatewayResponse, - paymentId: Long, - ): PaymentDetailEntity { - val method: PaymentMethod = paymentGatewayResponse.method - val id = idGenerator.create() - - if (method == PaymentMethod.TRANSFER) { - return paymentDetailRepository.save(paymentGatewayResponse.toTransferDetailEntity(id, paymentId)) - } - if (method == PaymentMethod.EASY_PAY && paymentGatewayResponse.card == null) { - return paymentDetailRepository.save(paymentGatewayResponse.toEasypayPrepaidDetailEntity(id, paymentId)) - } - if (paymentGatewayResponse.card != null) { - return paymentDetailRepository.save(paymentGatewayResponse.toCardDetailEntity(id, paymentId)) - } - throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE) - } - - fun cancel( - userId: Long, - payment: PaymentEntity, - requestedAt: Instant, - cancelResponse: PaymentGatewayCancelResponse - ): CanceledPaymentEntity { - log.debug { "[PaymentWriterV2.cancelPayment] 결제 취소 정보 저장 시작: payment.id=${payment.id}" } - - paymentRepository.save(payment.apply { this.cancel() }) - - return cancelResponse.cancels.toEntity( - id = idGenerator.create(), - paymentId = payment.id, - cancelRequestedAt = requestedAt, - canceledBy = userId - ).also { - canceledPaymentRepository.save(it) - log.debug { "[PaymentWriterV2.cancelPayment] 결제 취소 정보 저장 완료: payment.id=${payment.id}" } - } - } -} diff --git a/service/src/main/kotlin/com/sangdol/roomescape/payment/docs/PaymentAPI.kt b/service/src/main/kotlin/com/sangdol/roomescape/payment/docs/PaymentAPI.kt index 5f7160b8..682e35e2 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/payment/docs/PaymentAPI.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/payment/docs/PaymentAPI.kt @@ -2,11 +2,8 @@ package com.sangdol.roomescape.payment.docs import com.sangdol.common.types.web.CommonApiResponse import com.sangdol.roomescape.auth.web.support.User -import com.sangdol.roomescape.auth.web.support.UserOnly import com.sangdol.roomescape.common.types.CurrentUserContext -import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse import com.sangdol.roomescape.payment.dto.PaymentCancelRequest -import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses @@ -16,13 +13,6 @@ import org.springframework.web.bind.annotation.RequestBody interface PaymentAPI { - @UserOnly - @Operation(summary = "결제 승인") - @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) - fun confirmPayment( - @Valid @RequestBody request: PaymentConfirmRequest - ): ResponseEntity> - @Operation(summary = "결제 취소") @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) fun cancelPayment( diff --git a/service/src/main/kotlin/com/sangdol/roomescape/payment/mapper/PaymentClientDTOMappingExtensions.kt b/service/src/main/kotlin/com/sangdol/roomescape/payment/mapper/PaymentClientDTOMappingExtensions.kt index e5794fd0..e88c9da4 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/payment/mapper/PaymentClientDTOMappingExtensions.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/payment/mapper/PaymentClientDTOMappingExtensions.kt @@ -6,79 +6,9 @@ import com.sangdol.roomescape.payment.dto.CancelDetail import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse import com.sangdol.roomescape.payment.exception.PaymentErrorCode import com.sangdol.roomescape.payment.exception.PaymentException -import com.sangdol.roomescape.payment.infrastructure.persistence.* +import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity import java.time.Instant -fun PaymentGatewayResponse.toEntity( - id: Long, - reservationId: Long, -) = PaymentEntity( - id = id, - reservationId = reservationId, - paymentKey = this.paymentKey, - orderId = this.orderId, - totalAmount = this.totalAmount, - requestedAt = this.requestedAt.toInstant(), - approvedAt = this.approvedAt.toInstant(), - type = this.type, - method = this.method, - status = this.status, -) - -fun PaymentGatewayResponse.toCardDetailEntity(id: Long, paymentId: Long): PaymentCardDetailEntity { - val cardDetail = this.card ?: throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR) - - return PaymentCardDetailEntity( - id = id, - paymentId = paymentId, - suppliedAmount = this.suppliedAmount, - vat = this.vat, - issuerCode = cardDetail.issuerCode, - cardType = cardDetail.cardType, - ownerType = cardDetail.ownerType, - amount = cardDetail.amount, - cardNumber = cardDetail.number, - approvalNumber = cardDetail.approveNo, - installmentPlanMonths = cardDetail.installmentPlanMonths, - isInterestFree = cardDetail.isInterestFree, - easypayProviderCode = this.easyPay?.provider, - easypayDiscountAmount = this.easyPay?.discountAmount, - ) -} - -fun PaymentGatewayResponse.toEasypayPrepaidDetailEntity( - id: Long, - paymentId: Long -): PaymentEasypayPrepaidDetailEntity { - val easyPayDetail = this.easyPay ?: throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR) - - return PaymentEasypayPrepaidDetailEntity( - id = id, - paymentId = paymentId, - suppliedAmount = this.suppliedAmount, - vat = this.vat, - easypayProviderCode = easyPayDetail.provider, - amount = easyPayDetail.amount, - discountAmount = easyPayDetail.discountAmount - ) -} - -fun PaymentGatewayResponse.toTransferDetailEntity( - id: Long, - paymentId: Long -): PaymentBankTransferDetailEntity { - val transferDetail = this.transfer ?: throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR) - - return PaymentBankTransferDetailEntity( - id = id, - paymentId = paymentId, - suppliedAmount = this.suppliedAmount, - vat = this.vat, - bankCode = transferDetail.bankCode, - settlementStatus = transferDetail.settlementStatus - ) -} - fun CancelDetail.toEntity( id: Long, paymentId: Long, @@ -115,7 +45,7 @@ fun PaymentGatewayResponse.toEvent(reservationId: Long): PaymentEvent { } fun PaymentGatewayResponse.toDetail(): PaymentDetail { - return when(this.method) { + return when (this.method) { PaymentMethod.TRANSFER -> this.toBankTransferDetail() PaymentMethod.CARD -> this.toCardDetail() PaymentMethod.EASY_PAY -> { diff --git a/service/src/main/kotlin/com/sangdol/roomescape/payment/web/PaymentController.kt b/service/src/main/kotlin/com/sangdol/roomescape/payment/web/PaymentController.kt index 9f2f36b2..0eae2eee 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/payment/web/PaymentController.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/payment/web/PaymentController.kt @@ -6,27 +6,18 @@ import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.payment.business.PaymentService import com.sangdol.roomescape.payment.docs.PaymentAPI import com.sangdol.roomescape.payment.dto.PaymentCancelRequest -import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest -import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse import jakarta.validation.Valid import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/payments") class PaymentController( private val paymentService: PaymentService ) : PaymentAPI { - - @PostMapping("/confirm") - override fun confirmPayment( - @Valid @RequestBody request: PaymentConfirmRequest - ): ResponseEntity> { - val response = paymentService.requestConfirm(request) - - return ResponseEntity.ok(CommonApiResponse(response)) - } - @PostMapping("/cancel") override fun cancelPayment( @User user: CurrentUserContext, 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 773912ea..cdb12b63 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/payment/PaymentAPITest.kt @@ -4,22 +4,21 @@ 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.business.domain.PaymentMethod +import com.sangdol.roomescape.payment.business.domain.PaymentStatus +import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest 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.PaymentDetailRepository +import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEntity 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 com.sangdol.roomescape.payment.mapper.toDetailEntity +import com.sangdol.roomescape.payment.mapper.toEntity +import com.sangdol.roomescape.payment.mapper.toEvent +import com.sangdol.roomescape.supports.* 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 @@ -28,211 +27,10 @@ class PaymentAPITest( 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/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" @@ -262,7 +60,7 @@ class PaymentAPITest( val reservation = dummyInitializer.createConfirmReservation(user = user) val confirmRequest = PaymentFixture.confirmRequest - val paymentCreateResponse = createPayment( + val paymentEntity = createPayment( request = confirmRequest, reservationId = reservation.id ) @@ -289,10 +87,10 @@ class PaymentAPITest( statusCode(HttpStatus.OK.value()) } ).also { - val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId) + val payment = paymentRepository.findByIdOrNull(paymentEntity.id) ?: throw AssertionError("Unexpected Exception Occurred.") val canceledPayment = - canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId) + canceledPaymentRepository.findByPaymentId(paymentEntity.id) ?: throw AssertionError("Unexpected Exception Occurred.") payment.status shouldBe PaymentStatus.CANCELED @@ -319,7 +117,7 @@ class PaymentAPITest( private fun createPayment( request: PaymentConfirmRequest, reservationId: Long, - ): PaymentCreateResponse { + ): PaymentEntity { every { tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) } returns PaymentFixture.confirmResponse( @@ -331,49 +129,10 @@ class PaymentAPITest( transferDetail = null, ) - val paymentResponse = paymentService.requestConfirm(request) - return paymentService.savePayment(reservationId, paymentResponse) - } + val paymentEvent = paymentService.requestConfirm(reservationId, request).toEvent(reservationId) - 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("결제타입 확인 필요.") + return paymentRepository.save(paymentEvent.toEntity(IDGenerator.create())).also { + paymentDetailRepository.save(paymentEvent.toDetailEntity(IDGenerator.create(), it.id)) } - - 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()) - } - ) } } diff --git a/service/src/test/kotlin/com/sangdol/roomescape/supports/DummyInitializer.kt b/service/src/test/kotlin/com/sangdol/roomescape/supports/DummyInitializer.kt index dca0fefe..324d9f29 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/supports/DummyInitializer.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/supports/DummyInitializer.kt @@ -1,11 +1,11 @@ package com.sangdol.roomescape.supports -import com.sangdol.roomescape.payment.business.PaymentWriter import com.sangdol.roomescape.payment.business.domain.PaymentMethod import com.sangdol.roomescape.payment.dto.* -import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity -import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEntity -import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentRepository +import com.sangdol.roomescape.payment.infrastructure.persistence.* +import com.sangdol.roomescape.payment.mapper.toDetailEntity +import com.sangdol.roomescape.payment.mapper.toEntity +import com.sangdol.roomescape.payment.mapper.toEvent import com.sangdol.roomescape.payment.mapper.toResponse import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity @@ -33,7 +33,8 @@ class DummyInitializer( private val scheduleRepository: ScheduleRepository, private val reservationRepository: ReservationRepository, private val paymentRepository: PaymentRepository, - private val paymentWriter: PaymentWriter + private val paymentDetailRepository: PaymentDetailRepository, + private val canceledPaymentRepository: CanceledPaymentRepository ) { fun createStore( @@ -156,27 +157,6 @@ class DummyInitializer( } } - fun createExpiredOrCanceledReservation( - user: UserEntity, - status: ReservationStatus, - storeId: Long = IDGenerator.create(), - themeRequest: ThemeCreateRequest = ThemeFixture.createRequest, - scheduleRequest: ScheduleCreateRequest = ScheduleFixture.createRequest, - reservationRequest: PendingReservationCreateRequest = ReservationFixture.pendingCreateRequest, - ): ReservationEntity { - return createPendingReservation(user, storeId, themeRequest, scheduleRequest, reservationRequest).apply { - this.status = status - }.also { - reservationRepository.save(it) - - scheduleRepository.findByIdOrNull(it.scheduleId)?.let { schedule -> - schedule.status = ScheduleStatus.AVAILABLE - schedule.holdExpiredAt = null - scheduleRepository.save(schedule) - } - } - } - fun createPayment( reservationId: Long, request: PaymentConfirmRequest = PaymentFixture.confirmRequest, @@ -204,12 +184,10 @@ class DummyInitializer( transferDetail = transferDetail ) - val payment = paymentWriter.createPayment( - reservationId = reservationId, - paymentGatewayResponse = clientConfirmResponse - ) + val paymentEvent = clientConfirmResponse.toEvent(reservationId) + val payment = paymentRepository.save(paymentEvent.toEntity(IDGenerator.create())) - val detail = paymentWriter.createDetail(clientConfirmResponse, payment.id) + val detail = paymentDetailRepository.save(paymentEvent.toDetailEntity(IDGenerator.create(), payment.id)) return payment.toResponse(detail = detail.toResponse(), cancel = null) } @@ -227,11 +205,14 @@ class DummyInitializer( cancelReason = cancelReason, ) - return paymentWriter.cancel( - userId, - payment, - requestedAt = Instant.now(), - clientCancelResponse - ) + + return clientCancelResponse.cancels.toEntity( + id = IDGenerator.create(), + paymentId = payment.id, + cancelRequestedAt = Instant.now(), + canceledBy = userId + ).also { + canceledPaymentRepository.save(it) + } } } diff --git a/service/src/test/kotlin/com/sangdol/roomescape/supports/KotestConfig.kt b/service/src/test/kotlin/com/sangdol/roomescape/supports/KotestConfig.kt index a9eae9bf..0dcf3b61 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/supports/KotestConfig.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/supports/KotestConfig.kt @@ -1,7 +1,8 @@ package com.sangdol.roomescape.supports import com.sangdol.roomescape.admin.infrastructure.persistence.AdminRepository -import com.sangdol.roomescape.payment.business.PaymentWriter +import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentRepository +import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentDetailRepository import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository @@ -43,13 +44,7 @@ abstract class FunSpecSpringbootTest( } }) { @Autowired - private lateinit var userRepository: UserRepository - - @Autowired - private lateinit var adminRepository: AdminRepository - - @Autowired - private lateinit var storeRepository: StoreRepository + lateinit var testAuthUtil: TestAuthUtil @Autowired lateinit var dummyInitializer: DummyInitializer @@ -57,32 +52,40 @@ abstract class FunSpecSpringbootTest( @LocalServerPort var port: Int = 0 - lateinit var testAuthUtil: TestAuthUtil - override suspend fun beforeSpec(spec: Spec) { RestAssured.port = port - testAuthUtil = TestAuthUtil(userRepository, adminRepository, storeRepository) } } @TestConfiguration class TestConfig { + @Bean + fun testAuthUtil( + userRepository: UserRepository, + adminRepository: AdminRepository, + storeRepository: StoreRepository + ): TestAuthUtil { + return TestAuthUtil(userRepository, adminRepository, storeRepository) + } + @Bean fun dummyInitializer( storeRepository: StoreRepository, themeRepository: ThemeRepository, scheduleRepository: ScheduleRepository, reservationRepository: ReservationRepository, - paymentWriter: PaymentWriter, - paymentRepository: PaymentRepository + paymentRepository: PaymentRepository, + paymentDetailRepository: PaymentDetailRepository, + canceledPaymentRepository: CanceledPaymentRepository ): DummyInitializer { return DummyInitializer( themeRepository = themeRepository, scheduleRepository = scheduleRepository, reservationRepository = reservationRepository, - paymentWriter = paymentWriter, paymentRepository = paymentRepository, - storeRepository = storeRepository + storeRepository = storeRepository, + paymentDetailRepository = paymentDetailRepository, + canceledPaymentRepository = canceledPaymentRepository ) } }