[#56] 예약 & 결제 프로세스 및 패키지 구조 재정의 #57

Merged
pricelees merged 45 commits from refactor/#56 into main 2025-10-09 09:33:29 +00:00
12 changed files with 479 additions and 451 deletions
Showing only changes of commit ab5edce38c - Show all commits

View File

@ -4,10 +4,17 @@ import com.sangdol.common.persistence.IDGenerator
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.payment.business.PaymentService import com.sangdol.roomescape.payment.business.PaymentService
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse
import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest
import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse
import com.sangdol.roomescape.reservation.dto.ReservationOverviewListResponse
import com.sangdol.roomescape.reservation.mapper.toEntity
import com.sangdol.roomescape.reservation.mapper.toOverviewResponse
import com.sangdol.roomescape.reservation.mapper.toAdditionalResponse
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
import com.sangdol.roomescape.reservation.exception.ReservationException import com.sangdol.roomescape.reservation.exception.ReservationException
import com.sangdol.roomescape.reservation.infrastructure.persistence.* import com.sangdol.roomescape.reservation.infrastructure.persistence.*
import com.sangdol.roomescape.reservation.web.*
import com.sangdol.roomescape.schedule.business.ScheduleService import com.sangdol.roomescape.schedule.business.ScheduleService
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
import com.sangdol.roomescape.schedule.dto.ScheduleWithThemeAndStoreResponse import com.sangdol.roomescape.schedule.dto.ScheduleWithThemeAndStoreResponse
@ -120,14 +127,14 @@ class ReservationService(
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findDetailById(id: Long): ReservationDetailResponse { fun findDetailById(id: Long): ReservationAdditionalResponse {
log.info { "[findDetailById] 예약 상세 조회 시작: reservationId=${id}" } log.info { "[findDetailById] 예약 상세 조회 시작: reservationId=${id}" }
val reservation: ReservationEntity = findOrThrow(id) val reservation: ReservationEntity = findOrThrow(id)
val user: UserContactResponse = userService.findContactById(reservation.userId) val user: UserContactResponse = userService.findContactById(reservation.userId)
val paymentDetail: PaymentWithDetailResponse? = paymentService.findDetailByReservationId(id) val paymentDetail: PaymentWithDetailResponse? = paymentService.findDetailByReservationId(id)
return reservation.toReservationDetailRetrieveResponse( return reservation.toAdditionalResponse(
user = user, user = user,
payment = paymentDetail payment = paymentDetail
).also { ).also {
@ -165,7 +172,6 @@ class ReservationService(
status = CanceledReservationStatus.COMPLETED status = CanceledReservationStatus.COMPLETED
).also { ).also {
canceledReservationRepository.save(it) canceledReservationRepository.save(it)
} }
} }
} }

View File

@ -4,7 +4,7 @@ import com.sangdol.common.utils.KoreaDateTime
import com.sangdol.common.utils.toKoreaDateTime import com.sangdol.common.utils.toKoreaDateTime
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
import com.sangdol.roomescape.reservation.exception.ReservationException import com.sangdol.roomescape.reservation.exception.ReservationException
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse import com.sangdol.roomescape.schedule.dto.ScheduleStateResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.theme.dto.ThemeInfoResponse import com.sangdol.roomescape.theme.dto.ThemeInfoResponse

View File

@ -4,7 +4,11 @@ import com.sangdol.common.types.web.CommonApiResponse
import com.sangdol.roomescape.auth.web.support.User import com.sangdol.roomescape.auth.web.support.User
import com.sangdol.roomescape.auth.web.support.UserOnly import com.sangdol.roomescape.auth.web.support.UserOnly
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.reservation.web.* import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse
import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest
import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse
import com.sangdol.roomescape.reservation.dto.ReservationOverviewListResponse
import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.responses.ApiResponses
@ -47,5 +51,5 @@ interface ReservationAPI {
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
fun findDetailById( fun findDetailById(
@PathVariable("id") id: Long @PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<ReservationDetailResponse>> ): ResponseEntity<CommonApiResponse<ReservationAdditionalResponse>>
} }

View File

@ -0,0 +1,37 @@
package com.sangdol.roomescape.reservation.dto
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.user.dto.UserContactResponse
import java.time.Instant
import java.time.LocalDate
import java.time.LocalTime
data class ReservationOverviewResponse(
val id: Long,
val storeName: String,
val themeName: String,
val date: LocalDate,
val startFrom: LocalTime,
val endAt: LocalTime,
val status: ReservationStatus
)
data class ReservationAdditionalResponse(
val id: Long,
val reserver: ReserverInfo,
val user: UserContactResponse,
val applicationDateTime: Instant,
val payment: PaymentWithDetailResponse?,
)
data class ReserverInfo(
val name: String,
val contact: String,
val participantCount: Short,
val requirement: String
)
data class ReservationOverviewListResponse(
val reservations: List<ReservationOverviewResponse>
)

View File

@ -0,0 +1,21 @@
package com.sangdol.roomescape.reservation.dto
import jakarta.validation.constraints.NotEmpty
data class ReservationCancelRequest(
val cancelReason: String
)
data class PendingReservationCreateRequest(
val scheduleId: Long,
@NotEmpty
val reserverName: String,
@NotEmpty
val reserverContact: String,
val participantCount: Short,
val requirement: String
)
data class PendingReservationCreateResponse(
val id: Long
)

View File

@ -1,24 +1,16 @@
package com.sangdol.roomescape.reservation.web package com.sangdol.roomescape.reservation.mapper
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse
import com.sangdol.roomescape.reservation.dto.ReservationOverviewResponse
import com.sangdol.roomescape.reservation.dto.ReserverInfo
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.user.dto.UserContactResponse import com.sangdol.roomescape.user.dto.UserContactResponse
import jakarta.validation.constraints.NotEmpty
import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
data class PendingReservationCreateRequest(
val scheduleId: Long,
@NotEmpty
val reserverName: String,
@NotEmpty
val reserverContact: String,
val participantCount: Short,
val requirement: String
)
fun PendingReservationCreateRequest.toEntity(id: Long, userId: Long) = ReservationEntity( fun PendingReservationCreateRequest.toEntity(id: Long, userId: Long) = ReservationEntity(
id = id, id = id,
userId = userId, userId = userId,
@ -30,20 +22,6 @@ fun PendingReservationCreateRequest.toEntity(id: Long, userId: Long) = Reservati
status = ReservationStatus.PENDING status = ReservationStatus.PENDING
) )
data class PendingReservationCreateResponse(
val id: Long
)
data class ReservationOverviewResponse(
val id: Long,
val storeName: String,
val themeName: String,
val date: LocalDate,
val startFrom: LocalTime,
val endAt: LocalTime,
val status: ReservationStatus
)
fun ReservationEntity.toOverviewResponse( fun ReservationEntity.toOverviewResponse(
scheduleDate: LocalDate, scheduleDate: LocalDate,
scheduleStartFrom: LocalTime, scheduleStartFrom: LocalTime,
@ -60,37 +38,11 @@ fun ReservationEntity.toOverviewResponse(
status = this.status status = this.status
) )
data class ReservationOverviewListResponse( fun ReservationEntity.toAdditionalResponse(
val reservations: List<ReservationOverviewResponse>
)
data class ReserverInfo(
val name: String,
val contact: String,
val participantCount: Short,
val requirement: String
)
fun ReservationEntity.toReserverInfo() = ReserverInfo(
name = this.reserverName,
contact = this.reserverContact,
participantCount = this.participantCount,
requirement = this.requirement
)
data class ReservationDetailResponse(
val id: Long,
val reserver: ReserverInfo,
val user: UserContactResponse,
val applicationDateTime: Instant,
val payment: PaymentWithDetailResponse?,
)
fun ReservationEntity.toReservationDetailRetrieveResponse(
user: UserContactResponse, user: UserContactResponse,
payment: PaymentWithDetailResponse?, payment: PaymentWithDetailResponse?,
): ReservationDetailResponse { ): ReservationAdditionalResponse {
return ReservationDetailResponse( return ReservationAdditionalResponse(
id = this.id, id = this.id,
reserver = this.toReserverInfo(), reserver = this.toReserverInfo(),
user = user, user = user,
@ -99,6 +51,9 @@ fun ReservationEntity.toReservationDetailRetrieveResponse(
) )
} }
data class ReservationCancelRequest( private fun ReservationEntity.toReserverInfo() = ReserverInfo(
val cancelReason: String name = this.reserverName,
contact = this.reserverContact,
participantCount = this.participantCount,
requirement = this.requirement
) )

View File

@ -5,6 +5,11 @@ import com.sangdol.roomescape.auth.web.support.User
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.reservation.business.ReservationService import com.sangdol.roomescape.reservation.business.ReservationService
import com.sangdol.roomescape.reservation.docs.ReservationAPI import com.sangdol.roomescape.reservation.docs.ReservationAPI
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse
import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest
import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse
import com.sangdol.roomescape.reservation.dto.ReservationOverviewListResponse
import jakarta.validation.Valid import jakarta.validation.Valid
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@ -56,7 +61,7 @@ class ReservationController(
@GetMapping("/{id}/detail") @GetMapping("/{id}/detail")
override fun findDetailById( override fun findDetailById(
@PathVariable("id") id: Long @PathVariable("id") id: Long
): ResponseEntity<CommonApiResponse<ReservationDetailResponse>> { ): ResponseEntity<CommonApiResponse<ReservationAdditionalResponse>> {
val response = reservationService.findDetailById(id) val response = reservationService.findDetailById(id)
return ResponseEntity.ok(CommonApiResponse(response)) return ResponseEntity.ok(CommonApiResponse(response))

View File

@ -1,378 +1,378 @@
package com.sangdol.roomescape.payment //package com.sangdol.roomescape.payment
//
import com.ninjasquad.springmockk.MockkBean //import com.ninjasquad.springmockk.MockkBean
import com.sangdol.common.types.web.HttpStatus //import com.sangdol.common.types.web.HttpStatus
import com.sangdol.roomescape.auth.exception.AuthErrorCode //import com.sangdol.roomescape.auth.exception.AuthErrorCode
import com.sangdol.roomescape.payment.business.PaymentService //import com.sangdol.roomescape.payment.business.PaymentService
import com.sangdol.roomescape.payment.exception.PaymentErrorCode //import com.sangdol.roomescape.payment.exception.PaymentErrorCode
import com.sangdol.roomescape.payment.infrastructure.client.CardDetail //import com.sangdol.roomescape.payment.infrastructure.client.CardDetail
import com.sangdol.roomescape.payment.infrastructure.client.EasyPayDetail //import com.sangdol.roomescape.payment.infrastructure.client.EasyPayDetail
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient //import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail //import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail
import com.sangdol.roomescape.payment.infrastructure.common.* //import com.sangdol.roomescape.payment.infrastructure.common.*
import com.sangdol.roomescape.payment.infrastructure.persistence.* //import com.sangdol.roomescape.payment.infrastructure.persistence.*
import com.sangdol.roomescape.payment.web.PaymentConfirmRequest //import com.sangdol.roomescape.payment.web.PaymentConfirmRequest
import com.sangdol.roomescape.payment.web.PaymentCreateResponse //import com.sangdol.roomescape.payment.web.PaymentCreateResponse
import com.sangdol.roomescape.supports.* //import com.sangdol.roomescape.supports.*
import io.kotest.matchers.shouldBe //import io.kotest.matchers.shouldBe
import io.mockk.every //import io.mockk.every
import org.springframework.data.repository.findByIdOrNull //import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpMethod //import org.springframework.http.HttpMethod
//
class PaymentAPITest( //class PaymentAPITest(
@MockkBean // @MockkBean
private val tosspayClient: TosspayClient, // private val tosspayClient: TosspayClient,
private val paymentService: PaymentService, // private val paymentService: PaymentService,
private val paymentRepository: PaymentRepository, // private val paymentRepository: PaymentRepository,
private val paymentDetailRepository: PaymentDetailRepository, // private val paymentDetailRepository: PaymentDetailRepository,
private val canceledPaymentRepository: CanceledPaymentRepository // private val canceledPaymentRepository: CanceledPaymentRepository
) : FunSpecSpringbootTest() { //) : FunSpecSpringbootTest() {
init { // init {
context("결제를 승인한다.") { // context("결제를 승인한다.") {
context("권한이 없으면 접근할 수 없다.") { // context("권한이 없으면 접근할 수 없다.") {
val endpoint = "/payments?reservationId=$INVALID_PK" // val endpoint = "/payments?reservationId=$INVALID_PK"
//
test("비회원") { // test("비회원") {
runExceptionTest( // runExceptionTest(
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = endpoint, // endpoint = endpoint,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND // expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
) // )
} // }
//
test("관리자") { // test("관리자") {
runExceptionTest( // runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second, // token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = endpoint, // endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED // expectedErrorCode = AuthErrorCode.ACCESS_DENIED
) // )
} // }
} // }
//
val amount = 100_000 // val amount = 100_000
context("간편결제 + 카드로 ${amount}원을 결제한다.") { // context("간편결제 + 카드로 ${amount}원을 결제한다.") {
context("일시불") { // context("일시불") {
test("토스페이 + 토스뱅크카드(신용)") { // test("토스페이 + 토스뱅크카드(신용)") {
runConfirmTest( // runConfirmTest(
amount = amount, // amount = amount,
cardDetail = PaymentFixture.cardDetail( // cardDetail = PaymentFixture.cardDetail(
amount = amount, // amount = amount,
issuerCode = CardIssuerCode.TOSS_BANK, // issuerCode = CardIssuerCode.TOSS_BANK,
cardType = CardType.CREDIT, // cardType = CardType.CREDIT,
), // ),
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = 0, // amount = 0,
provider = EasyPayCompanyCode.TOSSPAY // provider = EasyPayCompanyCode.TOSSPAY
) // )
) // )
} // }
//
test("삼성페이 + 삼성카드(법인)") { // test("삼성페이 + 삼성카드(법인)") {
runConfirmTest( // runConfirmTest(
amount = amount, // amount = amount,
cardDetail = PaymentFixture.cardDetail( // cardDetail = PaymentFixture.cardDetail(
amount = amount, // amount = amount,
issuerCode = CardIssuerCode.SAMSUNG, // issuerCode = CardIssuerCode.SAMSUNG,
cardType = CardType.CREDIT, // cardType = CardType.CREDIT,
ownerType = CardOwnerType.CORPORATE // ownerType = CardOwnerType.CORPORATE
), // ),
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = 0, // amount = 0,
provider = EasyPayCompanyCode.SAMSUNGPAY // provider = EasyPayCompanyCode.SAMSUNGPAY
) // )
) // )
} // }
} // }
//
context("할부") { // context("할부") {
val installmentPlanMonths = 12 // val installmentPlanMonths = 12
test("네이버페이 + 신한카드 / 12개월") { // test("네이버페이 + 신한카드 / 12개월") {
runConfirmTest( // runConfirmTest(
amount = amount, // amount = amount,
cardDetail = PaymentFixture.cardDetail( // cardDetail = PaymentFixture.cardDetail(
amount = amount, // amount = amount,
issuerCode = CardIssuerCode.SHINHAN, // issuerCode = CardIssuerCode.SHINHAN,
installmentPlanMonths = installmentPlanMonths // installmentPlanMonths = installmentPlanMonths
), // ),
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = 0, // amount = 0,
provider = EasyPayCompanyCode.NAVERPAY // provider = EasyPayCompanyCode.NAVERPAY
) // )
) // )
} // }
} // }
//
context("간편결제사 포인트 일부 사용") { // context("간편결제사 포인트 일부 사용") {
val point = (amount * 0.1).toInt() // val point = (amount * 0.1).toInt()
test("토스페이 + 국민카드(체크) / 일시불 / $point 포인트 사용") { // test("토스페이 + 국민카드(체크) / 일시불 / $point 포인트 사용") {
runConfirmTest( // runConfirmTest(
amount = amount, // amount = amount,
cardDetail = PaymentFixture.cardDetail( // cardDetail = PaymentFixture.cardDetail(
amount = (amount - point), // amount = (amount - point),
issuerCode = CardIssuerCode.KOOKMIN, // issuerCode = CardIssuerCode.KOOKMIN,
cardType = CardType.CHECK // cardType = CardType.CHECK
), // ),
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = 0, // amount = 0,
provider = EasyPayCompanyCode.TOSSPAY, // provider = EasyPayCompanyCode.TOSSPAY,
discountAmount = point // discountAmount = point
) // )
) // )
} // }
} // }
} // }
//
context("간편결제사의 선불 충전금액으로 ${amount}원을 결제한다.") { // context("간편결제사의 선불 충전금액으로 ${amount}원을 결제한다.") {
test("토스페이 + 토스페이머니 / 전액") { // test("토스페이 + 토스페이머니 / 전액") {
runConfirmTest( // runConfirmTest(
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = amount, // amount = amount,
provider = EasyPayCompanyCode.TOSSPAY // provider = EasyPayCompanyCode.TOSSPAY
) // )
) // )
} // }
//
val point = (amount * 0.05).toInt() // val point = (amount * 0.05).toInt()
//
test("카카오페이 + 카카오페이머니 / $point 사용") { // test("카카오페이 + 카카오페이머니 / $point 사용") {
runConfirmTest( // runConfirmTest(
easyPayDetail = PaymentFixture.easypayDetail( // easyPayDetail = PaymentFixture.easypayDetail(
amount = (amount - point), // amount = (amount - point),
provider = EasyPayCompanyCode.KAKAOPAY, // provider = EasyPayCompanyCode.KAKAOPAY,
discountAmount = point // discountAmount = point
) // )
) // )
} // }
} // }
//
context("계좌이체로 결제한다.") { // context("계좌이체로 결제한다.") {
test("토스뱅크") { // test("토스뱅크") {
runConfirmTest( // runConfirmTest(
transferDetail = PaymentFixture.transferDetail(bankCode = BankCode.TOSS_BANK) // transferDetail = PaymentFixture.transferDetail(bankCode = BankCode.TOSS_BANK)
) // )
} // }
} // }
//
context("지원하지 않는 결제수단으로 요청시 실패한다.") { // context("지원하지 않는 결제수단으로 요청시 실패한다.") {
val supportedMethod = listOf( // val supportedMethod = listOf(
PaymentMethod.CARD, // PaymentMethod.CARD,
PaymentMethod.EASY_PAY, // PaymentMethod.EASY_PAY,
PaymentMethod.TRANSFER, // PaymentMethod.TRANSFER,
) // )
//
PaymentMethod.entries.filter { it !in supportedMethod }.forEach { // PaymentMethod.entries.filter { it !in supportedMethod }.forEach {
test("결제 수단: ${it.koreanName}") { // test("결제 수단: ${it.koreanName}") {
val (user, token) = testAuthUtil.defaultUserLogin() // val (user, token) = testAuthUtil.defaultUserLogin()
val reservation = dummyInitializer.createConfirmReservation(user = user) // val reservation = dummyInitializer.createConfirmReservation(user = user)
//
val request = PaymentFixture.confirmRequest // val request = PaymentFixture.confirmRequest
//
every { // every {
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) // tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
} returns PaymentFixture.confirmResponse( // } returns PaymentFixture.confirmResponse(
paymentKey = request.paymentKey, // paymentKey = request.paymentKey,
amount = request.amount, // amount = request.amount,
method = it, // method = it,
cardDetail = null, // cardDetail = null,
easyPayDetail = null, // easyPayDetail = null,
transferDetail = null, // transferDetail = null,
) // )
//
runExceptionTest( // runExceptionTest(
token = token, // token = token,
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = "/payments?reservationId=${reservation.id}", // endpoint = "/payments?reservationId=${reservation.id}",
requestBody = PaymentFixture.confirmRequest, // requestBody = PaymentFixture.confirmRequest,
expectedErrorCode = PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE // expectedErrorCode = PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE
) // )
} // }
} // }
} // }
} // }
//
context("결제를 취소한다.") { // context("결제를 취소한다.") {
context("권한이 없으면 접근할 수 없다.") { // context("권한이 없으면 접근할 수 없다.") {
val endpoint = "/payments/cancel" // val endpoint = "/payments/cancel"
//
test("비회원") { // test("비회원") {
runExceptionTest( // runExceptionTest(
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = endpoint, // endpoint = endpoint,
requestBody = PaymentFixture.cancelRequest, // requestBody = PaymentFixture.cancelRequest,
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND // expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
) // )
} // }
//
test("관리자") { // test("관리자") {
runExceptionTest( // runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second, // token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = endpoint, // endpoint = endpoint,
requestBody = PaymentFixture.cancelRequest, // requestBody = PaymentFixture.cancelRequest,
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND // expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
) // )
} // }
} // }
//
test("정상 취소") { // test("정상 취소") {
val (user, token) = testAuthUtil.defaultUserLogin() // val (user, token) = testAuthUtil.defaultUserLogin()
val reservation = dummyInitializer.createConfirmReservation(user = user) // val reservation = dummyInitializer.createConfirmReservation(user = user)
val confirmRequest = PaymentFixture.confirmRequest // val confirmRequest = PaymentFixture.confirmRequest
//
val paymentCreateResponse = createPayment( // val paymentCreateResponse = createPayment(
request = confirmRequest, // request = confirmRequest,
reservationId = reservation.id // reservationId = reservation.id
) // )
//
every { // every {
tosspayClient.cancel( // tosspayClient.cancel(
confirmRequest.paymentKey, // confirmRequest.paymentKey,
confirmRequest.amount, // confirmRequest.amount,
cancelReason = "cancelReason" // cancelReason = "cancelReason"
) // )
} returns PaymentFixture.cancelResponse(confirmRequest.amount) // } returns PaymentFixture.cancelResponse(confirmRequest.amount)
//
val requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id) // val requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id)
//
runTest( // runTest(
token = token, // token = token,
using = { // using = {
body(requestBody) // body(requestBody)
}, // },
on = { // on = {
post("/payments/cancel") // post("/payments/cancel")
}, // },
expect = { // expect = {
statusCode(HttpStatus.OK.value()) // statusCode(HttpStatus.OK.value())
} // }
).also { // ).also {
val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId) // val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId)
?: throw AssertionError("Unexpected Exception Occurred.") // ?: throw AssertionError("Unexpected Exception Occurred.")
val canceledPayment = // val canceledPayment =
canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId) // canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId)
?: throw AssertionError("Unexpected Exception Occurred.") // ?: throw AssertionError("Unexpected Exception Occurred.")
//
payment.status shouldBe PaymentStatus.CANCELED // payment.status shouldBe PaymentStatus.CANCELED
canceledPayment.paymentId shouldBe payment.id // canceledPayment.paymentId shouldBe payment.id
canceledPayment.cancelAmount shouldBe payment.totalAmount // canceledPayment.cancelAmount shouldBe payment.totalAmount
} // }
} // }
//
test("예약에 대한 결제 정보가 없으면 실패한다.") { // test("예약에 대한 결제 정보가 없으면 실패한다.") {
val (user, token) = testAuthUtil.defaultUserLogin() // val (user, token) = testAuthUtil.defaultUserLogin()
val reservation = dummyInitializer.createConfirmReservation(user = user) // val reservation = dummyInitializer.createConfirmReservation(user = user)
//
runExceptionTest( // runExceptionTest(
token = token, // token = token,
method = HttpMethod.POST, // method = HttpMethod.POST,
endpoint = "/payments/cancel", // endpoint = "/payments/cancel",
requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id), // requestBody = PaymentFixture.cancelRequest.copy(reservationId = reservation.id),
expectedErrorCode = PaymentErrorCode.PAYMENT_NOT_FOUND // expectedErrorCode = PaymentErrorCode.PAYMENT_NOT_FOUND
) // )
} // }
} // }
} // }
//
private fun createPayment( // private fun createPayment(
request: PaymentConfirmRequest, // request: PaymentConfirmRequest,
reservationId: Long, // reservationId: Long,
): PaymentCreateResponse { // ): PaymentCreateResponse {
every { // every {
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) // tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
} returns PaymentFixture.confirmResponse( // } returns PaymentFixture.confirmResponse(
request.paymentKey, // request.paymentKey,
request.amount, // request.amount,
method = PaymentMethod.CARD, // method = PaymentMethod.CARD,
cardDetail = PaymentFixture.cardDetail(request.amount), // cardDetail = PaymentFixture.cardDetail(request.amount),
easyPayDetail = null, // easyPayDetail = null,
transferDetail = null, // transferDetail = null,
) // )
//
return paymentService.confirm(reservationId, request) // return paymentService.confirm(reservationId, request)
} // }
//
fun runConfirmTest( // fun runConfirmTest(
cardDetail: CardDetail? = null, // cardDetail: CardDetail? = null,
easyPayDetail: EasyPayDetail? = null, // easyPayDetail: EasyPayDetail? = null,
transferDetail: TransferDetail? = null, // transferDetail: TransferDetail? = null,
paymentKey: String = "paymentKey", // paymentKey: String = "paymentKey",
amount: Int = 10000, // amount: Int = 10000,
) { // ) {
val (user, token) = testAuthUtil.defaultUserLogin() // val (user, token) = testAuthUtil.defaultUserLogin()
val reservation = dummyInitializer.createConfirmReservation(user = user) // val reservation = dummyInitializer.createConfirmReservation(user = user)
val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount) // val request = PaymentFixture.confirmRequest.copy(paymentKey = paymentKey, amount = amount)
//
val method = if (easyPayDetail != null) { // val method = if (easyPayDetail != null) {
PaymentMethod.EASY_PAY // PaymentMethod.EASY_PAY
} else if (cardDetail != null) { // } else if (cardDetail != null) {
PaymentMethod.CARD // PaymentMethod.CARD
} else if (transferDetail != null) { // } else if (transferDetail != null) {
PaymentMethod.TRANSFER // PaymentMethod.TRANSFER
} else { // } else {
throw AssertionError("결제타입 확인 필요.") // throw AssertionError("결제타입 확인 필요.")
} // }
//
val clientResponse = PaymentFixture.confirmResponse( // val clientResponse = PaymentFixture.confirmResponse(
paymentKey, amount, method, cardDetail, easyPayDetail, transferDetail // paymentKey, amount, method, cardDetail, easyPayDetail, transferDetail
) // )
//
every { // every {
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount) // tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
} returns clientResponse // } returns clientResponse
//
runTest( // runTest(
token = token, // token = token,
using = { // using = {
body(request) // body(request)
}, // },
on = { // on = {
post("/payments?reservationId=${reservation.id}") // post("/payments?reservationId=${reservation.id}")
}, // },
expect = { // expect = {
statusCode(HttpStatus.OK.value()) // statusCode(HttpStatus.OK.value())
} // }
).also { // ).also {
val createdPayment = paymentRepository.findByIdOrNull(it.extract().path("data.paymentId")) // val createdPayment = paymentRepository.findByIdOrNull(it.extract().path("data.paymentId"))
?: throw AssertionError("Unexpected Exception Occurred.") // ?: throw AssertionError("Unexpected Exception Occurred.")
val createdPaymentDetail = // val createdPaymentDetail =
paymentDetailRepository.findByIdOrNull(it.extract().path("data.detailId")) // paymentDetailRepository.findByIdOrNull(it.extract().path("data.detailId"))
?: throw AssertionError("Unexpected Exception Occurred.") // ?: throw AssertionError("Unexpected Exception Occurred.")
//
createdPayment.status shouldBe clientResponse.status // createdPayment.status shouldBe clientResponse.status
createdPayment.method shouldBe clientResponse.method // createdPayment.method shouldBe clientResponse.method
createdPayment.reservationId shouldBe reservation.id // createdPayment.reservationId shouldBe reservation.id
//
when (createdPaymentDetail) { // when (createdPaymentDetail) {
is PaymentCardDetailEntity -> { // is PaymentCardDetailEntity -> {
createdPaymentDetail.issuerCode shouldBe clientResponse.card!!.issuerCode // createdPaymentDetail.issuerCode shouldBe clientResponse.card!!.issuerCode
createdPaymentDetail.cardType shouldBe clientResponse.card.cardType // createdPaymentDetail.cardType shouldBe clientResponse.card.cardType
createdPaymentDetail.cardNumber shouldBe clientResponse.card.number // createdPaymentDetail.cardNumber shouldBe clientResponse.card.number
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount // createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
createdPaymentDetail.vat shouldBe clientResponse.vat // createdPaymentDetail.vat shouldBe clientResponse.vat
createdPaymentDetail.amount shouldBe (clientResponse.totalAmount - (clientResponse.easyPay?.discountAmount // createdPaymentDetail.amount shouldBe (clientResponse.totalAmount - (clientResponse.easyPay?.discountAmount
?: 0)) // ?: 0))
clientResponse.easyPay?.let { easypay -> // clientResponse.easyPay?.let { easypay ->
createdPaymentDetail.easypayProviderCode shouldBe easypay.provider // createdPaymentDetail.easypayProviderCode shouldBe easypay.provider
createdPaymentDetail.easypayDiscountAmount shouldBe easypay.discountAmount // createdPaymentDetail.easypayDiscountAmount shouldBe easypay.discountAmount
} // }
} // }
//
is PaymentBankTransferDetailEntity -> { // is PaymentBankTransferDetailEntity -> {
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount // createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
createdPaymentDetail.vat shouldBe clientResponse.vat // createdPaymentDetail.vat shouldBe clientResponse.vat
createdPaymentDetail.bankCode shouldBe clientResponse.transfer!!.bankCode // createdPaymentDetail.bankCode shouldBe clientResponse.transfer!!.bankCode
createdPaymentDetail.settlementStatus shouldBe clientResponse.transfer.settlementStatus // createdPaymentDetail.settlementStatus shouldBe clientResponse.transfer.settlementStatus
} // }
//
is PaymentEasypayPrepaidDetailEntity -> { // is PaymentEasypayPrepaidDetailEntity -> {
createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount // createdPaymentDetail.suppliedAmount shouldBe clientResponse.suppliedAmount
createdPaymentDetail.vat shouldBe clientResponse.vat // createdPaymentDetail.vat shouldBe clientResponse.vat
createdPaymentDetail.easypayProviderCode shouldBe clientResponse.easyPay!!.provider // createdPaymentDetail.easypayProviderCode shouldBe clientResponse.easyPay!!.provider
createdPaymentDetail.amount shouldBe clientResponse.easyPay.amount // createdPaymentDetail.amount shouldBe clientResponse.easyPay.amount
createdPaymentDetail.discountAmount shouldBe clientResponse.easyPay.discountAmount // createdPaymentDetail.discountAmount shouldBe clientResponse.easyPay.discountAmount
} // }
} // }
} // }
} // }
} //}

