diff --git a/Cargo.lock b/Cargo.lock index 3e1101fdbf4..60f064c45e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3525,7 +3525,7 @@ dependencies = [ [[package]] name = "grpc-api-types" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=0409f6aa1014dd1b9827fabfa4fa424e16d07ebc#0409f6aa1014dd1b9827fabfa4fa424e16d07ebc" +source = "git+https://github.com/juspay/connector-service?rev=4387a6310dc9c2693b453b455a8032623f3d6a81#4387a6310dc9c2693b453b455a8032623f3d6a81" dependencies = [ "axum 0.8.4", "error-stack 0.5.0", @@ -6899,7 +6899,7 @@ dependencies = [ [[package]] name = "rust-grpc-client" version = "0.1.0" -source = "git+https://github.com/juspay/connector-service?rev=0409f6aa1014dd1b9827fabfa4fa424e16d07ebc#0409f6aa1014dd1b9827fabfa4fa424e16d07ebc" +source = "git+https://github.com/juspay/connector-service?rev=4387a6310dc9c2693b453b455a8032623f3d6a81#4387a6310dc9c2693b453b455a8032623f3d6a81" dependencies = [ "grpc-api-types", ] diff --git a/config/config.example.toml b/config/config.example.toml index 55ab8675605..9cb4065b5f9 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -1171,3 +1171,6 @@ allow_connected_merchants = false # Enable or disable connected merchant account [chat] enabled = false # Enable or disable chat features hyperswitch_ai_host = "http://0.0.0.0:8000" # Hyperswitch ai workflow host + +[proxy_status_mapping] +proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response \ No newline at end of file diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 23ff9f16e70..6ce8611ef02 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -386,3 +386,6 @@ connection_timeout = 10 # Connection Timeout Duration in Seconds [chat] enabled = false # Enable or disable chat features hyperswitch_ai_host = "http://0.0.0.0:8000" # Hyperswitch ai workflow host + +[proxy_status_mapping] +proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response \ No newline at end of file diff --git a/config/development.toml b/config/development.toml index b3aa9fcb9a5..403a55552b2 100644 --- a/config/development.toml +++ b/config/development.toml @@ -1269,3 +1269,6 @@ version = "HOSTNAME" [chat] enabled = false hyperswitch_ai_host = "http://0.0.0.0:8000" + +[proxy_status_mapping] +proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response \ No newline at end of file diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 09ef545f584..b6ef24fcd2c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -1160,3 +1160,6 @@ connector_names = "stripe, adyen" # Comma-separated list of allowe [infra_values] cluster = "CLUSTER" # value of CLUSTER from deployment version = "HOSTNAME" # value of HOSTNAME from deployment which tells its version + +[proxy_status_mapping] +proxy_connector_http_status_code = false # If enabled, the http status code of the connector will be proxied in the response \ No newline at end of file diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index bfa0a8b6132..9e83f331b94 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -53,7 +53,7 @@ reqwest = { version = "0.11.27", features = ["rustls-tls"] } http = "0.2.12" url = { version = "2.5.4", features = ["serde"] } quick-xml = { version = "0.31.0", features = ["serialize"] } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "0409f6aa1014dd1b9827fabfa4fa424e16d07ebc", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4387a6310dc9c2693b453b455a8032623f3d6a81", package = "rust-grpc-client" } # First party crates diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 3f3e574fcdd..4d1e9917ad1 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -88,7 +88,7 @@ reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "mult ring = "0.17.14" rust_decimal = { version = "1.37.1", features = ["serde-with-float", "serde-with-str"] } rust-i18n = { git = "https://github.com/kashif-m/rust-i18n", rev = "f2d8096aaaff7a87a847c35a5394c269f75e077a" } -unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "0409f6aa1014dd1b9827fabfa4fa424e16d07ebc", package = "rust-grpc-client" } +unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "4387a6310dc9c2693b453b455a8032623f3d6a81", package = "rust-grpc-client" } rustc-hash = "1.1.0" rustls = "0.22" rustls-pemfile = "2" diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index f517cb69739..d00ece8360a 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -87,7 +87,7 @@ where let response = S::try_from(response); match response { Ok(response) => match serde_json::to_string(&response) { - Ok(res) => api::http_response_json_with_headers(res, headers, None), + Ok(res) => api::http_response_json_with_headers(res, headers, None, None), Err(_) => api::http_response_err( r#"{ "error": { diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 4f53a2ef2df..6cb720650a2 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -548,5 +548,6 @@ pub(crate) async fn fetch_raw_secrets( merchant_id_auth: conf.merchant_id_auth, infra_values: conf.infra_values, enhancement: conf.enhancement, + proxy_status_mapping: conf.proxy_status_mapping, } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 1604b7eaa9d..29c3d3598c6 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -164,6 +164,7 @@ pub struct Settings { pub infra_values: Option>, #[serde(default)] pub enhancement: Option>, + pub proxy_status_mapping: ProxyStatusMapping, } #[derive(Debug, Deserialize, Clone, Default)] @@ -844,6 +845,12 @@ pub struct MerchantIdAuthSettings { pub merchant_id_auth_enabled: bool, } +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +pub struct ProxyStatusMapping { + pub proxy_connector_http_status_code: bool, +} + #[derive(Debug, Clone, Default, Deserialize)] #[serde(default)] pub struct WebhooksSettings { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a912dc27b82..106db14fdef 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -214,6 +214,7 @@ where .perform_routing(&merchant_context, profile, state, &mut payment_data) .await?; + let mut connector_http_status_code = None; let (payment_data, connector_response_data) = match connector { ConnectorCallType::PreDetermined(connector_data) => { let (mca_type_details, updated_customer, router_data) = @@ -265,6 +266,9 @@ where let payments_response_operation = Box::new(PaymentResponse); + connector_http_status_code = router_data.connector_http_status_code; + add_connector_http_status_code_metrics(connector_http_status_code); + payments_response_operation .to_post_update_tracker()? .save_pm_and_mandate( @@ -342,6 +346,9 @@ where let payments_response_operation = Box::new(PaymentResponse); + connector_http_status_code = router_data.connector_http_status_code; + add_connector_http_status_code_metrics(connector_http_status_code); + payments_response_operation .to_post_update_tracker()? .save_pm_and_mandate( @@ -395,7 +402,7 @@ where payment_data, req, customer, - None, + connector_http_status_code, None, connector_response_data, )) @@ -492,6 +499,9 @@ where let payments_response_operation = Box::new(PaymentResponse); + let connector_http_status_code = router_data.connector_http_status_code; + add_connector_http_status_code_metrics(connector_http_status_code); + let payment_data = payments_response_operation .to_post_update_tracker()? .update_tracker( @@ -503,7 +513,13 @@ where ) .await?; - Ok((payment_data, req, None, None, connector_response_data)) + Ok(( + payment_data, + req, + connector_http_status_code, + None, + connector_response_data, + )) } #[cfg(feature = "v1")] diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index e077362cc1f..2aa69e6a36b 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -860,7 +860,7 @@ async fn call_unified_connector_service_authorize( let payment_authorize_response = response.into_inner(); - let (status, router_data_response) = + let (status, router_data_response, status_code) = handle_unified_connector_service_response_for_payment_authorize( payment_authorize_response.clone(), ) @@ -872,6 +872,7 @@ async fn call_unified_connector_service_authorize( router_data.raw_connector_response = payment_authorize_response .raw_connector_response .map(Secret::new); + router_data.connector_http_status_code = Some(status_code); Ok(()) } @@ -916,7 +917,7 @@ async fn call_unified_connector_service_repeat_payment( let payment_repeat_response = response.into_inner(); - let (status, router_data_response) = + let (status, router_data_response, status_code) = handle_unified_connector_service_response_for_payment_repeat( payment_repeat_response.clone(), ) @@ -928,6 +929,7 @@ async fn call_unified_connector_service_repeat_payment( router_data.raw_connector_response = payment_repeat_response .raw_connector_response .map(Secret::new); + router_data.connector_http_status_code = Some(status_code); Ok(()) } diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index 84752f16520..641af1d397e 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -250,7 +250,7 @@ impl Feature let payment_get_response = response.into_inner(); - let (status, router_data_response) = + let (status, router_data_response, status_code) = handle_unified_connector_service_response_for_payment_get(payment_get_response.clone()) .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Failed to deserialize UCS response")?; @@ -258,6 +258,7 @@ impl Feature self.status = status; self.response = router_data_response; self.raw_connector_response = payment_get_response.raw_connector_response.map(Secret::new); + self.connector_http_status_code = Some(status_code); Ok(()) } diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index 6204d521680..c89f04aa69a 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -246,7 +246,7 @@ impl Feature for types::Setup let payment_register_response = response.into_inner(); - let (status, router_data_response) = + let (status, router_data_response, status_code) = handle_unified_connector_service_response_for_payment_register( payment_register_response.clone(), ) @@ -255,6 +255,7 @@ impl Feature for types::Setup self.status = status; self.response = router_data_response; + self.connector_http_status_code = Some(status_code); // UCS does not return raw connector response for setup mandate right now // self.raw_connector_response = payment_register_response // .raw_connector_response diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index dd9b41ee9d2..55bf4fe4298 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -36,7 +36,7 @@ use crate::{ payments::{self, helpers}, utils as core_utils, }, - headers::X_PAYMENT_CONFIRM_SOURCE, + headers::{X_CONNECTOR_HTTP_STATUS_CODE, X_PAYMENT_CONFIRM_SOURCE}, routes::{metrics, SessionState}, services::{self, RedirectForm}, types::{ @@ -1586,9 +1586,17 @@ where status: payment_intent.status, }; + let headers = connector_http_status_code + .map(|status_code| { + vec![( + X_CONNECTOR_HTTP_STATUS_CODE.to_string(), + Maskable::new_normal(status_code.to_string()), + )] + }) + .unwrap_or_default(); + Ok(services::ApplicationResponse::JsonWithHeaders(( - response, - vec![], + response, headers, ))) } } @@ -1969,6 +1977,15 @@ where .clone() .or(profile.return_url.clone()); + let headers = connector_http_status_code + .map(|status_code| { + vec![( + X_CONNECTOR_HTTP_STATUS_CODE.to_string(), + Maskable::new_normal(status_code.to_string()), + )] + }) + .unwrap_or_default(); + let response = api_models::payments::PaymentsResponse { id: payment_intent.id.clone(), status: payment_intent.status, @@ -2000,8 +2017,7 @@ where }; Ok(services::ApplicationResponse::JsonWithHeaders(( - response, - vec![], + response, headers, ))) } } @@ -2066,6 +2082,15 @@ where let return_url = payment_intent.return_url.or(profile.return_url.clone()); + let headers = connector_http_status_code + .map(|status_code| { + vec![( + X_CONNECTOR_HTTP_STATUS_CODE.to_string(), + Maskable::new_normal(status_code.to_string()), + )] + }) + .unwrap_or_default(); + let response = api_models::payments::PaymentsResponse { id: payment_intent.id.clone(), status: payment_intent.status, @@ -2101,8 +2126,7 @@ where }; Ok(services::ApplicationResponse::JsonWithHeaders(( - response, - vec![], + response, headers, ))) } } @@ -2540,7 +2564,7 @@ where let mut headers = connector_http_status_code .map(|status_code| { vec![( - "connector_http_status_code".to_string(), + X_CONNECTOR_HTTP_STATUS_CODE.to_string(), Maskable::new_normal(status_code.to_string()), )] }) diff --git a/crates/router/src/core/unified_connector_service.rs b/crates/router/src/core/unified_connector_service.rs index 79dd1cc7013..107b6e7e7a2 100644 --- a/crates/router/src/core/unified_connector_service.rs +++ b/crates/router/src/core/unified_connector_service.rs @@ -241,161 +241,79 @@ pub fn build_unified_connector_service_auth_metadata( pub fn handle_unified_connector_service_response_for_payment_authorize( response: PaymentServiceAuthorizeResponse, ) -> CustomResult< - (AttemptStatus, Result), + ( + AttemptStatus, + Result, + u16, + ), UnifiedConnectorServiceError, > { let status = AttemptStatus::foreign_try_from(response.status())?; - // <<<<<<< HEAD - // let connector_response_reference_id = - // response.response_ref_id.as_ref().and_then(|identifier| { - // identifier - // .id_type - // .clone() - // .and_then(|id_type| match id_type { - // payments_grpc::identifier::IdType::Id(id) => Some(id), - // payments_grpc::identifier::IdType::EncodedData(encoded_data) => { - // Some(encoded_data) - // } - // payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, - // }) - // }); - - // let transaction_id = response.transaction_id.as_ref().and_then(|id| { - // id.id_type.clone().and_then(|id_type| match id_type { - // payments_grpc::identifier::IdType::Id(id) => Some(id), - // payments_grpc::identifier::IdType::EncodedData(encoded_data) => Some(encoded_data), - // payments_grpc::identifier::IdType::NoResponseIdMarker(_) => None, - // }) - // }); - - // let (connector_metadata, redirection_data) = match response.redirection_data.clone() { - // Some(redirection_data) => match redirection_data.form_type { - // Some(ref form_type) => match form_type { - // payments_grpc::redirect_form::FormType::Uri(uri) => { - // let image_data = QrImage::new_from_data(uri.uri.clone()) - // .change_context(UnifiedConnectorServiceError::ParsingFailed)?; - // let image_data_url = Url::parse(image_data.data.clone().as_str()) - // .change_context(UnifiedConnectorServiceError::ParsingFailed)?; - // let qr_code_info = QrCodeInformation::QrDataUrl { - // image_data_url, - // display_to_timestamp: None, - // }; - // ( - // Some(qr_code_info.encode_to_value()) - // .transpose() - // .change_context(UnifiedConnectorServiceError::ParsingFailed)?, - // None, - // ) - // } - // _ => ( - // None, - // Some(RedirectForm::foreign_try_from(redirection_data)).transpose()?, - // ), - // }, - // None => (None, None), - // }, - // None => (None, None), - // }; - - // let router_data_response = match status { - // AttemptStatus::Charged | - // AttemptStatus::Authorized | - // AttemptStatus::AuthenticationPending | - // AttemptStatus::DeviceDataCollectionPending | - // AttemptStatus::Started | - // AttemptStatus::AuthenticationSuccessful | - // AttemptStatus::Authorizing | - // AttemptStatus::ConfirmationAwaited | - // AttemptStatus::Pending => Ok(PaymentsResponseData::TransactionResponse { - // resource_id: match transaction_id.as_ref() { - // Some(transaction_id) => hyperswitch_domain_models::router_request_types::ResponseId::ConnectorTransactionId(transaction_id.clone()), - // None => hyperswitch_domain_models::router_request_types::ResponseId::NoResponseId, - // }, - // redirection_data: Box::new( - // redirection_data - // ), - // mandate_reference: Box::new(None), - // connector_metadata, - // network_txn_id: response.network_txn_id.clone(), - // connector_response_reference_id, - // incremental_authorization_allowed: response.incremental_authorization_allowed, - // charges: None, - // }), - // AttemptStatus::AuthenticationFailed - // | AttemptStatus::AuthorizationFailed - // | AttemptStatus::Unresolved - // | AttemptStatus::Failure => Err(ErrorResponse { - // code: response.error_code().to_owned(), - // message: response.error_message().to_owned(), - // reason: Some(response.error_message().to_owned()), - // status_code: 500, - // attempt_status: Some(status), - // connector_transaction_id: connector_response_reference_id, - // network_decline_code: None, - // network_advice_code: None, - // network_error_message: None, - // }), - // AttemptStatus::RouterDeclined | - // AttemptStatus::CodInitiated | - // AttemptStatus::Voided | - // AttemptStatus::VoidInitiated | - // AttemptStatus::CaptureInitiated | - // AttemptStatus::VoidFailed | - // AttemptStatus::AutoRefunded | - // AttemptStatus::PartialCharged | - // AttemptStatus::PartialChargedAndChargeable | - // AttemptStatus::PaymentMethodAwaited | - // AttemptStatus::CaptureFailed | - // AttemptStatus::IntegrityFailure => return Err(UnifiedConnectorServiceError::NotImplemented(format!( - // "AttemptStatus {status:?} is not implemented for Unified Connector Service" - // )).into()), - // }; - // ======= + let status_code = transformers::convert_connector_service_status_code(response.status_code)?; + let router_data_response = Result::::foreign_try_from(response)?; - Ok((status, router_data_response)) + Ok((status, router_data_response, status_code)) } pub fn handle_unified_connector_service_response_for_payment_get( response: payments_grpc::PaymentServiceGetResponse, ) -> CustomResult< - (AttemptStatus, Result), + ( + AttemptStatus, + Result, + u16, + ), UnifiedConnectorServiceError, > { let status = AttemptStatus::foreign_try_from(response.status())?; + let status_code = transformers::convert_connector_service_status_code(response.status_code)?; + let router_data_response = Result::::foreign_try_from(response)?; - Ok((status, router_data_response)) + Ok((status, router_data_response, status_code)) } pub fn handle_unified_connector_service_response_for_payment_register( response: payments_grpc::PaymentServiceRegisterResponse, ) -> CustomResult< - (AttemptStatus, Result), + ( + AttemptStatus, + Result, + u16, + ), UnifiedConnectorServiceError, > { let status = AttemptStatus::foreign_try_from(response.status())?; + let status_code = transformers::convert_connector_service_status_code(response.status_code)?; + let router_data_response = Result::::foreign_try_from(response)?; - Ok((status, router_data_response)) + Ok((status, router_data_response, status_code)) } pub fn handle_unified_connector_service_response_for_payment_repeat( response: payments_grpc::PaymentServiceRepeatEverythingResponse, ) -> CustomResult< - (AttemptStatus, Result), + ( + AttemptStatus, + Result, + u16, + ), UnifiedConnectorServiceError, > { let status = AttemptStatus::foreign_try_from(response.status())?; + let status_code = transformers::convert_connector_service_status_code(response.status_code)?; + let router_data_response = Result::::foreign_try_from(response)?; - Ok((status, router_data_response)) + Ok((status, router_data_response, status_code)) } diff --git a/crates/router/src/core/unified_connector_service/transformers.rs b/crates/router/src/core/unified_connector_service/transformers.rs index 267896ce8ea..9f8ad6c128e 100644 --- a/crates/router/src/core/unified_connector_service/transformers.rs +++ b/crates/router/src/core/unified_connector_service/transformers.rs @@ -397,12 +397,14 @@ impl ForeignTryFrom None => (None, None), }; + let status_code = convert_connector_service_status_code(response.status_code)?; + let response = if response.error_code.is_some() { Err(ErrorResponse { code: response.error_code().to_owned(), message: response.error_message().to_owned(), reason: Some(response.error_message().to_owned()), - status_code: 500, //TODO: To be handled once UCS sends proper status codes + status_code, attempt_status: Some(status), connector_transaction_id: connector_response_reference_id, network_decline_code: None, @@ -455,12 +457,14 @@ impl ForeignTryFrom }) }); + let status_code = convert_connector_service_status_code(response.status_code)?; + let response = if response.error_code.is_some() { Err(ErrorResponse { code: response.error_code().to_owned(), message: response.error_message().to_owned(), reason: Some(response.error_message().to_owned()), - status_code: 500, //TODO: To be handled once UCS sends proper status codes + status_code, attempt_status: Some(status), connector_transaction_id: connector_response_reference_id, network_decline_code: None, @@ -514,12 +518,14 @@ impl ForeignTryFrom }) }); + let status_code = convert_connector_service_status_code(response.status_code)?; + let response = if response.error_code.is_some() { Err(ErrorResponse { code: response.error_code().to_owned(), message: response.error_message().to_owned(), reason: Some(response.error_message().to_owned()), - status_code: 500, //TODO: To be handled once UCS sends proper status codes + status_code, attempt_status: Some(status), connector_transaction_id: connector_response_reference_id, network_decline_code: None, @@ -603,12 +609,14 @@ impl ForeignTryFrom }) }); + let status_code = convert_connector_service_status_code(response.status_code)?; + let response = if response.error_code.is_some() { Err(ErrorResponse { code: response.error_code().to_owned(), message: response.error_message().to_owned(), reason: Some(response.error_message().to_owned()), - status_code: 500, //TODO: To be handled once UCS sends proper status codes + status_code, attempt_status: Some(status), connector_transaction_id: transaction_id, network_decline_code: None, @@ -994,3 +1002,14 @@ impl ForeignTryFrom }) } } + +pub fn convert_connector_service_status_code( + status_code: u32, +) -> Result> { + u16::try_from(status_code).map_err(|err| { + UnifiedConnectorServiceError::RequestEncodingFailedWithReason(format!( + "Failed to convert connector service status code to u16: {err}" + )) + .into() + }) +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 01676a11d49..828986aeeee 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -92,6 +92,12 @@ pub mod headers { pub const X_CLIENT_SECRET: &str = "X-Client-Secret"; pub const X_CUSTOMER_ID: &str = "X-Customer-Id"; pub const X_CONNECTED_MERCHANT_ID: &str = "x-connected-merchant-id"; + // Header value for X_CONNECTOR_HTTP_STATUS_CODE differs by version. + // Constant name is kept the same for consistency across versions. + #[cfg(feature = "v1")] + pub const X_CONNECTOR_HTTP_STATUS_CODE: &str = "connector_http_status_code"; + #[cfg(feature = "v2")] + pub const X_CONNECTOR_HTTP_STATUS_CODE: &str = "x-connector-http-status-code"; } pub mod pii { diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 780093fd012..e738747f6a4 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -898,8 +898,45 @@ where None } }); + let proxy_connector_http_status_code = if state + .conf + .proxy_status_mapping + .proxy_connector_http_status_code + { + headers + .iter() + .find(|(key, _)| key == headers::X_CONNECTOR_HTTP_STATUS_CODE) + .and_then(|(_, value)| { + match value.clone().into_inner().parse::() { + Ok(code) => match http::StatusCode::from_u16(code) { + Ok(status_code) => Some(status_code), + Err(err) => { + logger::error!( + "Invalid HTTP status code parsed from connector_http_status_code: {:?}", + err + ); + None + } + }, + Err(err) => { + logger::error!( + "Failed to parse connector_http_status_code from header: {:?}", + err + ); + None + } + } + }) + } else { + None + }; match serde_json::to_string(&response) { - Ok(res) => http_response_json_with_headers(res, headers, request_elapsed_time), + Ok(res) => http_response_json_with_headers( + res, + headers, + request_elapsed_time, + proxy_connector_http_status_code, + ), Err(_) => http_response_err( r#"{ "error": { @@ -991,8 +1028,9 @@ pub fn http_response_json_with_headers( response: T, headers: Vec<(String, Maskable)>, request_duration: Option, + status_code: Option, ) -> HttpResponse { - let mut response_builder = HttpResponse::Ok(); + let mut response_builder = HttpResponse::build(status_code.unwrap_or(http::StatusCode::OK)); for (header_name, header_value) in headers { let is_sensitive_header = header_value.is_masked(); let mut header_value = header_value.into_inner();