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

Merged
pricelees merged 45 commits from refactor/#56 into main 2025-10-09 09:33:29 +00:00
27 changed files with 387 additions and 344 deletions
Showing only changes of commit 1902fc6f7c - Show all commits

View File

@ -1,15 +1,19 @@
package com.sangdol.roomescape.payment.business package com.sangdol.roomescape.payment.business
import com.sangdol.common.persistence.TransactionExecutionUtil import com.sangdol.common.persistence.TransactionExecutionUtil
import com.sangdol.roomescape.payment.business.domain.PaymentClientError import com.sangdol.roomescape.payment.business.domain.UserFacingPaymentErrorCode
import com.sangdol.roomescape.payment.dto.PaymentCancelRequest
import com.sangdol.roomescape.payment.exception.ExternalPaymentException import com.sangdol.roomescape.payment.exception.ExternalPaymentException
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.client.PaymentClientCancelResponse import com.sangdol.roomescape.payment.dto.PaymentGatewayCancelResponse
import com.sangdol.roomescape.payment.infrastructure.client.PaymentClientConfirmResponse import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
import com.sangdol.roomescape.payment.dto.PaymentCreateResponse
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.dto.PaymentResponse
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.web.* import com.sangdol.roomescape.payment.mapper.toResponse
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.stereotype.Service import org.springframework.stereotype.Service
@ -26,7 +30,7 @@ class PaymentService(
private val paymentWriter: PaymentWriter, private val paymentWriter: PaymentWriter,
private val transactionExecutionUtil: TransactionExecutionUtil, private val transactionExecutionUtil: TransactionExecutionUtil,
) { ) {
fun requestConfirm(request: PaymentConfirmRequest): PaymentClientConfirmResponse { fun requestConfirm(request: PaymentConfirmRequest): PaymentGatewayResponse {
try { try {
return paymentClient.confirm(request.paymentKey, request.orderId, request.amount) return paymentClient.confirm(request.paymentKey, request.orderId, request.amount)
} catch (e: ExternalPaymentException) { } catch (e: ExternalPaymentException) {
@ -36,7 +40,7 @@ class PaymentService(
PaymentErrorCode.PAYMENT_PROVIDER_ERROR PaymentErrorCode.PAYMENT_PROVIDER_ERROR
} }
val message = if (PaymentClientError.contains(e.errorCode)) { val message = if (UserFacingPaymentErrorCode.contains(e.errorCode)) {
"${errorCode.message}(${e.message})" "${errorCode.message}(${e.message})"
} else { } else {
errorCode.message errorCode.message
@ -48,13 +52,13 @@ class PaymentService(
fun savePayment( fun savePayment(
reservationId: Long, reservationId: Long,
paymentConfirmResponse: PaymentClientConfirmResponse paymentGatewayResponse: PaymentGatewayResponse
): PaymentCreateResponse { ): PaymentCreateResponse {
val payment: PaymentEntity = paymentWriter.createPayment( val payment: PaymentEntity = paymentWriter.createPayment(
reservationId = reservationId, reservationId = reservationId,
paymentClientConfirmResponse = paymentConfirmResponse paymentGatewayResponse = paymentGatewayResponse
) )
val detail: PaymentDetailEntity = paymentWriter.createDetail(paymentConfirmResponse, payment.id) val detail: PaymentDetailEntity = paymentWriter.createDetail(paymentGatewayResponse, payment.id)
return PaymentCreateResponse(paymentId = payment.id, detailId = detail.id) return PaymentCreateResponse(paymentId = payment.id, detailId = detail.id)
} }
@ -62,7 +66,7 @@ class PaymentService(
fun cancel(userId: Long, request: PaymentCancelRequest) { fun cancel(userId: Long, request: PaymentCancelRequest) {
val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId) val payment: PaymentEntity = findByReservationIdOrThrow(request.reservationId)
val clientCancelResponse: PaymentClientCancelResponse = paymentClient.cancel( val clientCancelResponse: PaymentGatewayCancelResponse = paymentClient.cancel(
paymentKey = payment.paymentKey, paymentKey = payment.paymentKey,
amount = payment.totalAmount, amount = payment.totalAmount,
cancelReason = request.cancelReason cancelReason = request.cancelReason
@ -81,16 +85,16 @@ class PaymentService(
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findDetailByReservationId(reservationId: Long): PaymentWithDetailResponse? { fun findDetailByReservationId(reservationId: Long): PaymentResponse? {
log.info { "[findDetailByReservationId] 예약 결제 정보 조회 시작: reservationId=$reservationId" } log.info { "[findDetailByReservationId] 예약 결제 정보 조회 시작: reservationId=$reservationId" }
val payment: PaymentEntity? = findByReservationIdOrNull(reservationId) val payment: PaymentEntity? = findByReservationIdOrNull(reservationId)
val paymentDetail: PaymentDetailEntity? = payment?.let { findDetailByPaymentIdOrNull(it.id) } val paymentDetail: PaymentDetailEntity? = payment?.let { findDetailByPaymentIdOrNull(it.id) }
val cancelDetail: CanceledPaymentEntity? = payment?.let { findCancelByPaymentIdOrNull(it.id) } val cancelDetail: CanceledPaymentEntity? = payment?.let { findCancelByPaymentIdOrNull(it.id) }
return payment?.toDetailResponse( return payment?.toResponse(
detail = paymentDetail?.toPaymentDetailResponse(), detail = paymentDetail?.toResponse(),
cancel = cancelDetail?.toCancelDetailResponse() cancel = cancelDetail?.toResponse()
) )
} }

View File

@ -3,8 +3,13 @@ package com.sangdol.roomescape.payment.business
import com.sangdol.common.persistence.IDGenerator import com.sangdol.common.persistence.IDGenerator
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.client.* import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.infrastructure.common.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 com.sangdol.roomescape.payment.infrastructure.persistence.*
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
@ -23,31 +28,31 @@ class PaymentWriter(
fun createPayment( fun createPayment(
reservationId: Long, reservationId: Long,
paymentClientConfirmResponse: PaymentClientConfirmResponse paymentGatewayResponse: PaymentGatewayResponse
): PaymentEntity { ): PaymentEntity {
log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 시작: reservationId=${reservationId}, paymentKey=${paymentClientConfirmResponse.paymentKey}" } log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 시작: reservationId=${reservationId}, paymentKey=${paymentGatewayResponse.paymentKey}" }
return paymentClientConfirmResponse.toEntity(id = idGenerator.create(), reservationId = reservationId).also { return paymentGatewayResponse.toEntity(id = idGenerator.create(), reservationId = reservationId).also {
paymentRepository.save(it) paymentRepository.save(it)
log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 완료: reservationId=${reservationId}, payment.id=${it.id}" } log.info { "[PaymentWriterV2.createPayment] 결제 승인 및 결제 정보 저장 완료: reservationId=${reservationId}, payment.id=${it.id}" }
} }
} }
fun createDetail( fun createDetail(
paymentResponse: PaymentClientConfirmResponse, paymentGatewayResponse: PaymentGatewayResponse,
paymentId: Long, paymentId: Long,
): PaymentDetailEntity { ): PaymentDetailEntity {
val method: PaymentMethod = paymentResponse.method val method: PaymentMethod = paymentGatewayResponse.method
val id = idGenerator.create() val id = idGenerator.create()
if (method == PaymentMethod.TRANSFER) { if (method == PaymentMethod.TRANSFER) {
return paymentDetailRepository.save(paymentResponse.toTransferDetailEntity(id, paymentId)) return paymentDetailRepository.save(paymentGatewayResponse.toTransferDetailEntity(id, paymentId))
} }
if (method == PaymentMethod.EASY_PAY && paymentResponse.card == null) { if (method == PaymentMethod.EASY_PAY && paymentGatewayResponse.card == null) {
return paymentDetailRepository.save(paymentResponse.toEasypayPrepaidDetailEntity(id, paymentId)) return paymentDetailRepository.save(paymentGatewayResponse.toEasypayPrepaidDetailEntity(id, paymentId))
} }
if (paymentResponse.card != null) { if (paymentGatewayResponse.card != null) {
return paymentDetailRepository.save(paymentResponse.toCardDetailEntity(id, paymentId)) return paymentDetailRepository.save(paymentGatewayResponse.toCardDetailEntity(id, paymentId))
} }
throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE) throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
} }
@ -56,7 +61,7 @@ class PaymentWriter(
userId: Long, userId: Long,
payment: PaymentEntity, payment: PaymentEntity,
requestedAt: Instant, requestedAt: Instant,
cancelResponse: PaymentClientCancelResponse cancelResponse: PaymentGatewayCancelResponse
): CanceledPaymentEntity { ): CanceledPaymentEntity {
log.debug { "[PaymentWriterV2.cancelPayment] 결제 취소 정보 저장 시작: payment.id=${payment.id}" } log.debug { "[PaymentWriterV2.cancelPayment] 결제 취소 정보 저장 시작: payment.id=${payment.id}" }

View File

@ -1,4 +1,4 @@
package com.sangdol.roomescape.payment.infrastructure.common package com.sangdol.roomescape.payment.business.domain
import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonCreator
import com.sangdol.roomescape.payment.exception.PaymentErrorCode import com.sangdol.roomescape.payment.exception.PaymentErrorCode

View File

@ -1,6 +1,6 @@
package com.sangdol.roomescape.payment.business.domain package com.sangdol.roomescape.payment.business.domain
enum class PaymentClientError { enum class UserFacingPaymentErrorCode {
ALREADY_PROCESSED_PAYMENT, ALREADY_PROCESSED_PAYMENT,
EXCEED_MAX_CARD_INSTALLMENT_PLAN, EXCEED_MAX_CARD_INSTALLMENT_PLAN,
NOT_ALLOWED_POINT_USE, NOT_ALLOWED_POINT_USE,

View File

@ -4,9 +4,9 @@ import com.sangdol.common.types.web.CommonApiResponse
import com.sangdol.roomescape.auth.web.support.User import com.sangdol.roomescape.auth.web.support.User
import com.sangdol.roomescape.auth.web.support.UserOnly import com.sangdol.roomescape.auth.web.support.UserOnly
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.payment.infrastructure.client.PaymentClientConfirmResponse import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.web.PaymentCancelRequest import com.sangdol.roomescape.payment.dto.PaymentCancelRequest
import com.sangdol.roomescape.payment.web.PaymentConfirmRequest 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
@ -21,7 +21,7 @@ interface PaymentAPI {
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
fun confirmPayment( fun confirmPayment(
@Valid @RequestBody request: PaymentConfirmRequest @Valid @RequestBody request: PaymentConfirmRequest
): ResponseEntity<CommonApiResponse<PaymentClientConfirmResponse>> ): ResponseEntity<CommonApiResponse<PaymentGatewayResponse>>
@Operation(summary = "결제 취소") @Operation(summary = "결제 취소")
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true)) @ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))

View File

@ -0,0 +1,59 @@
package com.sangdol.roomescape.payment.dto
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.sangdol.roomescape.payment.business.domain.*
import com.sangdol.roomescape.payment.infrastructure.client.CancelDetailDeserializer
import java.time.OffsetDateTime
data class PaymentGatewayResponse(
val paymentKey: String,
val orderId: String,
val type: PaymentType,
val status: PaymentStatus,
val totalAmount: Int,
val vat: Int,
val suppliedAmount: Int,
val method: PaymentMethod,
val card: CardDetailResponse?,
val easyPay: EasyPayDetailResponse?,
val transfer: TransferDetailResponse?,
val requestedAt: OffsetDateTime,
val approvedAt: OffsetDateTime,
)
data class PaymentGatewayCancelResponse(
val status: PaymentStatus,
@JsonDeserialize(using = CancelDetailDeserializer::class)
val cancels: CancelDetail,
)
data class CardDetailResponse(
val issuerCode: CardIssuerCode,
val number: String,
val amount: Int,
val cardType: CardType,
val ownerType: CardOwnerType,
val isInterestFree: Boolean,
val approveNo: String,
val installmentPlanMonths: Int
)
data class EasyPayDetailResponse(
val provider: EasyPayCompanyCode,
val amount: Int,
val discountAmount: Int,
)
data class TransferDetailResponse(
val bankCode: BankCode,
val settlementStatus: String,
)
data class CancelDetail(
val cancelAmount: Int,
val cardDiscountAmount: Int,
val transferDiscountAmount: Int,
val easyPayDiscountAmount: Int,
val canceledAt: OffsetDateTime,
val cancelReason: String
)

View File

@ -0,0 +1,49 @@
package com.sangdol.roomescape.payment.dto
import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import java.time.Instant
data class PaymentResponse(
val orderId: String,
val totalAmount: Int,
val method: String,
val status: PaymentStatus,
val requestedAt: Instant,
val approvedAt: Instant,
val detail: PaymentDetailResponse?,
val cancel: PaymentCancelDetailResponse?,
)
sealed class PaymentDetailResponse {
data class CardDetailResponse(
val type: String = "CARD",
val issuerCode: String,
val cardType: String,
val ownerType: String,
val cardNumber: String,
val amount: Int,
val approvalNumber: String,
val installmentPlanMonths: Int,
val easypayProviderName: String?,
val easypayDiscountAmount: Int?,
) : PaymentDetailResponse()
data class BankTransferDetailResponse(
val type: String = "BANK_TRANSFER",
val bankName: String,
) : PaymentDetailResponse()
data class EasyPayPrepaidDetailResponse(
val type: String = "EASYPAY_PREPAID",
val providerName: String,
val amount: Int,
val discountAmount: Int,
) : PaymentDetailResponse()
}
data class PaymentCancelDetailResponse(
val cancellationRequestedAt: Instant,
val cancellationApprovedAt: Instant?,
val cancelReason: String,
val canceledBy: Long,
)

View File

@ -0,0 +1,20 @@
package com.sangdol.roomescape.payment.dto
import java.time.Instant
data class PaymentConfirmRequest(
val paymentKey: String,
val orderId: String,
val amount: Int,
)
data class PaymentCreateResponse(
val paymentId: Long,
val detailId: Long
)
data class PaymentCancelRequest(
val reservationId: Long,
val cancelReason: String,
val requestedAt: Instant = Instant.now()
)

View File

@ -1,67 +0,0 @@
package com.sangdol.roomescape.payment.infrastructure.client
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.sangdol.roomescape.payment.infrastructure.common.PaymentStatus
import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import java.time.Instant
import java.time.OffsetDateTime
data class PaymentClientCancelResponse(
val status: PaymentStatus,
@JsonDeserialize(using = CancelDetailDeserializer::class)
val cancels: CancelDetail,
)
data class CancelDetail(
val cancelAmount: Int,
val cardDiscountAmount: Int,
val transferDiscountAmount: Int,
val easyPayDiscountAmount: Int,
val canceledAt: OffsetDateTime,
val cancelReason: String
)
fun CancelDetail.toEntity(
id: Long,
paymentId: Long,
canceledBy: Long,
cancelRequestedAt: Instant
) = CanceledPaymentEntity(
id = id,
canceledAt = this.canceledAt.toInstant(),
requestedAt = cancelRequestedAt,
paymentId = paymentId,
canceledBy = canceledBy,
cancelReason = this.cancelReason,
cancelAmount = this.cancelAmount,
cardDiscountAmount = this.cardDiscountAmount,
transferDiscountAmount = this.transferDiscountAmount,
easypayDiscountAmount = this.easyPayDiscountAmount
)
class CancelDetailDeserializer : com.fasterxml.jackson.databind.JsonDeserializer<CancelDetail>() {
override fun deserialize(
p: JsonParser,
ctxt: DeserializationContext
): CancelDetail? {
val node: JsonNode = p.codec.readTree(p) ?: return null
val targetNode = when {
node.isArray && !node.isEmpty -> node[0]
node.isObject -> node
else -> return null
}
return CancelDetail(
cancelAmount = targetNode.get("cancelAmount").asInt(),
cardDiscountAmount = targetNode.get("cardDiscountAmount").asInt(),
transferDiscountAmount = targetNode.get("transferDiscountAmount").asInt(),
easyPayDiscountAmount = targetNode.get("easyPayDiscountAmount").asInt(),
canceledAt = OffsetDateTime.parse(targetNode.get("canceledAt").asText()),
cancelReason = targetNode.get("cancelReason").asText()
)
}
}

View File

@ -1,6 +1,8 @@
package com.sangdol.roomescape.payment.infrastructure.client package com.sangdol.roomescape.payment.infrastructure.client
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.sangdol.roomescape.payment.dto.PaymentGatewayCancelResponse
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.exception.ExternalPaymentException import com.sangdol.roomescape.payment.exception.ExternalPaymentException
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
@ -29,7 +31,7 @@ class TosspayClient(
paymentKey: String, paymentKey: String,
orderId: String, orderId: String,
amount: Int, amount: Int,
): PaymentClientConfirmResponse { ): PaymentGatewayResponse {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
log.info { "[TosspayClient.confirm] 결제 승인 요청: paymentKey=$paymentKey, orderId=$orderId, amount=$amount" } log.info { "[TosspayClient.confirm] 결제 승인 요청: paymentKey=$paymentKey, orderId=$orderId, amount=$amount" }
@ -43,7 +45,7 @@ class TosspayClient(
paymentKey: String, paymentKey: String,
amount: Int, amount: Int,
cancelReason: String cancelReason: String
): PaymentClientCancelResponse { ): PaymentGatewayCancelResponse {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
log.info { "[TosspayClient.cancel] 결제 취소 요청: paymentKey=$paymentKey, amount=$amount, cancelReason=$cancelReason" } log.info { "[TosspayClient.cancel] 결제 취소 요청: paymentKey=$paymentKey, amount=$amount, cancelReason=$cancelReason" }
@ -63,7 +65,7 @@ private class ConfirmClient(
private val errorHandler: TosspayErrorHandler = TosspayErrorHandler(objectMapper) private val errorHandler: TosspayErrorHandler = TosspayErrorHandler(objectMapper)
fun request(paymentKey: String, orderId: String, amount: Int): PaymentClientConfirmResponse { fun request(paymentKey: String, orderId: String, amount: Int): PaymentGatewayResponse {
val response = client.post() val response = client.post()
.uri(CONFIRM_URI) .uri(CONFIRM_URI)
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
@ -84,7 +86,7 @@ private class ConfirmClient(
log.debug { "[TosspayClient.confirm] 응답 수신: json = $response" } log.debug { "[TosspayClient.confirm] 응답 수신: json = $response" }
return objectMapper.readValue(response, PaymentClientConfirmResponse::class.java) return objectMapper.readValue(response, PaymentGatewayResponse::class.java)
} }
} }
@ -102,7 +104,7 @@ private class CancelClient(
paymentKey: String, paymentKey: String,
amount: Int, amount: Int,
cancelReason: String cancelReason: String
): PaymentClientCancelResponse { ): PaymentGatewayCancelResponse {
val response = client.post() val response = client.post()
.uri(CANCEL_URI, paymentKey) .uri(CANCEL_URI, paymentKey)
.body( .body(
@ -120,7 +122,7 @@ private class CancelClient(
} }
log.debug { "[TosspayClient.cancel] 응답 수신: json = $response" } log.debug { "[TosspayClient.cancel] 응답 수신: json = $response" }
return objectMapper.readValue(response, PaymentClientCancelResponse::class.java) return objectMapper.readValue(response, PaymentGatewayCancelResponse::class.java)
} }
} }

View File

@ -0,0 +1,32 @@
package com.sangdol.roomescape.payment.infrastructure.client
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.sangdol.roomescape.payment.dto.CancelDetail
import java.time.OffsetDateTime
class CancelDetailDeserializer : JsonDeserializer<CancelDetail>() {
override fun deserialize(
p: JsonParser,
ctxt: DeserializationContext
): CancelDetail? {
val node: JsonNode = p.codec.readTree(p) ?: return null
val targetNode = when {
node.isArray && !node.isEmpty -> node[0]
node.isObject -> node
else -> return null
}
return CancelDetail(
cancelAmount = targetNode.get("cancelAmount").asInt(),
cardDiscountAmount = targetNode.get("cardDiscountAmount").asInt(),
transferDiscountAmount = targetNode.get("transferDiscountAmount").asInt(),
easyPayDiscountAmount = targetNode.get("easyPayDiscountAmount").asInt(),
canceledAt = OffsetDateTime.parse(targetNode.get("canceledAt").asText()),
cancelReason = targetNode.get("cancelReason").asText()
)
}
}

View File

@ -1,7 +1,11 @@
package com.sangdol.roomescape.payment.infrastructure.persistence package com.sangdol.roomescape.payment.infrastructure.persistence
import com.sangdol.common.persistence.PersistableBaseEntity import com.sangdol.common.persistence.PersistableBaseEntity
import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.payment.business.domain.BankCode
import com.sangdol.roomescape.payment.business.domain.CardIssuerCode
import com.sangdol.roomescape.payment.business.domain.CardOwnerType
import com.sangdol.roomescape.payment.business.domain.CardType
import com.sangdol.roomescape.payment.business.domain.EasyPayCompanyCode
import jakarta.persistence.* import jakarta.persistence.*
@Entity @Entity

View File

@ -1,9 +1,9 @@
package com.sangdol.roomescape.payment.infrastructure.persistence package com.sangdol.roomescape.payment.infrastructure.persistence
import com.sangdol.common.persistence.PersistableBaseEntity import com.sangdol.common.persistence.PersistableBaseEntity
import com.sangdol.roomescape.payment.infrastructure.common.PaymentMethod import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.infrastructure.common.PaymentStatus import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import com.sangdol.roomescape.payment.infrastructure.common.PaymentType import com.sangdol.roomescape.payment.business.domain.PaymentType
import jakarta.persistence.Entity import jakarta.persistence.Entity
import jakarta.persistence.EnumType import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated import jakarta.persistence.Enumerated

View File

@ -1,31 +1,13 @@
package com.sangdol.roomescape.payment.infrastructure.client package com.sangdol.roomescape.payment.mapper
import com.sangdol.roomescape.payment.dto.CancelDetail
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.exception.PaymentErrorCode import com.sangdol.roomescape.payment.exception.PaymentErrorCode
import com.sangdol.roomescape.payment.exception.PaymentException import com.sangdol.roomescape.payment.exception.PaymentException
import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.payment.infrastructure.persistence.*
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentBankTransferDetailEntity import java.time.Instant
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentCardDetailEntity
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEasypayPrepaidDetailEntity
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEntity
import java.time.OffsetDateTime
data class PaymentClientConfirmResponse( fun PaymentGatewayResponse.toEntity(
val paymentKey: String,
val orderId: String,
val type: PaymentType,
val status: PaymentStatus,
val totalAmount: Int,
val vat: Int,
val suppliedAmount: Int,
val method: PaymentMethod,
val card: CardDetail?,
val easyPay: EasyPayDetail?,
val transfer: TransferDetail?,
val requestedAt: OffsetDateTime,
val approvedAt: OffsetDateTime,
)
fun PaymentClientConfirmResponse.toEntity(
id: Long, id: Long,
reservationId: Long, reservationId: Long,
) = PaymentEntity( ) = PaymentEntity(
@ -41,18 +23,7 @@ fun PaymentClientConfirmResponse.toEntity(
status = this.status, status = this.status,
) )
data class CardDetail( fun PaymentGatewayResponse.toCardDetailEntity(id: Long, paymentId: Long): PaymentCardDetailEntity {
val issuerCode: CardIssuerCode,
val number: String,
val amount: Int,
val cardType: CardType,
val ownerType: CardOwnerType,
val isInterestFree: Boolean,
val approveNo: String,
val installmentPlanMonths: Int
)
fun PaymentClientConfirmResponse.toCardDetailEntity(id: Long, paymentId: Long): PaymentCardDetailEntity {
val cardDetail = this.card ?: throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR) val cardDetail = this.card ?: throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR)
return PaymentCardDetailEntity( return PaymentCardDetailEntity(
@ -73,13 +44,7 @@ fun PaymentClientConfirmResponse.toCardDetailEntity(id: Long, paymentId: Long):
) )
} }
data class EasyPayDetail( fun PaymentGatewayResponse.toEasypayPrepaidDetailEntity(
val provider: EasyPayCompanyCode,
val amount: Int,
val discountAmount: Int,
)
fun PaymentClientConfirmResponse.toEasypayPrepaidDetailEntity(
id: Long, id: Long,
paymentId: Long paymentId: Long
): PaymentEasypayPrepaidDetailEntity { ): PaymentEasypayPrepaidDetailEntity {
@ -96,12 +61,7 @@ fun PaymentClientConfirmResponse.toEasypayPrepaidDetailEntity(
) )
} }
data class TransferDetail( fun PaymentGatewayResponse.toTransferDetailEntity(
val bankCode: BankCode,
val settlementStatus: String,
)
fun PaymentClientConfirmResponse.toTransferDetailEntity(
id: Long, id: Long,
paymentId: Long paymentId: Long
): PaymentBankTransferDetailEntity { ): PaymentBankTransferDetailEntity {
@ -116,3 +76,21 @@ fun PaymentClientConfirmResponse.toTransferDetailEntity(
settlementStatus = transferDetail.settlementStatus settlementStatus = transferDetail.settlementStatus
) )
} }
fun CancelDetail.toEntity(
id: Long,
paymentId: Long,
canceledBy: Long,
cancelRequestedAt: Instant
) = CanceledPaymentEntity(
id = id,
canceledAt = this.canceledAt.toInstant(),
requestedAt = cancelRequestedAt,
paymentId = paymentId,
canceledBy = canceledBy,
cancelReason = this.cancelReason,
cancelAmount = this.cancelAmount,
cardDiscountAmount = this.cardDiscountAmount,
transferDiscountAmount = this.transferDiscountAmount,
easypayDiscountAmount = this.easyPayDiscountAmount
)

View File

@ -0,0 +1,70 @@
package com.sangdol.roomescape.payment.mapper
import com.sangdol.roomescape.payment.dto.PaymentCancelDetailResponse
import com.sangdol.roomescape.payment.dto.PaymentDetailResponse
import com.sangdol.roomescape.payment.dto.PaymentResponse
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
import com.sangdol.roomescape.payment.exception.PaymentException
import com.sangdol.roomescape.payment.infrastructure.persistence.*
fun PaymentEntity.toResponse(
detail: PaymentDetailResponse?,
cancel: PaymentCancelDetailResponse?
): PaymentResponse {
return PaymentResponse(
orderId = this.orderId,
totalAmount = this.totalAmount,
method = this.method.koreanName,
status = this.status,
requestedAt = this.requestedAt,
approvedAt = this.approvedAt,
detail = detail,
cancel = cancel
)
}
fun PaymentDetailEntity.toResponse(): PaymentDetailResponse {
return when (this) {
is PaymentCardDetailEntity -> this.toResponse()
is PaymentBankTransferDetailEntity -> this.toResponse()
is PaymentEasypayPrepaidDetailEntity -> this.toResponse()
else -> throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
}
}
fun PaymentCardDetailEntity.toResponse(): PaymentDetailResponse.CardDetailResponse {
return PaymentDetailResponse.CardDetailResponse(
issuerCode = this.issuerCode.koreanName,
cardType = this.cardType.koreanName,
ownerType = this.ownerType.koreanName,
cardNumber = this.cardNumber,
amount = this.amount,
approvalNumber = this.approvalNumber,
installmentPlanMonths = this.installmentPlanMonths,
easypayProviderName = this.easypayProviderCode?.koreanName,
easypayDiscountAmount = this.easypayDiscountAmount
)
}
fun PaymentBankTransferDetailEntity.toResponse(): PaymentDetailResponse.BankTransferDetailResponse {
return PaymentDetailResponse.BankTransferDetailResponse(
bankName = this.bankCode.koreanName
)
}
fun PaymentEasypayPrepaidDetailEntity.toResponse(): PaymentDetailResponse.EasyPayPrepaidDetailResponse {
return PaymentDetailResponse.EasyPayPrepaidDetailResponse(
providerName = this.easypayProviderCode.koreanName,
amount = this.amount,
discountAmount = this.discountAmount
)
}
fun CanceledPaymentEntity.toResponse(): PaymentCancelDetailResponse {
return PaymentCancelDetailResponse(
cancellationRequestedAt = this.requestedAt,
cancellationApprovedAt = this.canceledAt,
cancelReason = this.cancelReason,
canceledBy = this.canceledBy
)
}

View File

@ -5,7 +5,9 @@ import com.sangdol.roomescape.auth.web.support.User
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.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.infrastructure.client.PaymentClientConfirmResponse 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.*
@ -19,7 +21,7 @@ class PaymentController(
@PostMapping("/confirm") @PostMapping("/confirm")
override fun confirmPayment( override fun confirmPayment(
@Valid @RequestBody request: PaymentConfirmRequest @Valid @RequestBody request: PaymentConfirmRequest
): ResponseEntity<CommonApiResponse<PaymentClientConfirmResponse>> { ): ResponseEntity<CommonApiResponse<PaymentGatewayResponse>> {
val response = paymentService.requestConfirm(request) val response = paymentService.requestConfirm(request)
return ResponseEntity.ok(CommonApiResponse(response)) return ResponseEntity.ok(CommonApiResponse(response))

View File

@ -1,134 +0,0 @@
package com.sangdol.roomescape.payment.web
import com.sangdol.roomescape.payment.exception.PaymentErrorCode
import com.sangdol.roomescape.payment.exception.PaymentException
import com.sangdol.roomescape.payment.infrastructure.common.PaymentStatus
import com.sangdol.roomescape.payment.infrastructure.common.PaymentType
import com.sangdol.roomescape.payment.infrastructure.persistence.*
import com.sangdol.roomescape.payment.web.PaymentDetailResponse.*
import java.time.Instant
data class PaymentConfirmRequest(
val paymentKey: String,
val orderId: String,
val amount: Int,
)
data class PaymentCreateResponse(
val paymentId: Long,
val detailId: Long
)
data class PaymentCancelRequest(
val reservationId: Long,
val cancelReason: String,
val requestedAt: Instant = Instant.now()
)
data class PaymentWithDetailResponse(
val orderId: String,
val totalAmount: Int,
val method: String,
val status: PaymentStatus,
val requestedAt: Instant,
val approvedAt: Instant,
val detail: PaymentDetailResponse?,
val cancel: PaymentCancelDetailResponse?,
)
fun PaymentEntity.toDetailResponse(
detail: PaymentDetailResponse?,
cancel: PaymentCancelDetailResponse?
): PaymentWithDetailResponse {
return PaymentWithDetailResponse(
orderId = this.orderId,
totalAmount = this.totalAmount,
method = this.method.koreanName,
status = this.status,
requestedAt = this.requestedAt,
approvedAt = this.approvedAt,
detail = detail,
cancel = cancel
)
}
sealed class PaymentDetailResponse {
data class CardDetailResponse(
val type: String = "CARD",
val issuerCode: String,
val cardType: String,
val ownerType: String,
val cardNumber: String,
val amount: Int,
val approvalNumber: String,
val installmentPlanMonths: Int,
val easypayProviderName: String?,
val easypayDiscountAmount: Int?,
) : PaymentDetailResponse()
data class BankTransferDetailResponse(
val type: String = "BANK_TRANSFER",
val bankName: String,
) : PaymentDetailResponse()
data class EasyPayPrepaidDetailResponse(
val type: String = "EASYPAY_PREPAID",
val providerName: String,
val amount: Int,
val discountAmount: Int,
) : PaymentDetailResponse()
}
fun PaymentDetailEntity.toPaymentDetailResponse(): PaymentDetailResponse {
return when (this) {
is PaymentCardDetailEntity -> this.toCardDetailResponse()
is PaymentBankTransferDetailEntity -> this.toBankTransferDetailResponse()
is PaymentEasypayPrepaidDetailEntity -> this.toEasyPayPrepaidDetailResponse()
else -> throw PaymentException(PaymentErrorCode.NOT_SUPPORTED_PAYMENT_TYPE)
}
}
fun PaymentCardDetailEntity.toCardDetailResponse(): CardDetailResponse {
return CardDetailResponse(
issuerCode = this.issuerCode.koreanName,
cardType = this.cardType.koreanName,
ownerType = this.ownerType.koreanName,
cardNumber = this.cardNumber,
amount = this.amount,
approvalNumber = this.approvalNumber,
installmentPlanMonths = this.installmentPlanMonths,
easypayProviderName = this.easypayProviderCode?.koreanName,
easypayDiscountAmount = this.easypayDiscountAmount
)
}
fun PaymentBankTransferDetailEntity.toBankTransferDetailResponse(): BankTransferDetailResponse {
return BankTransferDetailResponse(
bankName = this.bankCode.koreanName
)
}
fun PaymentEasypayPrepaidDetailEntity.toEasyPayPrepaidDetailResponse(): EasyPayPrepaidDetailResponse {
return EasyPayPrepaidDetailResponse(
providerName = this.easypayProviderCode.koreanName,
amount = this.amount,
discountAmount = this.discountAmount
)
}
data class PaymentCancelDetailResponse(
val cancellationRequestedAt: Instant,
val cancellationApprovedAt: Instant?,
val cancelReason: String,
val canceledBy: Long,
)
fun CanceledPaymentEntity.toCancelDetailResponse(): PaymentCancelDetailResponse {
return PaymentCancelDetailResponse(
cancellationRequestedAt = this.requestedAt,
cancellationApprovedAt = this.canceledAt,
cancelReason = this.cancelReason,
canceledBy = this.canceledBy
)
}

View File

@ -3,7 +3,7 @@ package com.sangdol.roomescape.reservation.business
import com.sangdol.common.persistence.IDGenerator import com.sangdol.common.persistence.IDGenerator
import com.sangdol.roomescape.common.types.CurrentUserContext import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.payment.business.PaymentService import com.sangdol.roomescape.payment.business.PaymentService
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.payment.dto.PaymentResponse
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse import com.sangdol.roomescape.reservation.dto.PendingReservationCreateResponse
import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest import com.sangdol.roomescape.reservation.dto.ReservationCancelRequest
@ -132,7 +132,7 @@ class ReservationService(
val reservation: ReservationEntity = findOrThrow(id) val reservation: ReservationEntity = findOrThrow(id)
val user: UserContactResponse = userService.findContactById(reservation.userId) val user: UserContactResponse = userService.findContactById(reservation.userId)
val paymentDetail: PaymentWithDetailResponse? = paymentService.findDetailByReservationId(id) val paymentDetail: PaymentResponse? = paymentService.findDetailByReservationId(id)
return reservation.toAdditionalResponse( return reservation.toAdditionalResponse(
user = user, user = user,

View File

@ -1,6 +1,6 @@
package com.sangdol.roomescape.reservation.dto package com.sangdol.roomescape.reservation.dto
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.payment.dto.PaymentResponse
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.user.dto.UserContactResponse import com.sangdol.roomescape.user.dto.UserContactResponse
import java.time.Instant import java.time.Instant
@ -22,7 +22,7 @@ data class ReservationAdditionalResponse(
val reserver: ReserverInfo, val reserver: ReserverInfo,
val user: UserContactResponse, val user: UserContactResponse,
val applicationDateTime: Instant, val applicationDateTime: Instant,
val payment: PaymentWithDetailResponse?, val payment: PaymentResponse?,
) )
data class ReserverInfo( data class ReserverInfo(

View File

@ -1,6 +1,6 @@
package com.sangdol.roomescape.reservation.mapper package com.sangdol.roomescape.reservation.mapper
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.payment.dto.PaymentResponse
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse import com.sangdol.roomescape.reservation.dto.ReservationAdditionalResponse
import com.sangdol.roomescape.reservation.dto.ReservationOverviewResponse import com.sangdol.roomescape.reservation.dto.ReservationOverviewResponse
@ -40,7 +40,7 @@ fun ReservationEntity.toOverviewResponse(
fun ReservationEntity.toAdditionalResponse( fun ReservationEntity.toAdditionalResponse(
user: UserContactResponse, user: UserContactResponse,
payment: PaymentWithDetailResponse?, payment: PaymentResponse?,
): ReservationAdditionalResponse { ): ReservationAdditionalResponse {
return ReservationAdditionalResponse( return ReservationAdditionalResponse(
id = this.id, id = this.id,

View File

@ -6,7 +6,14 @@ import com.sangdol.common.utils.KoreaDateTime
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminEntity import com.sangdol.roomescape.admin.infrastructure.persistence.AdminEntity
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType
import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.payment.business.domain.BankCode
import com.sangdol.roomescape.payment.business.domain.CardIssuerCode
import com.sangdol.roomescape.payment.business.domain.CardOwnerType
import com.sangdol.roomescape.payment.business.domain.CardType
import com.sangdol.roomescape.payment.business.domain.EasyPayCompanyCode
import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import com.sangdol.roomescape.payment.business.domain.PaymentType
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity

View File

@ -11,7 +11,7 @@
//import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail //import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail
//import com.sangdol.roomescape.payment.infrastructure.common.* //import com.sangdol.roomescape.payment.infrastructure.common.*
//import com.sangdol.roomescape.payment.infrastructure.persistence.* //import com.sangdol.roomescape.payment.infrastructure.persistence.*
//import com.sangdol.roomescape.payment.web.PaymentConfirmRequest //import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
//import com.sangdol.roomescape.payment.web.PaymentCreateResponse //import com.sangdol.roomescape.payment.web.PaymentCreateResponse
//import com.sangdol.roomescape.supports.* //import com.sangdol.roomescape.supports.*
//import io.kotest.matchers.shouldBe //import io.kotest.matchers.shouldBe

View File

@ -1,8 +1,15 @@
package com.sangdol.roomescape.payment package com.sangdol.roomescape.payment
import com.sangdol.roomescape.payment.business.domain.BankCode
import com.sangdol.roomescape.payment.business.domain.CardIssuerCode
import com.sangdol.roomescape.payment.business.domain.CardOwnerType
import com.sangdol.roomescape.payment.business.domain.CardType
import com.sangdol.roomescape.payment.business.domain.EasyPayCompanyCode
import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import com.sangdol.roomescape.payment.business.domain.PaymentType
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.common.*
import io.kotest.assertions.assertSoftly import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe

View File

@ -2,12 +2,10 @@ package com.sangdol.roomescape.payment
import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.MockkBean
import com.sangdol.roomescape.payment.exception.ExternalPaymentException import com.sangdol.roomescape.payment.exception.ExternalPaymentException
import com.sangdol.roomescape.payment.exception.PaymentErrorCode import com.sangdol.roomescape.payment.dto.PaymentGatewayCancelResponse
import com.sangdol.roomescape.payment.exception.PaymentException import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.infrastructure.client.PaymentClientCancelResponse
import com.sangdol.roomescape.payment.infrastructure.client.PaymentClientConfirmResponse
import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient import com.sangdol.roomescape.payment.infrastructure.client.TosspayClient
import com.sangdol.roomescape.payment.infrastructure.common.PaymentStatus import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import io.kotest.assertions.assertSoftly import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
@ -116,7 +114,7 @@ class TosspayClientTest(
.createResponse(it) .createResponse(it)
} }
val cancelResponse: PaymentClientCancelResponse = client.cancel( val cancelResponse: PaymentGatewayCancelResponse = client.cancel(
SampleTosspayConstant.PAYMENT_KEY, SampleTosspayConstant.PAYMENT_KEY,
SampleTosspayConstant.AMOUNT, SampleTosspayConstant.AMOUNT,
SampleTosspayConstant.CANCEL_REASON SampleTosspayConstant.CANCEL_REASON
@ -162,13 +160,13 @@ class TosspayClientTest(
} }
fun runConfirmTest() { fun runConfirmTest() {
val paymentResponse: PaymentClientConfirmResponse = client.confirm( val paymentGatewayResponse: PaymentGatewayResponse = client.confirm(
SampleTosspayConstant.PAYMENT_KEY, SampleTosspayConstant.PAYMENT_KEY,
SampleTosspayConstant.ORDER_ID, SampleTosspayConstant.ORDER_ID,
SampleTosspayConstant.AMOUNT SampleTosspayConstant.AMOUNT
) )
assertSoftly(paymentResponse) { assertSoftly(paymentGatewayResponse) {
this.paymentKey shouldBe SampleTosspayConstant.PAYMENT_KEY this.paymentKey shouldBe SampleTosspayConstant.PAYMENT_KEY
this.totalAmount shouldBe SampleTosspayConstant.AMOUNT this.totalAmount shouldBe SampleTosspayConstant.AMOUNT
} }

View File

@ -5,9 +5,9 @@ import com.sangdol.common.types.web.HttpStatus
import com.sangdol.common.utils.KoreaDate import com.sangdol.common.utils.KoreaDate
import com.sangdol.common.utils.KoreaTime import com.sangdol.common.utils.KoreaTime
import com.sangdol.roomescape.auth.exception.AuthErrorCode import com.sangdol.roomescape.auth.exception.AuthErrorCode
import com.sangdol.roomescape.payment.infrastructure.common.BankCode import com.sangdol.roomescape.payment.business.domain.BankCode
import com.sangdol.roomescape.payment.infrastructure.common.CardIssuerCode import com.sangdol.roomescape.payment.business.domain.CardIssuerCode
import com.sangdol.roomescape.payment.infrastructure.common.EasyPayCompanyCode import com.sangdol.roomescape.payment.business.domain.EasyPayCompanyCode
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentDetailRepository import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentDetailRepository
import com.sangdol.roomescape.reservation.exception.ReservationErrorCode import com.sangdol.roomescape.reservation.exception.ReservationErrorCode
import com.sangdol.roomescape.reservation.infrastructure.persistence.CanceledReservationRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.CanceledReservationRepository

View File

@ -1,32 +1,27 @@
package com.sangdol.roomescape.supports package com.sangdol.roomescape.supports
import com.sangdol.roomescape.payment.business.PaymentWriter import com.sangdol.roomescape.payment.business.PaymentWriter
import com.sangdol.roomescape.payment.infrastructure.client.CardDetail import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.infrastructure.client.EasyPayDetail import com.sangdol.roomescape.payment.dto.*
import com.sangdol.roomescape.payment.infrastructure.client.TransferDetail
import com.sangdol.roomescape.payment.infrastructure.common.PaymentMethod
import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity import com.sangdol.roomescape.payment.infrastructure.persistence.CanceledPaymentEntity
import com.sangdol.roomescape.payment.infrastructure.persistence.PaymentEntity 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.payment.web.PaymentConfirmRequest import com.sangdol.roomescape.payment.mapper.toResponse
import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.payment.web.toDetailResponse
import com.sangdol.roomescape.payment.web.toPaymentDetailResponse
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationRepository
import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.reservation.mapper.toEntity import com.sangdol.roomescape.reservation.mapper.toEntity
import com.sangdol.roomescape.schedule.dto.ScheduleCreateRequest
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus
import com.sangdol.roomescape.schedule.dto.ScheduleCreateRequest
import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity
import com.sangdol.roomescape.store.infrastructure.persistence.StoreRepository import com.sangdol.roomescape.store.infrastructure.persistence.StoreRepository
import com.sangdol.roomescape.store.infrastructure.persistence.StoreStatus import com.sangdol.roomescape.store.infrastructure.persistence.StoreStatus
import com.sangdol.roomescape.theme.dto.ThemeCreateRequest
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity
import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeRepository
import com.sangdol.roomescape.theme.dto.ThemeCreateRequest
import com.sangdol.roomescape.theme.mapper.toEntity import com.sangdol.roomescape.theme.mapper.toEntity
import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
@ -164,10 +159,10 @@ class DummyInitializer(
fun createPayment( fun createPayment(
reservationId: Long, reservationId: Long,
request: PaymentConfirmRequest = PaymentFixture.confirmRequest, request: PaymentConfirmRequest = PaymentFixture.confirmRequest,
cardDetail: CardDetail? = null, cardDetail: CardDetailResponse? = null,
easyPayDetail: EasyPayDetail? = null, easyPayDetail: EasyPayDetailResponse? = null,
transferDetail: TransferDetail? = null, transferDetail: TransferDetailResponse? = null,
): PaymentWithDetailResponse { ): PaymentResponse {
val method = if (easyPayDetail != null) { val method = if (easyPayDetail != null) {
PaymentMethod.EASY_PAY PaymentMethod.EASY_PAY
} else if (cardDetail != null) { } else if (cardDetail != null) {
@ -190,12 +185,12 @@ class DummyInitializer(
val payment = paymentWriter.createPayment( val payment = paymentWriter.createPayment(
reservationId = reservationId, reservationId = reservationId,
paymentClientConfirmResponse = clientConfirmResponse paymentGatewayResponse = clientConfirmResponse
) )
val detail = paymentWriter.createDetail(clientConfirmResponse, payment.id) val detail = paymentWriter.createDetail(clientConfirmResponse, payment.id)
return payment.toDetailResponse(detail = detail.toPaymentDetailResponse(), cancel = null) return payment.toResponse(detail = detail.toResponse(), cancel = null)
} }
fun cancelPayment( fun cancelPayment(

View File

@ -6,10 +6,22 @@ import com.sangdol.common.utils.KoreaDateTime
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminEntity import com.sangdol.roomescape.admin.infrastructure.persistence.AdminEntity
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel
import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType
import com.sangdol.roomescape.payment.infrastructure.client.* import com.sangdol.roomescape.payment.business.domain.BankCode
import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.payment.business.domain.CardIssuerCode
import com.sangdol.roomescape.payment.web.PaymentCancelRequest import com.sangdol.roomescape.payment.business.domain.CardOwnerType
import com.sangdol.roomescape.payment.web.PaymentConfirmRequest import com.sangdol.roomescape.payment.business.domain.CardType
import com.sangdol.roomescape.payment.business.domain.EasyPayCompanyCode
import com.sangdol.roomescape.payment.business.domain.PaymentMethod
import com.sangdol.roomescape.payment.business.domain.PaymentStatus
import com.sangdol.roomescape.payment.business.domain.PaymentType
import com.sangdol.roomescape.payment.dto.CancelDetail
import com.sangdol.roomescape.payment.dto.CardDetailResponse
import com.sangdol.roomescape.payment.dto.EasyPayDetailResponse
import com.sangdol.roomescape.payment.dto.PaymentGatewayCancelResponse
import com.sangdol.roomescape.payment.dto.PaymentGatewayResponse
import com.sangdol.roomescape.payment.dto.TransferDetailResponse
import com.sangdol.roomescape.payment.dto.PaymentCancelRequest
import com.sangdol.roomescape.payment.dto.PaymentConfirmRequest
import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest import com.sangdol.roomescape.reservation.dto.PendingReservationCreateRequest
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity
import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntityFactory
@ -254,7 +266,7 @@ object PaymentFixture {
cardType: CardType = CardType.entries.random(), cardType: CardType = CardType.entries.random(),
ownerType: CardOwnerType = CardOwnerType.entries.random(), ownerType: CardOwnerType = CardOwnerType.entries.random(),
installmentPlanMonths: Int = 0, installmentPlanMonths: Int = 0,
): CardDetail = CardDetail( ): CardDetailResponse = CardDetailResponse(
issuerCode = issuerCode, issuerCode = issuerCode,
number = "${(400000..500000).random()}*********", number = "${(400000..500000).random()}*********",
amount = amount, amount = amount,
@ -269,12 +281,12 @@ object PaymentFixture {
amount: Int, amount: Int,
provider: EasyPayCompanyCode = EasyPayCompanyCode.entries.random(), provider: EasyPayCompanyCode = EasyPayCompanyCode.entries.random(),
discountAmount: Int = 0 discountAmount: Int = 0
): EasyPayDetail = EasyPayDetail(provider, amount, discountAmount) ): EasyPayDetailResponse = EasyPayDetailResponse(provider, amount, discountAmount)
fun transferDetail( fun transferDetail(
bankCode: BankCode = BankCode.entries.random(), bankCode: BankCode = BankCode.entries.random(),
settlementStatus: String = "COMPLETED" settlementStatus: String = "COMPLETED"
): TransferDetail = TransferDetail( ): TransferDetailResponse = TransferDetailResponse(
bankCode = bankCode, bankCode = bankCode,
settlementStatus = settlementStatus settlementStatus = settlementStatus
) )
@ -283,11 +295,11 @@ object PaymentFixture {
paymentKey: String, paymentKey: String,
amount: Int, amount: Int,
method: PaymentMethod, method: PaymentMethod,
cardDetail: CardDetail?, cardDetail: CardDetailResponse?,
easyPayDetail: EasyPayDetail?, easyPayDetail: EasyPayDetailResponse?,
transferDetail: TransferDetail?, transferDetail: TransferDetailResponse?,
orderId: String = randomString(25), orderId: String = randomString(25),
) = PaymentClientConfirmResponse( ) = PaymentGatewayResponse(
paymentKey = paymentKey, paymentKey = paymentKey,
status = PaymentStatus.DONE, status = PaymentStatus.DONE,
orderId = orderId, orderId = orderId,
@ -309,7 +321,7 @@ object PaymentFixture {
transferDiscountAmount: Int = 0, transferDiscountAmount: Int = 0,
easypayDiscountAmount: Int = 0, easypayDiscountAmount: Int = 0,
cancelReason: String = "cancelReason" cancelReason: String = "cancelReason"
) = PaymentClientCancelResponse( ) = PaymentGatewayCancelResponse(
status = PaymentStatus.CANCELED, status = PaymentStatus.CANCELED,
cancels = CancelDetail( cancels = CancelDetail(
cancelAmount = amount, cancelAmount = amount,