I have a Spring Boot v3 application written in Kotlin with coroutines/suspend functions.
I have written a simple PayPalHttpClient class to call the PayPal Orders v2 API.
I have the method to create an order as below. If the token expires, it returns 401 UNAUTHORIZED. if that's the case, I set the local accessToken to null and I call again the getAccessToken() function to retrieve a new token. I don't like that for each method I have to create the same code to handle 401s errors. Also, code is recursive meaning that if the createOrder function keeps giving 401, the code deadlocks.
Is there a way nicer way to handle refreshing the token in HttpClient like classes?
private lateinit var webClient: WebClient
private var payPalAccessTokenResponse: PayPalAccessTokenResponse? = null
@PostConstruct
fun init() {
webClient = WebClient.builder()
.baseUrl(paypalConfig.baseUrl)
.build()
}
suspend fun getAccessToken(): PayPalAccessTokenResponse {
val payPalAccessTokenResponse = webClient.post()
.uri { it.path("/v1/oauth2/token").build() }
.header(ACCEPT, APPLICATION_JSON_VALUE)
.header(AUTHORIZATION, encodeBasicCredentials())
.header(ACCEPT_LANGUAGE, "en_US")
.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE)
.body(BodyInserters.fromFormData("grant_type", "client_credentials"))
.retrieve()
.awaitBody<PayPalAccessTokenResponse>()
this.payPalAccessTokenResponse = payPalAccessTokenResponse
return payPalAccessTokenResponse
}
suspend fun createOrder(payPalOrderRequest: PayPalOrderRequest): PayPalOrderResponse {
return try {
val accessToken = payPalAccessTokenResponse?.accessToken ?: getAccessToken().accessToken
createOrderRequest(payPalOrderRequest, accessToken)
} catch (ex: HttpClientErrorException) {
createOrderRequest(payPalOrderRequest, getAccessToken().accessToken)
}
}
suspend fun createOrderRequest(payPalOrderRequest: PayPalOrderRequest, authToken: String): PayPalOrderResponse {
val body = objectMapper.writeValueAsString(payPalOrderRequest)
return webClient
.post()
.uri { it.path("/v2/checkout/orders").build() }
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.header(
AUTHORIZATION,
authToken
)
.body(BodyInserters.fromValue(body))
.retrieve()
.onStatus({ status -> status.value() == 401 }) { clientResponse ->
clientResponse.bodyToMono(String::class.java).map { body ->
payPalAccessTokenResponse = null
HttpClientErrorException(HttpStatus.UNAUTHORIZED, body)
}
}
.awaitBody<PayPalOrderResponse>()
}