generated from pricelees/issue-pr-template
[#66] 결제 & 예약 확정 로직 수정 #67
@ -1,5 +1,6 @@
|
|||||||
package com.sangdol.roomescape.payment.business
|
package com.sangdol.roomescape.payment.business
|
||||||
|
|
||||||
|
import com.sangdol.common.persistence.IDGenerator
|
||||||
import com.sangdol.common.persistence.TransactionExecutionUtil
|
import com.sangdol.common.persistence.TransactionExecutionUtil
|
||||||
import com.sangdol.roomescape.payment.business.domain.UserFacingPaymentErrorCode
|
import com.sangdol.roomescape.payment.business.domain.UserFacingPaymentErrorCode
|
||||||
import com.sangdol.roomescape.payment.dto.*
|
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.exception.PaymentException
|
||||||
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
||||||
|
import com.sangdol.roomescape.payment.mapper.toEntity
|
||||||
import com.sangdol.roomescape.payment.mapper.toEvent
|
import com.sangdol.roomescape.payment.mapper.toEvent
|
||||||
import com.sangdol.roomescape.payment.mapper.toResponse
|
import com.sangdol.roomescape.payment.mapper.toResponse
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
@ -20,11 +22,11 @@ private val log: KLogger = KotlinLogging.logger {}
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class PaymentService(
|
class PaymentService(
|
||||||
|
private val idGenerator: IDGenerator,
|
||||||
private val paymentClient: TosspayClient,
|
private val paymentClient: TosspayClient,
|
||||||
private val paymentRepository: PaymentRepository,
|
private val paymentRepository: PaymentRepository,
|
||||||
private val paymentDetailRepository: PaymentDetailRepository,
|
private val paymentDetailRepository: PaymentDetailRepository,
|
||||||
private val canceledPaymentRepository: CanceledPaymentRepository,
|
private val canceledPaymentRepository: CanceledPaymentRepository,
|
||||||
private val paymentWriter: PaymentWriter,
|
|
||||||
private val transactionExecutionUtil: TransactionExecutionUtil,
|
private val transactionExecutionUtil: TransactionExecutionUtil,
|
||||||
private val eventPublisher: ApplicationEventPublisher
|
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) {
|
fun cancel(userId: Long, request: PaymentCancelRequest) {
|
||||||
val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId)
|
val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId)
|
||||||
|
|
||||||
@ -83,12 +72,17 @@ class PaymentService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
paymentWriter.cancel(
|
val payment = findByReservationIdOrThrow(request.reservationId).apply { this.cancel() }
|
||||||
userId = userId,
|
|
||||||
payment = payment,
|
clientCancelResponse.cancels.toEntity(
|
||||||
requestedAt = request.requestedAt,
|
id = idGenerator.create(),
|
||||||
cancelResponse = clientCancelResponse
|
paymentId = payment.id,
|
||||||
)
|
cancelRequestedAt = request.requestedAt,
|
||||||
|
canceledBy = userId
|
||||||
|
).also {
|
||||||
|
canceledPaymentRepository.save(it)
|
||||||
|
log.debug { "[cancel] 결제 취소 정보 저장 완료: payment.id=${payment.id}" }
|
||||||
|
}
|
||||||
}.also {
|
}.also {
|
||||||
log.info { "[cancel] 결제 취소 완료: paymentId=${payment.id}" }
|
log.info { "[cancel] 결제 취소 완료: paymentId=${payment.id}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,11 +2,8 @@ package com.sangdol.roomescape.payment.docs
|
|||||||
|
|
||||||
import com.sangdol.common.types.web.CommonApiResponse
|
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.common.types.CurrentUserContext
|
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.PaymentCancelRequest
|
||||||
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
|
|
||||||
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
|
||||||
@ -16,13 +13,6 @@ import org.springframework.web.bind.annotation.RequestBody
|
|||||||
|
|
||||||
interface PaymentAPI {
|
interface PaymentAPI {
|
||||||
|
|
||||||
@UserOnly
|
|
||||||
@Operation(summary = "결제 승인")
|
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
|
||||||
fun confirmPayment(
|
|
||||||
@Valid @RequestBody request: PaymentConfirmRequest
|
|
||||||
): ResponseEntity<CommonApiResponse<PaymentGatewayResponse>>
|
|
||||||
|
|
||||||
@Operation(summary = "결제 취소")
|
@Operation(summary = "결제 취소")
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
||||||
fun cancelPayment(
|
fun cancelPayment(
|
||||||
|
|||||||
@ -6,79 +6,9 @@ import com.sangdol.roomescape.payment.dto.CancelDetail
|
|||||||
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
|
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
|
||||||
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
||||||
import com.sangdol.roomescape.payment.exception.PaymentException
|
import com.sangdol.roomescape.payment.exception.PaymentException
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
||||||
import java.time.Instant
|
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(
|
fun CancelDetail.toEntity(
|
||||||
id: Long,
|
id: Long,
|
||||||
paymentId: Long,
|
paymentId: Long,
|
||||||
@ -115,7 +45,7 @@ fun PaymentGatewayResponse.toEvent(reservationId: Long): PaymentEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun PaymentGatewayResponse.toDetail(): PaymentDetail {
|
fun PaymentGatewayResponse.toDetail(): PaymentDetail {
|
||||||
return when(this.method) {
|
return when (this.method) {
|
||||||
PaymentMethod.TRANSFER -> this.toBankTransferDetail()
|
PaymentMethod.TRANSFER -> this.toBankTransferDetail()
|
||||||
PaymentMethod.CARD -> this.toCardDetail()
|
PaymentMethod.CARD -> this.toCardDetail()
|
||||||
PaymentMethod.EASY_PAY -> {
|
PaymentMethod.EASY_PAY -> {
|
||||||
|
|||||||
@ -6,27 +6,18 @@ 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.docs.PaymentAPI
|
import com.sangdol.roomescape.payment.docs.PaymentAPI
|
||||||
import com.sangdol.roomescape.payment.dto.PaymentCancelRequest
|
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 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.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/payments")
|
@RequestMapping("/payments")
|
||||||
class PaymentController(
|
class PaymentController(
|
||||||
private val paymentService: PaymentService
|
private val paymentService: PaymentService
|
||||||
) : PaymentAPI {
|
) : PaymentAPI {
|
||||||
|
|
||||||
@PostMapping("/confirm")
|
|
||||||
override fun confirmPayment(
|
|
||||||
@Valid @RequestBody request: PaymentConfirmRequest
|
|
||||||
): ResponseEntity<CommonApiResponse<PaymentGatewayResponse>> {
|
|
||||||
val response = paymentService.requestConfirm(request)
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/cancel")
|
@PostMapping("/cancel")
|
||||||
override fun cancelPayment(
|
override fun cancelPayment(
|
||||||
@User user: CurrentUserContext,
|
@User user: CurrentUserContext,
|
||||||
|
|||||||
@ -4,22 +4,21 @@ 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.business.domain.*
|
import com.sangdol.roomescape.payment.business.domain.PaymentMethod
|
||||||
import com.sangdol.roomescape.payment.dto.*
|
import com.sangdol.roomescape.payment.business.domain.PaymentStatus
|
||||||
import com.sangdol.roomescape.payment.exception.ExternalPaymentException
|
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
|
||||||
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
|
||||||
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
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.payment.infrastructure.persistence.PaymentRepository
|
||||||
import com.sangdol.roomescape.supports.FunSpecSpringbootTest
|
import com.sangdol.roomescape.payment.mapper.toDetailEntity
|
||||||
import com.sangdol.roomescape.supports.PaymentFixture
|
import com.sangdol.roomescape.payment.mapper.toEntity
|
||||||
import com.sangdol.roomescape.supports.runExceptionTest
|
import com.sangdol.roomescape.payment.mapper.toEvent
|
||||||
import com.sangdol.roomescape.supports.runTest
|
import com.sangdol.roomescape.supports.*
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.mockk.clearMocks
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import org.hamcrest.CoreMatchers.containsString
|
|
||||||
import org.hamcrest.CoreMatchers.equalTo
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.http.HttpMethod
|
import org.springframework.http.HttpMethod
|
||||||
|
|
||||||
@ -28,211 +27,10 @@ class PaymentAPITest(
|
|||||||
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 canceledPaymentRepository: CanceledPaymentRepository
|
private val canceledPaymentRepository: CanceledPaymentRepository
|
||||||
) : FunSpecSpringbootTest() {
|
) : FunSpecSpringbootTest() {
|
||||||
init {
|
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("결제를 취소한다.") {
|
||||||
context("권한이 없으면 접근할 수 없다.") {
|
context("권한이 없으면 접근할 수 없다.") {
|
||||||
val endpoint = "/payments/cancel"
|
val endpoint = "/payments/cancel"
|
||||||
@ -262,7 +60,7 @@ class PaymentAPITest(
|
|||||||
val reservation = dummyInitializer.createConfirmReservation(user = user)
|
val reservation = dummyInitializer.createConfirmReservation(user = user)
|
||||||
val confirmRequest = PaymentFixture.confirmRequest
|
val confirmRequest = PaymentFixture.confirmRequest
|
||||||
|
|
||||||
val paymentCreateResponse = createPayment(
|
val paymentEntity = createPayment(
|
||||||
request = confirmRequest,
|
request = confirmRequest,
|
||||||
reservationId = reservation.id
|
reservationId = reservation.id
|
||||||
)
|
)
|
||||||
@ -289,10 +87,10 @@ class PaymentAPITest(
|
|||||||
statusCode(HttpStatus.OK.value())
|
statusCode(HttpStatus.OK.value())
|
||||||
}
|
}
|
||||||
).also {
|
).also {
|
||||||
val payment = paymentRepository.findByIdOrNull(paymentCreateResponse.paymentId)
|
val payment = paymentRepository.findByIdOrNull(paymentEntity.id)
|
||||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||||
val canceledPayment =
|
val canceledPayment =
|
||||||
canceledPaymentRepository.findByPaymentId(paymentCreateResponse.paymentId)
|
canceledPaymentRepository.findByPaymentId(paymentEntity.id)
|
||||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||||
|
|
||||||
payment.status shouldBe PaymentStatus.CANCELED
|
payment.status shouldBe PaymentStatus.CANCELED
|
||||||
@ -319,7 +117,7 @@ class PaymentAPITest(
|
|||||||
private fun createPayment(
|
private fun createPayment(
|
||||||
request: PaymentConfirmRequest,
|
request: PaymentConfirmRequest,
|
||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
): PaymentCreateResponse {
|
): PaymentEntity {
|
||||||
every {
|
every {
|
||||||
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
tosspayClient.confirm(request.paymentKey, request.orderId, request.amount)
|
||||||
} returns PaymentFixture.confirmResponse(
|
} returns PaymentFixture.confirmResponse(
|
||||||
@ -331,49 +129,10 @@ class PaymentAPITest(
|
|||||||
transferDetail = null,
|
transferDetail = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val paymentResponse = paymentService.requestConfirm(request)
|
val paymentEvent = paymentService.requestConfirm(reservationId, request).toEvent(reservationId)
|
||||||
return paymentService.savePayment(reservationId, paymentResponse)
|
|
||||||
|
return paymentRepository.save(paymentEvent.toEntity(IDGenerator.create())).also {
|
||||||
|
paymentDetailRepository.save(paymentEvent.toDetailEntity(IDGenerator.create(), it.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package com.sangdol.roomescape.supports
|
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.business.domain.PaymentMethod
|
||||||
import com.sangdol.roomescape.payment.dto.*
|
import com.sangdol.roomescape.payment.dto.*
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
import com.sangdol.roomescape.payment.infrastructure.persistence.*
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEntity
|
import com.sangdol.roomescape.payment.mapper.toDetailEntity
|
||||||
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentRepository
|
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.payment.mapper.toResponse
|
||||||
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
|
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
@ -33,7 +33,8 @@ class DummyInitializer(
|
|||||||
private val scheduleRepository: ScheduleRepository,
|
private val scheduleRepository: ScheduleRepository,
|
||||||
private val reservationRepository: ReservationRepository,
|
private val reservationRepository: ReservationRepository,
|
||||||
private val paymentRepository: PaymentRepository,
|
private val paymentRepository: PaymentRepository,
|
||||||
private val paymentWriter: PaymentWriter
|
private val paymentDetailRepository: PaymentDetailRepository,
|
||||||
|
private val canceledPaymentRepository: CanceledPaymentRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun createStore(
|
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(
|
fun createPayment(
|
||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
request: PaymentConfirmRequest = PaymentFixture.confirmRequest,
|
request: PaymentConfirmRequest = PaymentFixture.confirmRequest,
|
||||||
@ -204,12 +184,10 @@ class DummyInitializer(
|
|||||||
transferDetail = transferDetail
|
transferDetail = transferDetail
|
||||||
)
|
)
|
||||||
|
|
||||||
val payment = paymentWriter.createPayment(
|
val paymentEvent = clientConfirmResponse.toEvent(reservationId)
|
||||||
reservationId = reservationId,
|
val payment = paymentRepository.save(paymentEvent.toEntity(IDGenerator.create()))
|
||||||
paymentGatewayResponse = clientConfirmResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
return payment.toResponse(detail = detail.toResponse(), cancel = null)
|
||||||
}
|
}
|
||||||
@ -227,11 +205,14 @@ class DummyInitializer(
|
|||||||
cancelReason = cancelReason,
|
cancelReason = cancelReason,
|
||||||
)
|
)
|
||||||
|
|
||||||
return paymentWriter.cancel(
|
|
||||||
userId,
|
return clientCancelResponse.cancels.toEntity(
|
||||||
payment,
|
id = IDGenerator.create(),
|
||||||
requestedAt = Instant.now(),
|
paymentId = payment.id,
|
||||||
clientCancelResponse
|
cancelRequestedAt = Instant.now(),
|
||||||
)
|
canceledBy = userId
|
||||||
|
).also {
|
||||||
|
canceledPaymentRepository.save(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
package com.sangdol.roomescape.supports
|
package com.sangdol.roomescape.supports
|
||||||
|
|
||||||
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminRepository
|
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.payment.infrastructure.persistence.PaymentRepository
|
||||||
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||||
@ -43,13 +44,7 @@ abstract class FunSpecSpringbootTest(
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@Autowired
|
@Autowired
|
||||||
private lateinit var userRepository: UserRepository
|
lateinit var testAuthUtil: TestAuthUtil
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private lateinit var adminRepository: AdminRepository
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private lateinit var storeRepository: StoreRepository
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
lateinit var dummyInitializer: DummyInitializer
|
lateinit var dummyInitializer: DummyInitializer
|
||||||
@ -57,32 +52,40 @@ abstract class FunSpecSpringbootTest(
|
|||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
var port: Int = 0
|
var port: Int = 0
|
||||||
|
|
||||||
lateinit var testAuthUtil: TestAuthUtil
|
|
||||||
|
|
||||||
override suspend fun beforeSpec(spec: Spec) {
|
override suspend fun beforeSpec(spec: Spec) {
|
||||||
RestAssured.port = port
|
RestAssured.port = port
|
||||||
testAuthUtil = TestAuthUtil(userRepository, adminRepository, storeRepository)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
class TestConfig {
|
class TestConfig {
|
||||||
|
@Bean
|
||||||
|
fun testAuthUtil(
|
||||||
|
userRepository: UserRepository,
|
||||||
|
adminRepository: AdminRepository,
|
||||||
|
storeRepository: StoreRepository
|
||||||
|
): TestAuthUtil {
|
||||||
|
return TestAuthUtil(userRepository, adminRepository, storeRepository)
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun dummyInitializer(
|
fun dummyInitializer(
|
||||||
storeRepository: StoreRepository,
|
storeRepository: StoreRepository,
|
||||||
themeRepository: ThemeRepository,
|
themeRepository: ThemeRepository,
|
||||||
scheduleRepository: ScheduleRepository,
|
scheduleRepository: ScheduleRepository,
|
||||||
reservationRepository: ReservationRepository,
|
reservationRepository: ReservationRepository,
|
||||||
paymentWriter: PaymentWriter,
|
paymentRepository: PaymentRepository,
|
||||||
paymentRepository: PaymentRepository
|
paymentDetailRepository: PaymentDetailRepository,
|
||||||
|
canceledPaymentRepository: CanceledPaymentRepository
|
||||||
): DummyInitializer {
|
): DummyInitializer {
|
||||||
return DummyInitializer(
|
return DummyInitializer(
|
||||||
themeRepository = themeRepository,
|
themeRepository = themeRepository,
|
||||||
scheduleRepository = scheduleRepository,
|
scheduleRepository = scheduleRepository,
|
||||||
reservationRepository = reservationRepository,
|
reservationRepository = reservationRepository,
|
||||||
paymentWriter = paymentWriter,
|
|
||||||
paymentRepository = paymentRepository,
|
paymentRepository = paymentRepository,
|
||||||
storeRepository = storeRepository
|
storeRepository = storeRepository,
|
||||||
|
paymentDetailRepository = paymentDetailRepository,
|
||||||
|
canceledPaymentRepository = canceledPaymentRepository
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user