mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
refactor(access_token): handle timeout errors gracefully (#1882)
This commit is contained in:
@ -11,6 +11,8 @@ pub(crate) const ALPHABETS: [char; 62] = [
|
|||||||
];
|
];
|
||||||
/// API client request timeout (in seconds)
|
/// API client request timeout (in seconds)
|
||||||
pub const REQUEST_TIME_OUT: u64 = 30;
|
pub const REQUEST_TIME_OUT: u64 = 30;
|
||||||
|
pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT";
|
||||||
|
pub const REQUEST_TIMEOUT_ERROR_MESSAGE: &str = "Connector did not respond in specified time";
|
||||||
|
|
||||||
///Payment intent fulfillment default timeout (in seconds)
|
///Payment intent fulfillment default timeout (in seconds)
|
||||||
pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
|
pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60;
|
||||||
|
|||||||
@ -186,7 +186,7 @@ pub fn http_not_implemented() -> actix_web::HttpResponse<BoxBody> {
|
|||||||
.error_response()
|
.error_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, PartialEq)]
|
||||||
pub enum ApiClientError {
|
pub enum ApiClientError {
|
||||||
#[error("Header map construction failed")]
|
#[error("Header map construction failed")]
|
||||||
HeaderMapConstructionFailed,
|
HeaderMapConstructionFailed,
|
||||||
@ -315,6 +315,8 @@ pub enum ConnectorError {
|
|||||||
MissingPaymentMethodType,
|
MissingPaymentMethodType,
|
||||||
#[error("Balance in the payment method is low")]
|
#[error("Balance in the payment method is low")]
|
||||||
InSufficientBalanceInPaymentMethod,
|
InSufficientBalanceInPaymentMethod,
|
||||||
|
#[error("Server responded with Request Timeout")]
|
||||||
|
RequestTimeoutReceived,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@ -490,6 +492,18 @@ pub enum WebhooksFlowError {
|
|||||||
MissingRequiredField { field_name: &'static str },
|
MissingRequiredField { field_name: &'static str },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ApiClientError {
|
||||||
|
pub fn is_upstream_timeout(&self) -> bool {
|
||||||
|
self == &Self::RequestTimeoutReceived
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectorError {
|
||||||
|
pub fn is_connector_timeout(&self) -> bool {
|
||||||
|
self == &Self::RequestTimeoutReceived
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "detailed_errors")]
|
#[cfg(feature = "detailed_errors")]
|
||||||
pub mod error_stack_parsing {
|
pub mod error_stack_parsing {
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use common_utils::ext_traits::AsyncExt;
|
|||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
consts,
|
||||||
core::{
|
core::{
|
||||||
errors::{self, RouterResult},
|
errors::{self, RouterResult},
|
||||||
payments,
|
payments,
|
||||||
@ -151,16 +152,38 @@ pub async fn refresh_connector_auth(
|
|||||||
types::AccessToken,
|
types::AccessToken,
|
||||||
> = connector.connector.get_connector_integration();
|
> = connector.connector.get_connector_integration();
|
||||||
|
|
||||||
let access_token_router_data = services::execute_connector_processing_step(
|
let access_token_router_data_result = services::execute_connector_processing_step(
|
||||||
state,
|
state,
|
||||||
connector_integration,
|
connector_integration,
|
||||||
router_data,
|
router_data,
|
||||||
payments::CallConnectorAction::Trigger,
|
payments::CallConnectorAction::Trigger,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
|
||||||
.attach_printable("Could not refresh access token")?;
|
let access_token_router_data = match access_token_router_data_result {
|
||||||
|
Ok(router_data) => Ok(router_data.response),
|
||||||
|
Err(connector_error) => {
|
||||||
|
// If we receive a timeout error from the connector, then
|
||||||
|
// the error has to be handled gracefully by updating the payment status to failed.
|
||||||
|
// further payment flow will not be continued
|
||||||
|
if connector_error.current_context().is_connector_timeout() {
|
||||||
|
let error_response = types::ErrorResponse {
|
||||||
|
code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
|
||||||
|
message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
|
||||||
|
reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()),
|
||||||
|
status_code: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Err(error_response))
|
||||||
|
} else {
|
||||||
|
Err(connector_error
|
||||||
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
|
.attach_printable("Could not refresh access token"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
metrics::ACCESS_TOKEN_CREATION.add(
|
metrics::ACCESS_TOKEN_CREATION.add(
|
||||||
&metrics::CONTEXT,
|
&metrics::CONTEXT,
|
||||||
1,
|
1,
|
||||||
@ -169,5 +192,5 @@ pub async fn refresh_connector_auth(
|
|||||||
connector.connector_name.to_string(),
|
connector.connector_name.to_string(),
|
||||||
)],
|
)],
|
||||||
);
|
);
|
||||||
Ok(access_token_router_data.response)
|
Ok(access_token_router_data)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -325,8 +325,16 @@ where
|
|||||||
};
|
};
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
Err(error) => Err(error
|
Err(error) => {
|
||||||
.change_context(errors::ConnectorError::ProcessingStepFailed(None))),
|
if error.current_context().is_upstream_timeout() {
|
||||||
|
Err(error
|
||||||
|
.change_context(errors::ConnectorError::RequestTimeoutReceived))
|
||||||
|
} else {
|
||||||
|
Err(error.change_context(
|
||||||
|
errors::ConnectorError::ProcessingStepFailed(None),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Ok(router_data),
|
None => Ok(router_data),
|
||||||
|
|||||||
Reference in New Issue
Block a user