generated from pricelees/issue-pr-template
[#35] 결제 스키마 재정의 & 예약 조회 페이지 개선 #36
@ -0,0 +1,133 @@
|
||||
package roomescape.payment.infrastructure.client.v2
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.http.HttpMethod
|
||||
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.ResponseErrorHandler
|
||||
import org.springframework.web.client.RestClient
|
||||
import roomescape.payment.exception.PaymentErrorCode
|
||||
import roomescape.payment.exception.PaymentException
|
||||
import roomescape.payment.infrastructure.client.TossPaymentErrorResponse
|
||||
import java.net.URI
|
||||
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
|
||||
@Component
|
||||
class TosspaymentClientV2(
|
||||
objectMapper: ObjectMapper,
|
||||
tossPaymentClientBuilder: RestClient.Builder
|
||||
) {
|
||||
private val confirmClient = ConfirmClient(objectMapper, tossPaymentClientBuilder.build())
|
||||
private val cancelClient = CancelClient(objectMapper, tossPaymentClientBuilder.build())
|
||||
|
||||
fun confirm(request: PaymentConfirmRequest): PaymentConfirmResponse {
|
||||
log.info { "[TossPaymentClientV2.confirm] 승인 요청: request=$request" }
|
||||
|
||||
return confirmClient.request(request)
|
||||
}
|
||||
|
||||
fun cancel(request: PaymentCancelRequestV2): PaymentCancelResponseV2 {
|
||||
log.info { "[TossPaymentClient.cancel] 취소 요청: request=$request" }
|
||||
|
||||
return cancelClient.request(request)
|
||||
}
|
||||
}
|
||||
|
||||
private class ConfirmClient(
|
||||
objectMapper: ObjectMapper,
|
||||
private val client: RestClient,
|
||||
) {
|
||||
companion object {
|
||||
private const val CONFIRM_URI: String = "/v1/payments/confirm"
|
||||
}
|
||||
|
||||
private val errorHandler: TosspayErrorHandler = TosspayErrorHandler(objectMapper)
|
||||
|
||||
fun request(request: PaymentConfirmRequest): PaymentConfirmResponse = client.post()
|
||||
.uri(CONFIRM_URI)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(request)
|
||||
.retrieve()
|
||||
.onStatus(errorHandler)
|
||||
.body(PaymentConfirmResponse::class.java)
|
||||
?: run {
|
||||
log.error { "[TossPaymentConfirmClient.request] 응답 바디 변환 실패" }
|
||||
throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
private class CancelClient(
|
||||
objectMapper: ObjectMapper,
|
||||
private val client: RestClient,
|
||||
) {
|
||||
companion object {
|
||||
private const val CANCEL_URI: String = "/v1/payments/{paymentKey}/cancel"
|
||||
}
|
||||
|
||||
private val errorHandler: TosspayErrorHandler = TosspayErrorHandler(objectMapper)
|
||||
|
||||
fun request(request: PaymentCancelRequestV2): PaymentCancelResponseV2 = client.post()
|
||||
.uri(CANCEL_URI, request.paymentKey)
|
||||
.body(
|
||||
mapOf(
|
||||
"cancelReason" to request.cancelReason,
|
||||
"cancelAmount" to request.amount,
|
||||
)
|
||||
)
|
||||
.retrieve()
|
||||
.onStatus(errorHandler)
|
||||
.body(PaymentCancelResponseV2::class.java)
|
||||
?: run {
|
||||
log.error { "[TossPaymentClient] 응답 바디 변환 실패" }
|
||||
throw PaymentException(PaymentErrorCode.PAYMENT_UNEXPECTED_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
private class TosspayErrorHandler(
|
||||
private val objectMapper: ObjectMapper
|
||||
) : ResponseErrorHandler {
|
||||
override fun hasError(response: ClientHttpResponse): Boolean {
|
||||
val statusCode: HttpStatusCode = response.statusCode
|
||||
|
||||
return statusCode.is4xxClientError || statusCode.is5xxServerError
|
||||
}
|
||||
|
||||
override fun handleError(
|
||||
url: URI,
|
||||
method: HttpMethod,
|
||||
response: ClientHttpResponse
|
||||
): Nothing {
|
||||
val requestType: String = paymentRequestType(url)
|
||||
log.warn { "[TossPaymentClient] $requestType 요청 실패: response: ${toErrorResponse(response)}" }
|
||||
|
||||
throw PaymentException(paymentErrorCode(response.statusCode))
|
||||
}
|
||||
|
||||
private fun paymentRequestType(url: URI): String {
|
||||
val type = url.path.split("/").last()
|
||||
|
||||
if (type == "cancel") {
|
||||
return "취소"
|
||||
}
|
||||
return "승인"
|
||||
}
|
||||
|
||||
private fun paymentErrorCode(statusCode: HttpStatusCode) = if (statusCode.is4xxClientError) {
|
||||
PaymentErrorCode.PAYMENT_CLIENT_ERROR
|
||||
} else {
|
||||
PaymentErrorCode.PAYMENT_PROVIDER_ERROR
|
||||
}
|
||||
|
||||
private fun toErrorResponse(response: ClientHttpResponse): TossPaymentErrorResponse {
|
||||
val body = response.body
|
||||
|
||||
return objectMapper.readValue(body, TossPaymentErrorResponse::class.java).also {
|
||||
body.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user