diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index f020d50f80..3c3413caf2 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -11,6 +11,8 @@ pub(crate) const ALPHABETS: [char; 62] = [ ]; /// API client request timeout (in seconds) 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) pub const DEFAULT_FULFILLMENT_TIME: i64 = 15 * 60; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 933ad295c9..b877eff907 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -186,7 +186,7 @@ pub fn http_not_implemented() -> actix_web::HttpResponse { .error_response() } -#[derive(Debug, thiserror::Error)] +#[derive(Debug, thiserror::Error, PartialEq)] pub enum ApiClientError { #[error("Header map construction failed")] HeaderMapConstructionFailed, @@ -315,6 +315,8 @@ pub enum ConnectorError { MissingPaymentMethodType, #[error("Balance in the payment method is low")] InSufficientBalanceInPaymentMethod, + #[error("Server responded with Request Timeout")] + RequestTimeoutReceived, } #[derive(Debug, thiserror::Error)] @@ -490,6 +492,18 @@ pub enum WebhooksFlowError { 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")] pub mod error_stack_parsing { diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index 067b6c71ac..e7b07efe73 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -4,6 +4,7 @@ use common_utils::ext_traits::AsyncExt; use error_stack::{IntoReport, ResultExt}; use crate::{ + consts, core::{ errors::{self, RouterResult}, payments, @@ -151,16 +152,38 @@ pub async fn refresh_connector_auth( types::AccessToken, > = 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, connector_integration, router_data, payments::CallConnectorAction::Trigger, None, ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Could not refresh access token")?; + .await; + + 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::CONTEXT, 1, @@ -169,5 +192,5 @@ pub async fn refresh_connector_auth( connector.connector_name.to_string(), )], ); - Ok(access_token_router_data.response) + Ok(access_token_router_data) } diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 2a24acdbd1..c919fc943e 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -325,8 +325,16 @@ where }; Ok(response) } - Err(error) => Err(error - .change_context(errors::ConnectorError::ProcessingStepFailed(None))), + Err(error) => { + 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),