Skip to content

feat(core): populate UCS status_code in response headers #8788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 5, 2025
Merged
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1152,4 +1152,7 @@ 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
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
3 changes: 3 additions & 0 deletions config/deployments/env_specific.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1245,4 +1245,7 @@ version = "HOSTNAME"

[chat]
enabled = false
hyperswitch_ai_host = "http://0.0.0.0:8000"
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
4 changes: 3 additions & 1 deletion config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1142,4 +1142,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
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
2 changes: 1 addition & 1 deletion crates/external_services/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "434690566b625262acbd10db56a23435c5bb1735", package = "rust-grpc-client" }


# First party crates
Expand Down
2 changes: 1 addition & 1 deletion crates/router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "a9f7cd96693fa034ea69d8e21125ea0f76182fae", package = "rust-grpc-client" }
unified-connector-service-client = { git = "https://github.com/juspay/connector-service", rev = "434690566b625262acbd10db56a23435c5bb1735", package = "rust-grpc-client" }
rustc-hash = "1.1.0"
rustls = "0.22"
rustls-pemfile = "2"
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/compatibility/wrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions crates/router/src/configs/secrets_transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
7 changes: 7 additions & 0 deletions crates/router/src/configs/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub struct Settings<S: SecretState> {
pub infra_values: Option<HashMap<String, String>>,
#[serde(default)]
pub enhancement: Option<HashMap<String, String>>,
pub proxy_status_mapping: ProxyStatusMapping,
}

#[derive(Debug, Deserialize, Clone, Default)]
Expand Down Expand Up @@ -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 {
Expand Down
20 changes: 18 additions & 2 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -395,7 +402,7 @@ where
payment_data,
req,
customer,
None,
connector_http_status_code,
None,
connector_response_data,
))
Expand Down Expand Up @@ -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(
Expand All @@ -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")]
Expand Down
6 changes: 4 additions & 2 deletions crates/router/src/core/payments/flows/authorize_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Expand All @@ -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(())
}
Expand Down Expand Up @@ -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(),
)
Expand All @@ -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(())
}
3 changes: 2 additions & 1 deletion crates/router/src/core/payments/flows/psync_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,15 @@ impl Feature<api::PSync, types::PaymentsSyncData>

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")?;

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(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> 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(),
)
Expand All @@ -255,6 +255,7 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> 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
Expand Down
40 changes: 32 additions & 8 deletions crates/router/src/core/payments/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -1585,9 +1585,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,
)))
}
}
Expand Down Expand Up @@ -1968,6 +1976,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,
Expand Down Expand Up @@ -1999,8 +2016,7 @@ where
};

Ok(services::ApplicationResponse::JsonWithHeaders((
response,
vec![],
response, headers,
)))
}
}
Expand Down Expand Up @@ -2065,6 +2081,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,
Expand Down Expand Up @@ -2100,8 +2125,7 @@ where
};

Ok(services::ApplicationResponse::JsonWithHeaders((
response,
vec![],
response, headers,
)))
}
}
Expand Down Expand Up @@ -2539,7 +2563,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()),
)]
})
Expand Down
Loading
Loading