generated from pricelees/issue-pr-template
[#41] 예약 스키마 재정의 #42
@ -1,48 +0,0 @@
|
|||||||
package roomescape.payment.implement
|
|
||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class PaymentFinder(
|
|
||||||
private val paymentRepository: PaymentRepository,
|
|
||||||
private val canceledPaymentRepository: CanceledPaymentRepository,
|
|
||||||
) {
|
|
||||||
fun existsPaymentByReservationId(reservationId: Long): Boolean {
|
|
||||||
log.debug { "[PaymentFinder.existsPaymentByReservationId] 시작: reservationId=$reservationId" }
|
|
||||||
|
|
||||||
return paymentRepository.existsByReservationId(reservationId)
|
|
||||||
.also { log.debug { "[PaymentFinder.existsPaymentByReservationId] 완료: reservationId=$reservationId, isExist=$it" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findByReservationId(reservationId: Long): PaymentEntity {
|
|
||||||
log.debug { "[PaymentFinder.findByReservationId] 시작: reservationId=$reservationId" }
|
|
||||||
|
|
||||||
return paymentRepository.findByReservationId(reservationId)
|
|
||||||
?.also { log.debug { "[PaymentFinder.findByReservationId] 완료: reservationId=$reservationId" } }
|
|
||||||
?: run {
|
|
||||||
log.warn { "[PaymentFinder.findByReservationId] 실패: reservationId=$reservationId" }
|
|
||||||
throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findCanceledByKey(paymentKey: String): CanceledPaymentEntity {
|
|
||||||
log.debug { "[PaymentFinder.findCanceledByKey] 시작: paymentKey=$paymentKey" }
|
|
||||||
|
|
||||||
return canceledPaymentRepository.findByPaymentKey(paymentKey)
|
|
||||||
?.also { log.debug { "[PaymentFinder.findCanceledByKey] 완료: canceledPaymentId=${it.id}" } }
|
|
||||||
?: run {
|
|
||||||
log.warn { "[PaymentFinder.findCanceledByKey] 실패: paymentKey=$paymentKey" }
|
|
||||||
throw PaymentException(PaymentErrorCode.CANCELED_PAYMENT_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
package roomescape.payment.implement
|
|
||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.CanceledPaymentEntityV2
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.CanceledPaymentRepositoryV2
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.PaymentDetailEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.PaymentDetailRepository
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.PaymentEntityV2
|
|
||||||
import roomescape.payment.infrastructure.persistence.v2.PaymentRepositoryV2
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class PaymentFinderV2(
|
|
||||||
private val paymentRepository: PaymentRepositoryV2,
|
|
||||||
private val paymentDetailRepository: PaymentDetailRepository,
|
|
||||||
private val canceledPaymentRepository: CanceledPaymentRepositoryV2
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun findPaymentByReservationId(reservationId: Long): PaymentEntityV2 {
|
|
||||||
log.debug { "[PaymentFinderV2.findByReservationId] 시작: reservationId=$reservationId" }
|
|
||||||
|
|
||||||
return paymentRepository.findByReservationId(reservationId)?.also {
|
|
||||||
log.debug { "[PaymentFinderV2.findByReservationId] 완료: reservationId=$reservationId, paymentId=${it.id}" }
|
|
||||||
} ?: run {
|
|
||||||
log.warn { "[PaymentFinderV2.findByReservationId] 실패: reservationId=$reservationId" }
|
|
||||||
throw PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findPaymentDetailByPaymentId(paymentId: Long): PaymentDetailEntity {
|
|
||||||
log.debug { "[PaymentFinderV2.findPaymentDetailByPaymentId] 시작: paymentId=$paymentId" }
|
|
||||||
|
|
||||||
return paymentDetailRepository.findByPaymentId(paymentId)?.also {
|
|
||||||
log.debug { "[PaymentFinderV2.findPaymentDetailByPaymentId] 완료: paymentId=$paymentId, detailId=${it.id}" }
|
|
||||||
} ?: run {
|
|
||||||
log.warn { "[PaymentFinderV2.findPaymentDetailByPaymentId] 실패: paymentId=$paymentId" }
|
|
||||||
throw PaymentException(PaymentErrorCode.PAYMENT_DETAIL_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findCanceledPaymentByPaymentIdOrNull(paymentId: Long): CanceledPaymentEntityV2? {
|
|
||||||
log.debug { "[PaymentFinderV2.findCanceledPaymentByKey] 시작: paymentId=$paymentId" }
|
|
||||||
|
|
||||||
return canceledPaymentRepository.findByPaymentId(paymentId)?.also {
|
|
||||||
log.debug { "[PaymentFinderV2.findCanceledPaymentByKey] 완료: paymentId=$paymentId, canceledPaymentId=${it.id}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
package roomescape.payment.implement
|
|
||||||
|
|
||||||
import com.github.f4b6a3.tsid.TsidFactory
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import roomescape.common.config.next
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class PaymentWriter(
|
|
||||||
private val paymentRepository: PaymentRepository,
|
|
||||||
private val canceledPaymentRepository: CanceledPaymentRepository,
|
|
||||||
private val tsidFactory: TsidFactory,
|
|
||||||
) {
|
|
||||||
fun create(
|
|
||||||
paymentKey: String,
|
|
||||||
orderId: String,
|
|
||||||
totalAmount: Long,
|
|
||||||
approvedAt: OffsetDateTime,
|
|
||||||
reservation: ReservationEntity
|
|
||||||
): PaymentEntity {
|
|
||||||
log.debug { "[PaymentWriter.create] 시작: paymentKey=${paymentKey}, reservationId=${reservation.id}" }
|
|
||||||
|
|
||||||
val payment = PaymentEntity(
|
|
||||||
_id = tsidFactory.next(),
|
|
||||||
orderId = orderId,
|
|
||||||
paymentKey = paymentKey,
|
|
||||||
totalAmount = totalAmount,
|
|
||||||
reservation = reservation,
|
|
||||||
approvedAt = approvedAt
|
|
||||||
)
|
|
||||||
|
|
||||||
return paymentRepository.save(payment)
|
|
||||||
.also { log.debug { "[PaymentWriter.create] 완료: paymentId=${it.id}, reservationId=${reservation.id}" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createCanceled(
|
|
||||||
payment: PaymentEntity,
|
|
||||||
cancelReason: String,
|
|
||||||
canceledAt: OffsetDateTime,
|
|
||||||
): CanceledPaymentEntity = createCanceled(
|
|
||||||
cancelReason = cancelReason,
|
|
||||||
canceledAt = canceledAt,
|
|
||||||
cancelAmount = payment.totalAmount,
|
|
||||||
approvedAt = payment.approvedAt,
|
|
||||||
paymentKey = payment.paymentKey
|
|
||||||
)
|
|
||||||
|
|
||||||
fun createCanceled(
|
|
||||||
cancelReason: String,
|
|
||||||
cancelAmount: Long,
|
|
||||||
canceledAt: OffsetDateTime,
|
|
||||||
approvedAt: OffsetDateTime,
|
|
||||||
paymentKey: String,
|
|
||||||
): CanceledPaymentEntity {
|
|
||||||
log.debug { "[PaymentWriter.createCanceled] 시작: paymentKey=$paymentKey cancelAmount=$cancelAmount" }
|
|
||||||
|
|
||||||
val canceledPayment = CanceledPaymentEntity(
|
|
||||||
_id = tsidFactory.next(),
|
|
||||||
paymentKey = paymentKey,
|
|
||||||
cancelReason = cancelReason,
|
|
||||||
cancelAmount = cancelAmount,
|
|
||||||
approvedAt = approvedAt,
|
|
||||||
canceledAt = canceledAt
|
|
||||||
)
|
|
||||||
|
|
||||||
return canceledPaymentRepository.save(canceledPayment)
|
|
||||||
.also {
|
|
||||||
paymentRepository.deleteByPaymentKey(paymentKey)
|
|
||||||
log.debug { "[PaymentWriter.createCanceled] 완료: paymentKey=${paymentKey}, canceledPaymentId=${it.id}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
import com.fasterxml.jackson.core.TreeNode
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationContext
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
|
||||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
|
||||||
import roomescape.payment.web.PaymentCancelResponse
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
class PaymentCancelResponseDeserializer(
|
|
||||||
vc: Class<PaymentCancelResponse>? = null
|
|
||||||
) : StdDeserializer<PaymentCancelResponse>(vc) {
|
|
||||||
override fun deserialize(
|
|
||||||
jsonParser: JsonParser,
|
|
||||||
deserializationContext: DeserializationContext?
|
|
||||||
): PaymentCancelResponse {
|
|
||||||
val cancels: JsonNode = jsonParser.codec.readTree<TreeNode>(jsonParser)
|
|
||||||
.get("cancels")
|
|
||||||
.get(0) as JsonNode
|
|
||||||
|
|
||||||
return PaymentCancelResponse(
|
|
||||||
cancels.get("cancelStatus").asText(),
|
|
||||||
cancels.get("cancelReason").asText(),
|
|
||||||
cancels.get("cancelAmount").asLong(),
|
|
||||||
OffsetDateTime.parse(cancels.get("canceledAt").asText())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
|
||||||
import org.springframework.http.HttpRequest
|
|
||||||
import org.springframework.http.HttpStatusCode
|
|
||||||
import org.springframework.http.MediaType
|
|
||||||
import org.springframework.http.client.ClientHttpResponse
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import org.springframework.web.client.RestClient
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.web.PaymentCancelRequest
|
|
||||||
import roomescape.payment.web.PaymentCancelResponse
|
|
||||||
import java.util.Map
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class TossPaymentClient(
|
|
||||||
private val objectMapper: ObjectMapper,
|
|
||||||
tossPaymentClientBuilder: RestClient.Builder,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
private const val CONFIRM_URL: String = "/v1/payments/confirm"
|
|
||||||
private const val CANCEL_URL: String = "/v1/payments/{paymentKey}/cancel"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tossPaymentClient: RestClient = tossPaymentClientBuilder.build()
|
|
||||||
|
|
||||||
fun confirm(paymentRequest: PaymentApproveRequest): PaymentApproveResponse {
|
|
||||||
logPaymentInfo(paymentRequest)
|
|
||||||
|
|
||||||
return tossPaymentClient.post()
|
|
||||||
.uri(CONFIRM_URL)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(paymentRequest)
|
|
||||||
.retrieve()
|
|
||||||
.onStatus(
|
|
||||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
|
||||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res, "confirm") }
|
|
||||||
)
|
|
||||||
.body(PaymentApproveResponse::class.java)
|
|
||||||
?: run {
|
|
||||||
log.error { "[TossPaymentClient] 응답 변환 오류" }
|
|
||||||
throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel(cancelRequest: PaymentCancelRequest): PaymentCancelResponse {
|
|
||||||
logPaymentCancelInfo(cancelRequest)
|
|
||||||
val param = Map.of<String, String>("cancelReason", cancelRequest.cancelReason)
|
|
||||||
|
|
||||||
return tossPaymentClient.post()
|
|
||||||
.uri(CANCEL_URL, cancelRequest.paymentKey)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(param)
|
|
||||||
.retrieve()
|
|
||||||
.onStatus(
|
|
||||||
{ status: HttpStatusCode -> status.is4xxClientError || status.is5xxServerError },
|
|
||||||
{ req: HttpRequest, res: ClientHttpResponse -> handlePaymentError(res, "cancel") }
|
|
||||||
)
|
|
||||||
.body(PaymentCancelResponse::class.java)
|
|
||||||
?: run {
|
|
||||||
log.error { "[TossPaymentClient] 응답 변환 오류" }
|
|
||||||
throw PaymentException(PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun logPaymentInfo(paymentRequest: PaymentApproveRequest) {
|
|
||||||
log.info {
|
|
||||||
"[TossPaymentClient.confirm] 결제 승인 요청: request: $paymentRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun logPaymentCancelInfo(cancelRequest: PaymentCancelRequest) {
|
|
||||||
log.info {
|
|
||||||
"[TossPaymentClient.cancel] 결제 취소 요청: request: $cancelRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handlePaymentError(
|
|
||||||
res: ClientHttpResponse,
|
|
||||||
calledBy: String
|
|
||||||
): Nothing {
|
|
||||||
getErrorCodeByHttpStatus(res.statusCode).also {
|
|
||||||
logTossPaymentError(res, calledBy)
|
|
||||||
throw PaymentException(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun logTossPaymentError(res: ClientHttpResponse, calledBy: String): TossPaymentErrorResponse {
|
|
||||||
val body = res.body
|
|
||||||
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
|
|
||||||
body.close()
|
|
||||||
|
|
||||||
log.error { "[TossPaymentClient.$calledBy] 요청 실패: response: $errorResponse" }
|
|
||||||
return errorResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getErrorCodeByHttpStatus(statusCode: HttpStatusCode): PaymentErrorCode {
|
|
||||||
if (statusCode.is4xxClientError) {
|
|
||||||
return PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
|
||||||
}
|
|
||||||
return PaymentErrorCode.PAYMENT_PROVIDER_ERROR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
data class TossPaymentErrorResponse(
|
|
||||||
val code: String,
|
|
||||||
val message: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PaymentApproveRequest(
|
|
||||||
val paymentKey: String,
|
|
||||||
val orderId: String,
|
|
||||||
val amount: Long,
|
|
||||||
val paymentType: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class PaymentApproveResponse(
|
|
||||||
val paymentKey: String,
|
|
||||||
val orderId: String,
|
|
||||||
val totalAmount: Long,
|
|
||||||
val approvedAt: OffsetDateTime
|
|
||||||
)
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence.v2
|
|
||||||
|
|
||||||
import jakarta.persistence.Entity
|
|
||||||
import jakarta.persistence.Table
|
|
||||||
import roomescape.common.entity.PersistableBaseEntity
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "canceled_payment1")
|
|
||||||
class CanceledPaymentEntityV2(
|
|
||||||
id: Long,
|
|
||||||
|
|
||||||
val paymentId: Long,
|
|
||||||
val requestedAt: LocalDateTime,
|
|
||||||
val canceledAt: OffsetDateTime,
|
|
||||||
val canceledBy: Long,
|
|
||||||
val cancelReason: String,
|
|
||||||
val cancelAmount: Int,
|
|
||||||
val cardDiscountAmount: Int,
|
|
||||||
val transferDiscountAmount: Int,
|
|
||||||
val easypayDiscountAmount: Int,
|
|
||||||
) : PersistableBaseEntity(id)
|
|
||||||
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence.v2
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
|
|
||||||
interface CanceledPaymentRepositoryV2 : JpaRepository<CanceledPaymentEntityV2, Long> {
|
|
||||||
fun findByPaymentId(paymentId: Long): CanceledPaymentEntityV2?
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence.v2
|
|
||||||
|
|
||||||
import jakarta.persistence.Entity
|
|
||||||
import jakarta.persistence.EnumType
|
|
||||||
import jakarta.persistence.Enumerated
|
|
||||||
import jakarta.persistence.Table
|
|
||||||
import roomescape.common.entity.PersistableBaseEntity
|
|
||||||
import roomescape.payment.infrastructure.common.PaymentMethod
|
|
||||||
import roomescape.payment.infrastructure.common.PaymentStatus
|
|
||||||
import roomescape.payment.infrastructure.common.PaymentType
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "payment1")
|
|
||||||
class PaymentEntityV2(
|
|
||||||
id: Long,
|
|
||||||
|
|
||||||
val reservationId: Long,
|
|
||||||
val paymentKey: String,
|
|
||||||
val orderId: String,
|
|
||||||
val totalAmount: Int,
|
|
||||||
val requestedAt: OffsetDateTime,
|
|
||||||
val approvedAt: OffsetDateTime,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val type: PaymentType,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
val method: PaymentMethod,
|
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
var status: PaymentStatus
|
|
||||||
) : PersistableBaseEntity(id) {
|
|
||||||
|
|
||||||
fun cancel() {
|
|
||||||
this.status = PaymentStatus.CANCELED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence.v2
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
|
|
||||||
interface PaymentRepositoryV2: JpaRepository<PaymentEntityV2, Long> {
|
|
||||||
|
|
||||||
fun findByReservationId(reservationId: Long): PaymentEntityV2?
|
|
||||||
}
|
|
||||||
@ -1,166 +0,0 @@
|
|||||||
package roomescape.payment.business
|
|
||||||
|
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import io.kotest.matchers.types.shouldBeInstanceOf
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.slot
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.implement.PaymentFinder
|
|
||||||
import roomescape.payment.implement.PaymentWriter
|
|
||||||
import roomescape.payment.infrastructure.client.PaymentApproveResponse
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
|
||||||
import roomescape.payment.web.PaymentCancelRequest
|
|
||||||
import roomescape.payment.web.PaymentCancelResponse
|
|
||||||
import roomescape.util.PaymentFixture
|
|
||||||
import roomescape.util.ReservationFixture
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
|
|
||||||
class PaymentServiceTest : FunSpec({
|
|
||||||
val paymentFinder: PaymentFinder = mockk()
|
|
||||||
val paymentWriter: PaymentWriter = mockk()
|
|
||||||
|
|
||||||
val paymentService = PaymentService(paymentFinder, paymentWriter)
|
|
||||||
|
|
||||||
context("createPayment") {
|
|
||||||
val approvedPaymentInfo = PaymentApproveResponse(
|
|
||||||
paymentKey = "paymentKey",
|
|
||||||
orderId = "orderId",
|
|
||||||
totalAmount = 1000L,
|
|
||||||
approvedAt = OffsetDateTime.now(),
|
|
||||||
)
|
|
||||||
val reservation = ReservationFixture.create(id = 1L)
|
|
||||||
|
|
||||||
test("정상 응답") {
|
|
||||||
every {
|
|
||||||
paymentWriter.create(
|
|
||||||
paymentKey = approvedPaymentInfo.paymentKey,
|
|
||||||
orderId = approvedPaymentInfo.orderId,
|
|
||||||
totalAmount = approvedPaymentInfo.totalAmount,
|
|
||||||
approvedAt = approvedPaymentInfo.approvedAt,
|
|
||||||
reservation = reservation
|
|
||||||
)
|
|
||||||
} returns PaymentFixture.create(
|
|
||||||
id = 1L,
|
|
||||||
orderId = approvedPaymentInfo.orderId,
|
|
||||||
paymentKey = approvedPaymentInfo.paymentKey,
|
|
||||||
totalAmount = approvedPaymentInfo.totalAmount,
|
|
||||||
approvedAt = approvedPaymentInfo.approvedAt,
|
|
||||||
reservation = reservation
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = paymentService.createPayment(approvedPaymentInfo, reservation)
|
|
||||||
|
|
||||||
assertSoftly(response) {
|
|
||||||
it.id shouldBe 1L
|
|
||||||
it.paymentKey shouldBe approvedPaymentInfo.paymentKey
|
|
||||||
it.reservationId shouldBe reservation.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("createCanceledPayment(canceledPaymentInfo)") {
|
|
||||||
val canceledPaymentInfo = PaymentCancelResponse(
|
|
||||||
cancelStatus = "normal",
|
|
||||||
cancelReason = "고객 요청",
|
|
||||||
cancelAmount = 1000L,
|
|
||||||
canceledAt = OffsetDateTime.now(),
|
|
||||||
)
|
|
||||||
val approvedAt = OffsetDateTime.now()
|
|
||||||
val paymentKey = "paymentKey"
|
|
||||||
|
|
||||||
test("CanceledPaymentEntity를 응답") {
|
|
||||||
every {
|
|
||||||
paymentWriter.createCanceled(
|
|
||||||
cancelReason = canceledPaymentInfo.cancelReason,
|
|
||||||
cancelAmount = canceledPaymentInfo.cancelAmount,
|
|
||||||
canceledAt = canceledPaymentInfo.canceledAt,
|
|
||||||
approvedAt = approvedAt,
|
|
||||||
paymentKey = paymentKey
|
|
||||||
)
|
|
||||||
} returns PaymentFixture.createCanceled(
|
|
||||||
id = 1L,
|
|
||||||
paymentKey = paymentKey,
|
|
||||||
cancelAmount = canceledPaymentInfo.cancelAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = paymentService.createCanceledPayment(canceledPaymentInfo, approvedAt, paymentKey)
|
|
||||||
|
|
||||||
response.shouldBeInstanceOf<CanceledPaymentEntity>()
|
|
||||||
response.paymentKey shouldBe paymentKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("createCanceledPayment(reservationId)") {
|
|
||||||
val reservationId = 1L
|
|
||||||
|
|
||||||
test("취소 사유를 '예약 취소'로 하여 PaymentCancelRequest를 응답") {
|
|
||||||
val payment = PaymentFixture.create(id = 1L, paymentKey = "paymentKey", totalAmount = 1000L)
|
|
||||||
every {
|
|
||||||
paymentFinder.findByReservationId(reservationId)
|
|
||||||
} returns payment
|
|
||||||
|
|
||||||
val cancelReasonSlot = slot<String>()
|
|
||||||
|
|
||||||
every {
|
|
||||||
paymentWriter.createCanceled(payment, capture(cancelReasonSlot), any())
|
|
||||||
} returns PaymentFixture.createCanceled(
|
|
||||||
id = 1L,
|
|
||||||
paymentKey = payment.paymentKey,
|
|
||||||
cancelAmount = payment.totalAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = paymentService.createCanceledPayment(reservationId)
|
|
||||||
|
|
||||||
response.shouldBeInstanceOf<PaymentCancelRequest>()
|
|
||||||
cancelReasonSlot.captured shouldBe "예약 취소"
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 정보가 없으면 예외 응답") {
|
|
||||||
every {
|
|
||||||
paymentFinder.findByReservationId(reservationId)
|
|
||||||
} throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
|
|
||||||
shouldThrow<PaymentException> {
|
|
||||||
paymentService.createCanceledPayment(reservationId)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("updateCanceledTime") {
|
|
||||||
val paymentKey = "paymentKey"
|
|
||||||
val canceledAt = OffsetDateTime.of(LocalDate.of(2025, 8, 5), LocalTime.of(10, 0), ZoneOffset.UTC)
|
|
||||||
|
|
||||||
test("정상 응답") {
|
|
||||||
val canceled = PaymentFixture.createCanceled(id = 1L)
|
|
||||||
every {
|
|
||||||
paymentFinder.findCanceledByKey(paymentKey)
|
|
||||||
} returns canceled
|
|
||||||
|
|
||||||
paymentService.updateCanceledTime(paymentKey, canceledAt)
|
|
||||||
|
|
||||||
canceled.canceledAt shouldBe canceledAt
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 취소 정보가 없으면 예외 응답") {
|
|
||||||
every {
|
|
||||||
paymentFinder.findCanceledByKey(paymentKey)
|
|
||||||
} throws PaymentException(PaymentErrorCode.PAYMENT_NOT_FOUND)
|
|
||||||
|
|
||||||
shouldThrow<PaymentException> {
|
|
||||||
paymentService.updateCanceledTime(paymentKey, canceledAt)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
package roomescape.payment.implement
|
|
||||||
|
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
|
||||||
|
|
||||||
class PaymentFinderTest : FunSpec({
|
|
||||||
val paymentRepository: PaymentRepository = mockk()
|
|
||||||
val canceledPaymentRepository: CanceledPaymentRepository = mockk()
|
|
||||||
|
|
||||||
val paymentFinder = PaymentFinder(paymentRepository, canceledPaymentRepository)
|
|
||||||
|
|
||||||
context("existsPaymentByReservationId") {
|
|
||||||
val reservationId = 1L
|
|
||||||
test("결제 정보가 있으면 true를 반환한다.") {
|
|
||||||
every {
|
|
||||||
paymentRepository.existsByReservationId(reservationId)
|
|
||||||
} returns true
|
|
||||||
|
|
||||||
paymentFinder.existsPaymentByReservationId(reservationId) shouldBe true
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 정보가 없으면 false를 반환한다.") {
|
|
||||||
every {
|
|
||||||
paymentRepository.existsByReservationId(reservationId)
|
|
||||||
} returns false
|
|
||||||
|
|
||||||
paymentFinder.existsPaymentByReservationId(reservationId) shouldBe false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("findByReservationId") {
|
|
||||||
val reservationId = 1L
|
|
||||||
test("결제 정보를 조회한다.") {
|
|
||||||
every {
|
|
||||||
paymentRepository.findByReservationId(reservationId)
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
paymentFinder.findByReservationId(reservationId)
|
|
||||||
|
|
||||||
verify(exactly = 1) {
|
|
||||||
paymentRepository.findByReservationId(reservationId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 정보가 없으면 실패한다.") {
|
|
||||||
every {
|
|
||||||
paymentRepository.findByReservationId(reservationId)
|
|
||||||
} returns null
|
|
||||||
|
|
||||||
shouldThrow<PaymentException> {
|
|
||||||
paymentFinder.findByReservationId(reservationId)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe PaymentErrorCode.PAYMENT_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("findCanceledByKey") {
|
|
||||||
val paymentKey = "paymentKey"
|
|
||||||
|
|
||||||
test("결제 취소 정보를 조회한다.") {
|
|
||||||
every {
|
|
||||||
canceledPaymentRepository.findByPaymentKey(paymentKey)
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
paymentFinder.findCanceledByKey(paymentKey)
|
|
||||||
|
|
||||||
verify(exactly = 1) {
|
|
||||||
canceledPaymentRepository.findByPaymentKey(paymentKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 취소 정보가 없으면 실패한다.") {
|
|
||||||
every {
|
|
||||||
canceledPaymentRepository.findByPaymentKey(paymentKey)
|
|
||||||
} returns null
|
|
||||||
|
|
||||||
shouldThrow<PaymentException> {
|
|
||||||
paymentFinder.findCanceledByKey(paymentKey)
|
|
||||||
}.also {
|
|
||||||
it.errorCode shouldBe PaymentErrorCode.CANCELED_PAYMENT_NOT_FOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
package roomescape.payment.implement
|
|
||||||
|
|
||||||
import com.ninjasquad.springmockk.MockkClear
|
|
||||||
import com.ninjasquad.springmockk.clear
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.date.after
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import io.mockk.clearMocks
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.slot
|
|
||||||
import io.mockk.verify
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.CanceledPaymentRepository
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentEntity
|
|
||||||
import roomescape.payment.infrastructure.persistence.PaymentRepository
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
||||||
import roomescape.util.PaymentFixture
|
|
||||||
import roomescape.util.ReservationFixture
|
|
||||||
import roomescape.util.TsidFactory
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
|
|
||||||
class PaymentWriterTest : FunSpec({
|
|
||||||
val paymentRepository: PaymentRepository = mockk()
|
|
||||||
val canceledPaymentRepository: CanceledPaymentRepository = mockk()
|
|
||||||
|
|
||||||
val paymentWriter = PaymentWriter(paymentRepository, canceledPaymentRepository, TsidFactory)
|
|
||||||
|
|
||||||
val paymentKey = "paymentKey"
|
|
||||||
val orderId = "orderId"
|
|
||||||
val totalAmount = 1000L
|
|
||||||
val approvedAt = OffsetDateTime.now()
|
|
||||||
|
|
||||||
context("create") {
|
|
||||||
val reservation: ReservationEntity = ReservationFixture.create(id = 1L)
|
|
||||||
test("결제 정보를 저장한다.") {
|
|
||||||
val slot = slot<PaymentEntity>()
|
|
||||||
every {
|
|
||||||
paymentRepository.save(capture(slot))
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
paymentWriter.create(paymentKey, orderId, totalAmount, approvedAt, reservation)
|
|
||||||
|
|
||||||
verify(exactly = 1) {
|
|
||||||
paymentRepository.save(any())
|
|
||||||
}
|
|
||||||
|
|
||||||
slot.captured.also {
|
|
||||||
it.paymentKey shouldBe paymentKey
|
|
||||||
it.orderId shouldBe orderId
|
|
||||||
it.totalAmount shouldBe totalAmount
|
|
||||||
it.approvedAt shouldBe approvedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentRepository.clear(MockkClear.AFTER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("createCanceled") {
|
|
||||||
val cancelReason = "고객 요청"
|
|
||||||
val canceledAt = OffsetDateTime.now()
|
|
||||||
|
|
||||||
afterTest {
|
|
||||||
clearMocks(paymentRepository)
|
|
||||||
clearMocks(canceledPaymentRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("PaymentEntity를 받아 저장한다.") {
|
|
||||||
val payment: PaymentEntity = PaymentFixture.create(id = 1L)
|
|
||||||
val slot = slot<CanceledPaymentEntity>()
|
|
||||||
|
|
||||||
every {
|
|
||||||
canceledPaymentRepository.save(capture(slot))
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
every {
|
|
||||||
paymentRepository.deleteByPaymentKey(paymentKey)
|
|
||||||
} returns Unit
|
|
||||||
|
|
||||||
paymentWriter.createCanceled(payment, cancelReason, canceledAt)
|
|
||||||
|
|
||||||
verify(exactly = 1) { canceledPaymentRepository.save(any()) }
|
|
||||||
verify(exactly = 1) { paymentRepository.deleteByPaymentKey(any()) }
|
|
||||||
|
|
||||||
slot.captured.also {
|
|
||||||
it.paymentKey shouldBe payment.paymentKey
|
|
||||||
it.cancelAmount shouldBe payment.totalAmount
|
|
||||||
it.approvedAt shouldBe payment.approvedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("취소 정보를 받아 저장한다.") {
|
|
||||||
val slot = slot<CanceledPaymentEntity>()
|
|
||||||
|
|
||||||
every {
|
|
||||||
canceledPaymentRepository.save(capture(slot))
|
|
||||||
} returns mockk()
|
|
||||||
|
|
||||||
every {
|
|
||||||
paymentRepository.deleteByPaymentKey(paymentKey)
|
|
||||||
} returns Unit
|
|
||||||
|
|
||||||
paymentWriter.createCanceled(
|
|
||||||
cancelReason = cancelReason,
|
|
||||||
cancelAmount = totalAmount,
|
|
||||||
canceledAt = canceledAt,
|
|
||||||
approvedAt = approvedAt,
|
|
||||||
paymentKey = paymentKey
|
|
||||||
)
|
|
||||||
|
|
||||||
verify(exactly = 1) { canceledPaymentRepository.save(any()) }
|
|
||||||
verify(exactly = 1) { paymentRepository.deleteByPaymentKey(any()) }
|
|
||||||
|
|
||||||
slot.captured.also {
|
|
||||||
it.paymentKey shouldBe paymentKey
|
|
||||||
it.cancelAmount shouldBe totalAmount
|
|
||||||
it.approvedAt shouldBe approvedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.core.spec.style.StringSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import roomescape.payment.web.PaymentCancelResponse
|
|
||||||
|
|
||||||
class PaymentCancelResponseDeserializerTest : StringSpec({
|
|
||||||
|
|
||||||
val objectMapper: ObjectMapper = jacksonObjectMapper().registerModule(
|
|
||||||
SimpleModule().addDeserializer(
|
|
||||||
PaymentCancelResponse::class.java,
|
|
||||||
PaymentCancelResponseDeserializer()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
"결제 취소 응답을 역직렬화하여 PaymentCancelResponse 객체를 생성한다" {
|
|
||||||
val cancelResponseJson: String = SampleTossPaymentConst.cancelJson
|
|
||||||
val cancelResponse: PaymentCancelResponse = objectMapper.readValue(
|
|
||||||
cancelResponseJson,
|
|
||||||
PaymentCancelResponse::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
assertSoftly(cancelResponse) {
|
|
||||||
cancelResponse.cancelStatus shouldBe "DONE"
|
|
||||||
cancelResponse.cancelReason shouldBe SampleTossPaymentConst.cancelReason
|
|
||||||
cancelResponse.cancelAmount shouldBe SampleTossPaymentConst.amount
|
|
||||||
cancelResponse.canceledAt.toString() shouldBe "2024-02-13T12:20:23+09:00"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.client
|
|
||||||
|
|
||||||
import com.ninjasquad.springmockk.MockkBean
|
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
|
|
||||||
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
|
|
||||||
import org.springframework.http.HttpMethod
|
|
||||||
import org.springframework.http.HttpStatus
|
|
||||||
import org.springframework.http.MediaType
|
|
||||||
import org.springframework.test.web.client.MockRestServiceServer
|
|
||||||
import org.springframework.test.web.client.ResponseActions
|
|
||||||
import org.springframework.test.web.client.match.MockRestRequestMatchers.*
|
|
||||||
import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
|
|
||||||
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
|
|
||||||
import roomescape.payment.exception.PaymentErrorCode
|
|
||||||
import roomescape.payment.exception.PaymentException
|
|
||||||
import roomescape.payment.web.PaymentCancelRequest
|
|
||||||
import roomescape.payment.web.PaymentCancelResponse
|
|
||||||
|
|
||||||
@RestClientTest(TossPaymentClient::class)
|
|
||||||
@MockkBean(JpaMetamodelMappingContext::class)
|
|
||||||
class TossPaymentClientTest(
|
|
||||||
@Autowired val client: TossPaymentClient,
|
|
||||||
@Autowired val mockServer: MockRestServiceServer
|
|
||||||
) : FunSpec() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
context("결제 승인 요청") {
|
|
||||||
fun commonAction(): ResponseActions = mockServer.expect {
|
|
||||||
requestTo("/v1/payments/confirm")
|
|
||||||
}.andExpect {
|
|
||||||
method(HttpMethod.POST)
|
|
||||||
}.andExpect {
|
|
||||||
content().contentType(MediaType.APPLICATION_JSON)
|
|
||||||
}.andExpect {
|
|
||||||
content().json(SampleTossPaymentConst.paymentRequestJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("성공 응답") {
|
|
||||||
commonAction().andRespond {
|
|
||||||
withSuccess()
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(SampleTossPaymentConst.confirmJson)
|
|
||||||
.createResponse(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val paymentRequest = SampleTossPaymentConst.paymentRequest
|
|
||||||
val paymentResponse: PaymentApproveResponse = client.confirm(paymentRequest)
|
|
||||||
|
|
||||||
assertSoftly(paymentResponse) {
|
|
||||||
this.paymentKey shouldBe paymentRequest.paymentKey
|
|
||||||
this.orderId shouldBe paymentRequest.orderId
|
|
||||||
this.totalAmount shouldBe paymentRequest.amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("실패 응답") {
|
|
||||||
fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
|
|
||||||
commonAction().andRespond {
|
|
||||||
withStatus(httpStatus)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(SampleTossPaymentConst.tossPaymentErrorJson)
|
|
||||||
.createResponse(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val paymentRequest = SampleTossPaymentConst.paymentRequest
|
|
||||||
|
|
||||||
// then
|
|
||||||
val exception = shouldThrow<PaymentException> {
|
|
||||||
client.confirm(paymentRequest)
|
|
||||||
}
|
|
||||||
exception.errorCode shouldBe expectedError
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 서버에서 4XX 응답 시") {
|
|
||||||
runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 서버에서 5XX 응답 시") {
|
|
||||||
runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("결제 취소 요청") {
|
|
||||||
fun commonAction(): ResponseActions = mockServer.expect {
|
|
||||||
requestTo("/v1/payments/${SampleTossPaymentConst.paymentKey}/cancel")
|
|
||||||
}.andExpect {
|
|
||||||
method(HttpMethod.POST)
|
|
||||||
}.andExpect {
|
|
||||||
content().contentType(MediaType.APPLICATION_JSON)
|
|
||||||
}.andExpect {
|
|
||||||
content().json(SampleTossPaymentConst.cancelRequestJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("성공 응답") {
|
|
||||||
commonAction().andRespond {
|
|
||||||
withSuccess()
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(SampleTossPaymentConst.cancelJson)
|
|
||||||
.createResponse(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
|
||||||
val cancelResponse: PaymentCancelResponse = client.cancel(cancelRequest)
|
|
||||||
|
|
||||||
assertSoftly(cancelResponse) {
|
|
||||||
this.cancelStatus shouldBe "DONE"
|
|
||||||
this.cancelReason shouldBe cancelRequest.cancelReason
|
|
||||||
this.cancelAmount shouldBe cancelRequest.amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context("실패 응답") {
|
|
||||||
fun runTest(httpStatus: HttpStatus, expectedError: PaymentErrorCode) {
|
|
||||||
commonAction().andRespond {
|
|
||||||
withStatus(httpStatus)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
|
||||||
.body(SampleTossPaymentConst.tossPaymentErrorJson)
|
|
||||||
.createResponse(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val cancelRequest: PaymentCancelRequest = SampleTossPaymentConst.cancelRequest
|
|
||||||
|
|
||||||
val exception = shouldThrow<PaymentException> {
|
|
||||||
client.cancel(cancelRequest)
|
|
||||||
}
|
|
||||||
exception.errorCode shouldBe expectedError
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 서버에서 4XX 응답 시") {
|
|
||||||
runTest(HttpStatus.BAD_REQUEST, PaymentErrorCode.PAYMENT_CLIENT_ERROR)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("결제 서버에서 5XX 응답 시") {
|
|
||||||
runTest(HttpStatus.INTERNAL_SERVER_ERROR, PaymentErrorCode.PAYMENT_PROVIDER_ERROR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence
|
|
||||||
|
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
|
||||||
import roomescape.common.config.next
|
|
||||||
import roomescape.util.PaymentFixture
|
|
||||||
import roomescape.util.TsidFactory
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@DataJpaTest(showSql = false)
|
|
||||||
class CanceledPaymentRepositoryTest(
|
|
||||||
@Autowired val canceledPaymentRepository: CanceledPaymentRepository,
|
|
||||||
) : FunSpec() {
|
|
||||||
init {
|
|
||||||
context("paymentKey로 CanceledPaymentEntity 조회") {
|
|
||||||
val paymentKey = "test-payment-key"
|
|
||||||
beforeTest {
|
|
||||||
PaymentFixture.createCanceled(id = TsidFactory.next(), paymentKey = paymentKey)
|
|
||||||
.also { canceledPaymentRepository.save(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("정상 반환") {
|
|
||||||
canceledPaymentRepository.findByPaymentKey(paymentKey)?.let {
|
|
||||||
assertSoftly(it) {
|
|
||||||
this.paymentKey shouldBe paymentKey
|
|
||||||
}
|
|
||||||
} ?: throw AssertionError("Unexpected null value")
|
|
||||||
}
|
|
||||||
|
|
||||||
test("null 반환") {
|
|
||||||
canceledPaymentRepository.findByPaymentKey(UUID.randomUUID().toString())
|
|
||||||
.also { it shouldBe null }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
package roomescape.payment.infrastructure.persistence
|
|
||||||
|
|
||||||
import io.kotest.assertions.assertSoftly
|
|
||||||
import io.kotest.core.spec.style.FunSpec
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import jakarta.persistence.EntityManager
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
|
||||||
import roomescape.common.config.next
|
|
||||||
import roomescape.reservation.infrastructure.persistence.ReservationEntity
|
|
||||||
import roomescape.util.PaymentFixture
|
|
||||||
import roomescape.util.ReservationFixture
|
|
||||||
import roomescape.util.TsidFactory
|
|
||||||
|
|
||||||
@DataJpaTest(showSql = false)
|
|
||||||
class PaymentRepositoryTest(
|
|
||||||
@Autowired val paymentRepository: PaymentRepository,
|
|
||||||
@Autowired val entityManager: EntityManager
|
|
||||||
) : FunSpec() {
|
|
||||||
|
|
||||||
lateinit var reservation: ReservationEntity
|
|
||||||
|
|
||||||
init {
|
|
||||||
context("existsByReservationId") {
|
|
||||||
beforeTest {
|
|
||||||
reservation = setupReservation()
|
|
||||||
PaymentFixture.create(reservation = reservation)
|
|
||||||
.also { paymentRepository.save(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("true") {
|
|
||||||
paymentRepository.existsByReservationId(reservation.id!!)
|
|
||||||
.also { it shouldBe true }
|
|
||||||
}
|
|
||||||
|
|
||||||
test("false") {
|
|
||||||
paymentRepository.existsByReservationId(reservation.id!! + 1L)
|
|
||||||
.also { it shouldBe false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupReservation(): ReservationEntity {
|
|
||||||
return ReservationFixture.create(
|
|
||||||
id = TsidFactory.next()
|
|
||||||
).also {
|
|
||||||
entityManager.persist(it.member)
|
|
||||||
entityManager.persist(it.theme)
|
|
||||||
entityManager.persist(it.time)
|
|
||||||
entityManager.persist(it)
|
|
||||||
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user