114 lines
4.4 KiB
Kotlin

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.HttpStatus
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.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.payment.web.PaymentApprove
import roomescape.payment.web.PaymentCancel
import java.io.IOException
import java.util.Map
@Component
class TossPaymentClient(
private val log: KLogger = KotlinLogging.logger {},
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 confirmPayment(paymentRequest: PaymentApprove.Request): PaymentApprove.Response {
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) }
)
.body(PaymentApprove.Response::class.java)
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
}
fun cancelPayment(cancelRequest: PaymentCancel.Request): PaymentCancel.Response {
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) }
)
.body(PaymentCancel.Response::class.java)
?: throw RoomescapeException(ErrorType.PAYMENT_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
}
private fun logPaymentInfo(paymentRequest: PaymentApprove.Request) {
log.info {
"결제 승인 요청: paymentKey=${paymentRequest.paymentKey}, orderId=${paymentRequest.orderId}, " +
"amount=${paymentRequest.amount}, paymentType=${paymentRequest.paymentType}"
}
}
private fun logPaymentCancelInfo(cancelRequest: PaymentCancel.Request) {
log.info {
"결제 취소 요청: paymentKey=${cancelRequest.paymentKey}, amount=${cancelRequest.amount}, " +
"cancelReason=${cancelRequest.cancelReason}"
}
}
@Throws(IOException::class)
private fun handlePaymentError(
res: ClientHttpResponse
): Nothing {
val statusCode = res.statusCode
val errorType = getErrorTypeByStatusCode(statusCode)
val errorResponse = getErrorResponse(res)
throw RoomescapeException(
errorType,
"[ErrorCode = ${errorResponse.code}, ErrorMessage = ${errorResponse.message}]",
statusCode
)
}
@Throws(IOException::class)
private fun getErrorResponse(
res: ClientHttpResponse
): TossPaymentErrorResponse {
val body = res.body
val errorResponse = objectMapper.readValue(body, TossPaymentErrorResponse::class.java)
body.close()
return errorResponse
}
private fun getErrorTypeByStatusCode(
statusCode: HttpStatusCode
): ErrorType {
if (statusCode.is4xxClientError) {
return ErrorType.PAYMENT_ERROR
}
return ErrorType.PAYMENT_SERVER_ERROR
}
}