generated from pricelees/issue-pr-template
[#35] 결제 스키마 재정의 & 예약 조회 페이지 개선 #36
@ -0,0 +1,21 @@
|
|||||||
|
package roomescape.payment.implement
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import roomescape.payment.infrastructure.client.v2.*
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class PaymentRequester(
|
||||||
|
private val client: TosspaymentClientV2
|
||||||
|
) {
|
||||||
|
fun requestConfirmPayment(paymentKey: String, orderId: String, amount: Int): PaymentConfirmResponse {
|
||||||
|
val request = PaymentConfirmRequest(paymentKey, orderId, amount)
|
||||||
|
|
||||||
|
return client.confirm(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestCancelPayment(paymentKey: String, amount: Int, cancelReason: String): PaymentCancelResponseV2 {
|
||||||
|
val request = PaymentCancelRequestV2(paymentKey, amount, cancelReason)
|
||||||
|
|
||||||
|
return client.cancel(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,33 +10,18 @@ import roomescape.payment.exception.PaymentException
|
|||||||
import roomescape.payment.infrastructure.client.v2.*
|
import roomescape.payment.infrastructure.client.v2.*
|
||||||
import roomescape.payment.infrastructure.common.PaymentMethod
|
import roomescape.payment.infrastructure.common.PaymentMethod
|
||||||
import roomescape.payment.infrastructure.persistence.v2.*
|
import roomescape.payment.infrastructure.persistence.v2.*
|
||||||
import roomescape.reservation.web.ReservationCancelRequest
|
|
||||||
import roomescape.reservation.web.ReservationPaymentRequest
|
import roomescape.reservation.web.ReservationPaymentRequest
|
||||||
import roomescape.reservation.web.toPaymentConfirmRequest
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
class PaymentWriterV2(
|
class PaymentWriterV2(
|
||||||
private val paymentClient: TosspaymentClientV2,
|
|
||||||
private val paymentRepository: PaymentRepositoryV2,
|
private val paymentRepository: PaymentRepositoryV2,
|
||||||
private val paymentDetailRepository: PaymentDetailRepository,
|
private val paymentDetailRepository: PaymentDetailRepository,
|
||||||
private val canceledPaymentRepository: CanceledPaymentRepositoryV2,
|
private val canceledPaymentRepository: CanceledPaymentRepositoryV2,
|
||||||
private val tsidFactory: TsidFactory,
|
private val tsidFactory: TsidFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun requestConfirmPayment(
|
|
||||||
reservationId: Long,
|
|
||||||
request: ReservationPaymentRequest
|
|
||||||
): PaymentConfirmResponse {
|
|
||||||
log.debug { "[PaymentWriterV2.requestConfirmPayment] 결제 승인 요청 시작: reservationId=${reservationId}, paymentKey=${request.paymentKey}" }
|
|
||||||
|
|
||||||
return paymentClient.confirm(request.toPaymentConfirmRequest()).also {
|
|
||||||
log.debug { "[PaymentWriterV2.requestConfirmPayment] 결제 승인 요청 완료: response=$it" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createPayment(
|
fun createPayment(
|
||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
request: ReservationPaymentRequest,
|
request: ReservationPaymentRequest,
|
||||||
@ -48,12 +33,12 @@ class PaymentWriterV2(
|
|||||||
id = tsidFactory.next(), reservationId, request.orderId, request.paymentType
|
id = tsidFactory.next(), reservationId, request.orderId, request.paymentType
|
||||||
).also {
|
).also {
|
||||||
paymentRepository.save(it)
|
paymentRepository.save(it)
|
||||||
saveDetail(paymentConfirmResponse, it.id)
|
createDetail(paymentConfirmResponse, it.id)
|
||||||
log.debug { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 완료: reservationId=${reservationId}, paymentId=${it.id}" }
|
log.debug { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 완료: reservationId=${reservationId}, paymentId=${it.id}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveDetail(
|
private fun createDetail(
|
||||||
paymentResponse: PaymentConfirmResponse,
|
paymentResponse: PaymentConfirmResponse,
|
||||||
paymentId: Long,
|
paymentId: Long,
|
||||||
): PaymentDetailEntity {
|
): PaymentDetailEntity {
|
||||||
@ -72,50 +57,24 @@ class PaymentWriterV2(
|
|||||||
throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
|
throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestCancelPayment(
|
|
||||||
reservationId: Long,
|
|
||||||
request: ReservationCancelRequest,
|
|
||||||
): PaymentCancelResponseV2 {
|
|
||||||
log.debug { "[PaymentWriterV2.requestConfirmPayment] 결제 취소 요청 시작: reservationId=$reservationId, request=${request}" }
|
|
||||||
|
|
||||||
val payment: PaymentEntityV2 = paymentRepository.findByReservationId(reservationId)
|
|
||||||
?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
|
|
||||||
val paymentCancelRequest = PaymentCancelRequestV2(
|
|
||||||
paymentKey = payment.paymentKey,
|
|
||||||
cancelReason = request.cancelReason,
|
|
||||||
amount = payment.totalAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
return paymentClient.cancel(paymentCancelRequest).also {
|
|
||||||
log.debug { "[PaymentWriterV2.requestCancelPayment] 결제 취소 요청 완료: reservationId=${reservationId}, paymentKey=${payment.paymentKey}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createCanceledPayment(
|
fun createCanceledPayment(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
reservationId: Long,
|
payment: PaymentEntityV2,
|
||||||
request: ReservationCancelRequest,
|
requestedAt: LocalDateTime,
|
||||||
paymentCancelResponse: PaymentCancelResponseV2,
|
cancelResponse: PaymentCancelResponseV2
|
||||||
requestedAt: LocalDateTime
|
|
||||||
) {
|
) {
|
||||||
val payment: PaymentEntityV2 = paymentRepository.findByReservationId(reservationId)
|
log.debug { "[PaymentWriterV2.cancelPayment] 취소된 결제 정보 저장 시작: paymentId=${payment.id}, paymentKey=${payment.paymentKey}" }
|
||||||
?.also { it.cancel() }
|
|
||||||
?: throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
|
|
||||||
val cancelDetail: CancelDetail = paymentCancelResponse.cancels
|
val canceledPayment: CanceledPaymentEntityV2 = cancelResponse.cancels.toEntity(
|
||||||
|
|
||||||
CanceledPaymentEntityV2(
|
|
||||||
id = tsidFactory.next(),
|
id = tsidFactory.next(),
|
||||||
canceledAt = cancelDetail.canceledAt,
|
|
||||||
requestedAt = requestedAt,
|
|
||||||
paymentId = payment.id,
|
paymentId = payment.id,
|
||||||
canceledBy = memberId,
|
cancelRequestedAt = requestedAt,
|
||||||
cancelReason = request.cancelReason,
|
canceledBy = memberId
|
||||||
cancelAmount = cancelDetail.cancelAmount,
|
)
|
||||||
cardDiscountAmount = cancelDetail.cardDiscountAmount,
|
|
||||||
transferDiscountAmount = cancelDetail.transferDiscountAmount,
|
canceledPaymentRepository.save(canceledPayment).also {
|
||||||
easypayDiscountAmount = cancelDetail.easyPayDiscountAmount,
|
payment.cancel()
|
||||||
).also { canceledPaymentRepository.save(it) }
|
log.debug { "[PaymentWriterV2.cancelPayment] 취소된 결제 정보 저장 완료: paymentId=${payment.id}, canceledPaymentId=${it.id}, paymentKey=${payment.paymentKey}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import jakarta.transaction.Transactional
|
import jakarta.transaction.Transactional
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import roomescape.common.util.TransactionExecutionUtil
|
import roomescape.common.util.TransactionExecutionUtil
|
||||||
|
import roomescape.payment.implement.PaymentFinderV2
|
||||||
|
import roomescape.payment.implement.PaymentRequester
|
||||||
import roomescape.payment.implement.PaymentWriterV2
|
import roomescape.payment.implement.PaymentWriterV2
|
||||||
import roomescape.payment.infrastructure.client.v2.PaymentConfirmResponse
|
import roomescape.payment.infrastructure.client.v2.PaymentConfirmResponse
|
||||||
import roomescape.payment.infrastructure.persistence.v2.PaymentEntityV2
|
import roomescape.payment.infrastructure.persistence.v2.PaymentEntityV2
|
||||||
@ -13,7 +15,6 @@ import roomescape.reservation.implement.ReservationWriter
|
|||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
import roomescape.reservation.web.*
|
import roomescape.reservation.web.*
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@ -21,10 +22,11 @@ private val log: KLogger = KotlinLogging.logger {}
|
|||||||
class ReservationWithPaymentServiceV2(
|
class ReservationWithPaymentServiceV2(
|
||||||
private val reservationWriter: ReservationWriter,
|
private val reservationWriter: ReservationWriter,
|
||||||
private val reservationFinder: ReservationFinder,
|
private val reservationFinder: ReservationFinder,
|
||||||
|
private val paymentRequester: PaymentRequester,
|
||||||
|
private val paymentFinder: PaymentFinderV2,
|
||||||
private val paymentWriter: PaymentWriterV2,
|
private val paymentWriter: PaymentWriterV2,
|
||||||
private val transactionExecutionUtil: TransactionExecutionUtil
|
private val transactionExecutionUtil: TransactionExecutionUtil,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun createPendingReservation(memberId: Long, request: ReservationCreateRequest): ReservationCreateResponseV2 {
|
fun createPendingReservation(memberId: Long, request: ReservationCreateRequest): ReservationCreateResponseV2 {
|
||||||
log.info {
|
log.info {
|
||||||
@ -59,29 +61,20 @@ class ReservationWithPaymentServiceV2(
|
|||||||
"예약 결제 시작: memberId=$memberId, reservationId=$reservationId, request=$request"
|
"예약 결제 시작: memberId=$memberId, reservationId=$reservationId, request=$request"
|
||||||
}
|
}
|
||||||
|
|
||||||
val paymentConfirmResponse = paymentWriter.requestConfirmPayment(reservationId, request)
|
val paymentConfirmResponse: PaymentConfirmResponse = paymentRequester.requestConfirmPayment(
|
||||||
|
paymentKey = request.paymentKey,
|
||||||
return transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
orderId = request.orderId,
|
||||||
savePayment(memberId, reservationId, request, paymentConfirmResponse)
|
amount = request.amount
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun savePayment(
|
|
||||||
memberId: Long,
|
|
||||||
reservationId: Long,
|
|
||||||
request: ReservationPaymentRequest,
|
|
||||||
paymentConfirmResponse: PaymentConfirmResponse
|
|
||||||
): ReservationPaymentResponse {
|
|
||||||
val reservation =
|
|
||||||
reservationFinder.findPendingReservation(reservationId, memberId).also { it.confirm() }
|
|
||||||
val payment: PaymentEntityV2 = paymentWriter.createPayment(
|
|
||||||
reservationId = reservationId,
|
|
||||||
request = request,
|
|
||||||
paymentConfirmResponse = paymentConfirmResponse
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return ReservationPaymentResponse(reservationId, reservation.status, payment.id, payment.status)
|
return transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
.also { log.info { "[ReservationWithPaymentServiceV2.payReservation] 예약 결제 완료: response=${it}" } }
|
val payment: PaymentEntityV2 = paymentWriter.createPayment(reservationId, request, paymentConfirmResponse)
|
||||||
|
val reservation: ReservationEntity =
|
||||||
|
reservationWriter.modifyStatusFromPendingToConfirmed(reservationId, memberId)
|
||||||
|
|
||||||
|
ReservationPaymentResponse(reservationId, reservation.status, payment.id, payment.status)
|
||||||
|
.also { log.info { "[ReservationWithPaymentServiceV2.payReservation] 예약 결제 완료: response=${it}" } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelReservation(
|
fun cancelReservation(
|
||||||
@ -89,17 +82,27 @@ class ReservationWithPaymentServiceV2(
|
|||||||
reservationId: Long,
|
reservationId: Long,
|
||||||
request: ReservationCancelRequest
|
request: ReservationCancelRequest
|
||||||
) {
|
) {
|
||||||
val requestedAt: LocalDateTime = LocalDateTime.now()
|
|
||||||
log.info {
|
log.info {
|
||||||
"[ReservationWithPaymentServiceV2.cancelReservation] " +
|
"[ReservationWithPaymentServiceV2.cancelReservation] " +
|
||||||
"예약 취소 시작: memberId=$memberId, reservationId=$reservationId, request=$request"
|
"예약 취소 시작: memberId=$memberId, reservationId=$reservationId, request=$request"
|
||||||
}
|
}
|
||||||
|
|
||||||
val paymentCancelResponse = paymentWriter.requestCancelPayment(reservationId, request)
|
val reservation: ReservationEntity = reservationFinder.findById(reservationId)
|
||||||
|
val payment: PaymentEntityV2 = paymentFinder.findPaymentByReservationId(reservationId)
|
||||||
|
val paymentCancelResponse = paymentRequester.requestCancelPayment(
|
||||||
|
paymentKey = payment.paymentKey,
|
||||||
|
amount = payment.totalAmount,
|
||||||
|
cancelReason = request.cancelReason
|
||||||
|
)
|
||||||
|
|
||||||
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
transactionExecutionUtil.withNewTransaction(isReadOnly = false) {
|
||||||
paymentWriter.createCanceledPayment(memberId, reservationId, request, paymentCancelResponse, requestedAt)
|
paymentWriter.createCanceledPayment(memberId, payment, request.requestedAt, paymentCancelResponse)
|
||||||
reservationFinder.findById(reservationId).also { reservationWriter.cancelByUser(it, memberId) }
|
reservationWriter.modifyStatusToCanceledByUser(reservation, memberId)
|
||||||
|
}.also {
|
||||||
|
log.info {
|
||||||
|
"[ReservationWithPaymentServiceV2.cancelReservation] " +
|
||||||
|
"예약 취소 완료: reservationId=$reservationId, memberId=$memberId, cancelReason=${request.cancelReason}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,4 +153,26 @@ class ReservationValidator(
|
|||||||
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_PENDING)
|
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_PENDING)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun validateIsPending(reservation: ReservationEntity) {
|
||||||
|
log.debug { "[ReservationValidator.validateIsPending] 시작: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
|
||||||
|
if (reservation.status != ReservationStatus.PENDING) {
|
||||||
|
log.warn { "[ReservationValidator.validateIsPending] 예약 상태가 결제 대기 중이 아님: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_PENDING)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateIsPending] 완료: reservationId=${reservation.id}, status=${reservation.status}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateModifyAuthority(reservation: ReservationEntity, memberId: Long) {
|
||||||
|
log.debug { "[ReservationValidator.validateModifyAuthority] 시작: reservationId=${reservation.id}, memberId=$memberId" }
|
||||||
|
|
||||||
|
if (reservation.member.id != memberId) {
|
||||||
|
log.error { "[ReservationValidator.validateModifyAuthority] 예약자 본인이 아님: reservationId=${reservation.id}, reservation.memberId=${reservation.member.id} memberId=$memberId" }
|
||||||
|
throw ReservationException(ReservationErrorCode.NOT_RESERVATION_OWNER)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "[ReservationValidator.validateModifyAuthority] 완료: reservationId=${reservation.id}, memberId=$memberId" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,12 @@ package roomescape.reservation.implement
|
|||||||
import com.github.f4b6a3.tsid.TsidFactory
|
import com.github.f4b6a3.tsid.TsidFactory
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import roomescape.common.config.next
|
import roomescape.common.config.next
|
||||||
import roomescape.member.implement.MemberFinder
|
import roomescape.member.implement.MemberFinder
|
||||||
|
import roomescape.reservation.exception.ReservationErrorCode
|
||||||
|
import roomescape.reservation.exception.ReservationException
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
import roomescape.reservation.infrastructure.persistence.ReservationRepository
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
import roomescape.reservation.infrastructure.persistence.ReservationStatus
|
||||||
@ -100,7 +103,7 @@ class ReservationWriter(
|
|||||||
log.debug { "[ReservationWriter.confirm] 완료: reservationId=$reservationId, status=${ReservationStatus.CONFIRMED_PAYMENT_REQUIRED}" }
|
log.debug { "[ReservationWriter.confirm] 완료: reservationId=$reservationId, status=${ReservationStatus.CONFIRMED_PAYMENT_REQUIRED}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelByUser(reservation: ReservationEntity, requesterId: Long) {
|
fun modifyStatusToCanceledByUser(reservation: ReservationEntity, requesterId: Long) {
|
||||||
log.debug { "[ReservationWriter.cancel] 예약 취소 시작: reservationId=${reservation.id}, requesterId=$requesterId" }
|
log.debug { "[ReservationWriter.cancel] 예약 취소 시작: reservationId=${reservation.id}, requesterId=$requesterId" }
|
||||||
|
|
||||||
memberFinder.findById(requesterId)
|
memberFinder.findById(requesterId)
|
||||||
@ -110,4 +113,19 @@ class ReservationWriter(
|
|||||||
log.debug { "[ReservationWriter.cancel] 예약 취소 완료: reservationId=${reservation.id}" }
|
log.debug { "[ReservationWriter.cancel] 예약 취소 완료: reservationId=${reservation.id}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun modifyStatusFromPendingToConfirmed(reservationId: Long, memberId: Long): ReservationEntity {
|
||||||
|
log.debug { "[ReservationWriter.confirmPendingReservation] 시작: reservationId=$reservationId, memberId=$memberId" }
|
||||||
|
|
||||||
|
return reservationRepository.findByIdOrNull(reservationId)?.also {
|
||||||
|
reservationValidator.validateIsPending(it)
|
||||||
|
reservationValidator.validateModifyAuthority(it, memberId)
|
||||||
|
|
||||||
|
it.confirm()
|
||||||
|
log.debug { "[ReservationWriter.confirmPendingReservation] 완료: reservationId=${it.id}, status=${it.status}" }
|
||||||
|
} ?: run {
|
||||||
|
log.warn { "[ReservationWriter.confirmPendingReservation] 예약을 찾을 수 없음: reservationId=$reservationId" }
|
||||||
|
throw ReservationException(ReservationErrorCode.RESERVATION_NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user