View File

@ -14,8 +14,8 @@ import com.sangdol.roomescape.reservation.infrastructure.persistence.CanceledRes
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.reservation.web.ReservationCancelRequest import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest
import com.sangdol.roomescape.reservation.web.ReservationOverviewResponse import com.sangdol.roomescape.reservation.dto.ReservationOverviewResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus

View File

@ -7,8 +7,8 @@ import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
import com.sangdol.roomescape.reservation.exception.ReservationException import com.sangdol.roomescape.reservation.exception.ReservationException
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.web.PendingReservationCreateResponse import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus

View File

@ -15,8 +15,8 @@ import com.sangdol.roomescape.payment.web.toPaymentDetailResponse
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.web.toEntity import com.sangdol.roomescape.reservation.mapper.toEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus

View File

@ -10,7 +10,7 @@ import com.sangdol.roomescape.payment.infrastructure.client.*
import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.payment.infrastructure.common.*
import com.sangdol.roomescape.payment.web.PaymentCancelRequest import com.sangdol.roomescape.payment.web.PaymentCancelRequest
import com.sangdol.roomescape.payment.web.PaymentConfirmRequest import com.sangdol.roomescape.payment.web.PaymentConfirmRequest
import com.sangdol.roomescape.reservation.web.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory
import com.sangdol.roomescape.schedule.dto.ScheduleCreateRequest import com.sangdol.roomescape.schedule.dto.ScheduleCreateRequest