diff --git a/crates/router/src/compatibility/wrap.rs b/crates/router/src/compatibility/wrap.rs index 9e0fbb5399..890c9435ed 100644 --- a/crates/router/src/compatibility/wrap.rs +++ b/crates/router/src/compatibility/wrap.rs @@ -71,6 +71,28 @@ where ), } } + Ok(api::ApplicationResponse::JsonWithHeaders((response, headers))) => { + 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), + Err(_) => api::http_response_err( + r#"{ + "error": { + "message": "Error serializing response from connector" + } + }"#, + ), + }, + Err(_) => api::http_response_err( + r#"{ + "error": { + "message": "Error converting juspay response to stripe response" + } + }"#, + ), + } + } Ok(api::ApplicationResponse::StatusOk) => api::http_response_ok(), Ok(api::ApplicationResponse::TextPlain(text)) => api::http_response_plaintext(text), Ok(api::ApplicationResponse::FileData((file_data, content_type))) => { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 52395f768c..845cdbce5e 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -39,7 +39,7 @@ use crate::{ self, api, domain, storage::{self, enums as storage_enums, ProcessTrackerExt}, }, - utils::{Encode, OptionExt, ValueExt}, + utils::{add_connector_http_status_code_metrics, Encode, OptionExt, ValueExt}, }; #[instrument(skip_all, fields(payment_id, merchant_id))] @@ -51,7 +51,7 @@ pub async fn payments_operation_core( req: Req, call_connector_action: CallConnectorAction, auth_flow: services::AuthFlow, -) -> RouterResult<(PaymentData, Req, Option)> +) -> RouterResult<(PaymentData, Req, Option, Option)> where F: Send + Clone + Sync, Req: Authenticate, @@ -151,6 +151,8 @@ where ) .await?; + let mut connector_http_status_code = None; + if let Some(connector_details) = connector { payment_data = match connector_details { api::ConnectorCallType::Single(connector) => { @@ -172,6 +174,9 @@ where let operation = Box::new(PaymentResponse); let db = &*state.store; + connector_http_status_code = router_data.connector_http_status_code; + //add connector http status code metrics + add_connector_http_status_code_metrics(connector_http_status_code); operation .to_post_update_tracker()? .update_tracker( @@ -226,7 +231,7 @@ where .await?; } - Ok((payment_data, req, customer)) + Ok((payment_data, req, customer, connector_http_status_code)) } #[allow(clippy::too_many_arguments)] @@ -256,7 +261,7 @@ where // To perform router related operation for PaymentResponse PaymentResponse: Operation, { - let (payment_data, req, customer) = payments_operation_core( + let (payment_data, req, customer, connector_http_status_code) = payments_operation_core( state, merchant_account, key_store, @@ -275,6 +280,7 @@ where &state.conf.server, operation, &state.conf.connector_request_reference_id_config, + connector_http_status_code, ) } @@ -373,6 +379,7 @@ pub trait PaymentRedirectFlow: Sync { let payments_response = match response? { services::ApplicationResponse::Json(response) => Ok(response), + services::ApplicationResponse::JsonWithHeaders((response, _)) => Ok(response), _ => Err(errors::ApiErrorResponse::InternalServerError) .into_report() .attach_printable("Failed to get the response in json"), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index c70a53a874..3c13d6bdad 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2462,6 +2462,7 @@ pub fn router_data_type_conversion( #[cfg(feature = "payouts")] quote_id: None, test_mode: router_data.test_mode, + connector_http_status_code: router_data.connector_http_status_code, } } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 870de1bfad..db36eb11a1 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use common_utils::fp_utils; use error_stack::ResultExt; use router_derive; @@ -51,9 +50,6 @@ impl PostUpdateTracker, types::PaymentsAuthorizeData .mandate_id .or_else(|| router_data.request.mandate_id.clone()); - let router_response = router_data.response.clone(); - let connector = router_data.connector.clone(); - payment_data = payment_response_update_tracker( db, payment_id, @@ -63,22 +59,6 @@ impl PostUpdateTracker, types::PaymentsAuthorizeData ) .await?; - router_response.map(|_| ()).or_else(|error_response| { - fp_utils::when( - !(200..300).contains(&error_response.status_code) - && !(500..=511).contains(&error_response.status_code), - || { - Err(errors::ApiErrorResponse::ExternalConnectorError { - code: error_response.code, - message: error_response.message, - connector, - status_code: error_response.status_code, - reason: error_response.reason, - }) - }, - ) - })?; - Ok(payment_data) } } @@ -116,9 +96,6 @@ impl PostUpdateTracker, types::PaymentsSessionData> where F: 'b + Send, { - let router_response = router_data.response.clone(); - let connector = router_data.connector.clone(); - payment_data = payment_response_update_tracker( db, payment_id, @@ -128,16 +105,6 @@ impl PostUpdateTracker, types::PaymentsSessionData> ) .await?; - router_response.map_err(|error_response| { - errors::ApiErrorResponse::ExternalConnectorError { - message: error_response.message, - code: error_response.code, - status_code: error_response.status_code, - reason: error_response.reason, - connector, - } - })?; - Ok(payment_data) } } @@ -157,9 +124,6 @@ impl PostUpdateTracker, types::PaymentsCaptureData> where F: 'b + Send, { - let router_response = router_data.response.clone(); - let connector = router_data.connector.clone(); - payment_data = payment_response_update_tracker( db, payment_id, @@ -169,16 +133,6 @@ impl PostUpdateTracker, types::PaymentsCaptureData> ) .await?; - router_response.map_err(|error_response| { - errors::ApiErrorResponse::ExternalConnectorError { - message: error_response.message, - code: error_response.code, - status_code: error_response.status_code, - reason: error_response.reason, - connector, - } - })?; - Ok(payment_data) } } @@ -197,9 +151,6 @@ impl PostUpdateTracker, types::PaymentsCancelData> f where F: 'b + Send, { - let router_response = router_data.response.clone(); - let connector = router_data.connector.clone(); - payment_data = payment_response_update_tracker( db, payment_id, @@ -209,16 +160,6 @@ impl PostUpdateTracker, types::PaymentsCancelData> f ) .await?; - router_response.map_err(|error_response| { - errors::ApiErrorResponse::ExternalConnectorError { - message: error_response.message, - code: error_response.code, - status_code: error_response.status_code, - reason: error_response.reason, - connector, - } - })?; - Ok(payment_data) } } @@ -242,9 +183,6 @@ impl PostUpdateTracker, types::VerifyRequestData> fo // .map(api_models::payments::MandateIds::new) }); - let router_response = router_data.response.clone(); - let connector = router_data.connector.clone(); - payment_data = payment_response_update_tracker( db, payment_id, @@ -254,16 +192,6 @@ impl PostUpdateTracker, types::VerifyRequestData> fo ) .await?; - router_response.map_err(|error_response| { - errors::ApiErrorResponse::ExternalConnectorError { - message: error_response.message, - code: error_response.code, - status_code: error_response.status_code, - reason: error_response.reason, - connector, - } - })?; - Ok(payment_data) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 51848748cd..56ff409aaf 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -138,6 +138,7 @@ where quote_id: None, test_mode, payment_method_balance: None, + connector_http_status_code: None, }; Ok(router_data) @@ -148,6 +149,7 @@ where Self: Sized, Op: Debug, { + #[allow(clippy::too_many_arguments)] fn generate_response( req: Option, data: D, @@ -156,6 +158,7 @@ where server: &Server, operation: Op, connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + connector_http_status_code: Option, ) -> RouterResponse; } @@ -164,6 +167,7 @@ where F: Clone, Op: Debug, { + #[allow(clippy::too_many_arguments)] fn generate_response( req: Option, payment_data: PaymentData, @@ -172,6 +176,7 @@ where server: &Server, operation: Op, connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + connector_http_status_code: Option, ) -> RouterResponse { payments_to_payments_response( req, @@ -192,6 +197,7 @@ where payment_data.frm_message, payment_data.setup_mandate, connector_request_reference_id_config, + connector_http_status_code, ) } } @@ -202,6 +208,7 @@ where F: Clone, Op: Debug, { + #[allow(clippy::too_many_arguments)] fn generate_response( _req: Option, payment_data: PaymentData, @@ -210,16 +217,20 @@ where _server: &Server, _operation: Op, _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, ) -> RouterResponse { - Ok(services::ApplicationResponse::Json(Self { - session_token: payment_data.sessions_token, - payment_id: payment_data.payment_attempt.payment_id, - client_secret: payment_data - .payment_intent - .client_secret - .get_required_value("client_secret")? - .into(), - })) + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + session_token: payment_data.sessions_token, + payment_id: payment_data.payment_attempt.payment_id, + client_secret: payment_data + .payment_intent + .client_secret + .get_required_value("client_secret")? + .into(), + }, + vec![], + ))) } } @@ -229,6 +240,7 @@ where F: Clone, Op: Debug, { + #[allow(clippy::too_many_arguments)] fn generate_response( _req: Option, data: PaymentData, @@ -237,6 +249,7 @@ where _server: &Server, _operation: Op, _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, ) -> RouterResponse { let additional_payment_method_data: Option = data.payment_attempt @@ -249,27 +262,30 @@ where })?; let payment_method_data_response = additional_payment_method_data.map(api::PaymentMethodDataResponse::from); - Ok(services::ApplicationResponse::Json(Self { - verify_id: Some(data.payment_intent.payment_id), - merchant_id: Some(data.payment_intent.merchant_id), - client_secret: data.payment_intent.client_secret.map(masking::Secret::new), - customer_id: customer.as_ref().map(|x| x.customer_id.clone()), - email: customer - .as_ref() - .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), - name: customer - .as_ref() - .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), - phone: customer - .as_ref() - .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), - mandate_id: data.mandate_id.map(|mandate_ids| mandate_ids.mandate_id), - payment_method: data.payment_attempt.payment_method, - payment_method_data: payment_method_data_response, - payment_token: data.token, - error_code: data.payment_attempt.error_code, - error_message: data.payment_attempt.error_message, - })) + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + verify_id: Some(data.payment_intent.payment_id), + merchant_id: Some(data.payment_intent.merchant_id), + client_secret: data.payment_intent.client_secret.map(masking::Secret::new), + customer_id: customer.as_ref().map(|x| x.customer_id.clone()), + email: customer + .as_ref() + .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), + name: customer + .as_ref() + .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), + phone: customer + .as_ref() + .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), + mandate_id: data.mandate_id.map(|mandate_ids| mandate_ids.mandate_id), + payment_method: data.payment_attempt.payment_method, + payment_method_data: payment_method_data_response, + payment_token: data.token, + error_code: data.payment_attempt.error_code, + error_message: data.payment_attempt.error_message, + }, + vec![], + ))) } } @@ -296,6 +312,7 @@ pub fn payments_to_payments_response( frm_message: Option, mandate_data: Option, connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + connector_http_status_code: Option, ) -> RouterResponse where Op: Debug, @@ -356,6 +373,15 @@ where let payment_method_data_response = additional_payment_method_data.map(api::PaymentMethodDataResponse::from); + let headers = connector_http_status_code + .map(|status_code| { + vec![( + "connector_http_status_code".to_string(), + status_code.to_string(), + )] + }) + .unwrap_or(vec![]); + let output = Ok(match payment_request { Some(_request) => { if payments::is_start_pay(&operation) && redirection_data.is_some() { @@ -445,7 +471,7 @@ where let amount_captured = payment_intent.amount_captured.unwrap_or_default(); let amount_capturable = Some(payment_attempt.amount - amount_captured); - services::ApplicationResponse::Json( + services::ApplicationResponse::JsonWithHeaders(( response .set_payment_id(Some(payment_attempt.payment_id)) .set_merchant_id(Some(payment_attempt.merchant_id)) @@ -531,61 +557,65 @@ where .set_connector_metadata(payment_intent.connector_metadata) .set_reference_id(payment_attempt.connector_response_reference_id) .to_owned(), - ) + headers, + )) } } - None => services::ApplicationResponse::Json(api::PaymentsResponse { - payment_id: Some(payment_attempt.payment_id), - merchant_id: Some(payment_attempt.merchant_id), - status: payment_intent.status, - amount: payment_attempt.amount, - amount_capturable: None, - amount_received: payment_intent.amount_captured, - client_secret: payment_intent.client_secret.map(masking::Secret::new), - created: Some(payment_intent.created_at), - currency: currency.to_string(), - customer_id: payment_intent.customer_id, - description: payment_intent.description, - refunds: refunds_response, - disputes: disputes_response, - attempts: attempts_response, - payment_method: payment_attempt.payment_method, - capture_method: payment_attempt.capture_method, - error_message: payment_attempt - .error_reason - .or(payment_attempt.error_message), - error_code: payment_attempt.error_code, - payment_method_data: payment_method_data_response, - email: customer - .as_ref() - .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), - name: customer - .as_ref() - .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), - phone: customer - .as_ref() - .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), - mandate_id, - shipping: address.shipping, - billing: address.billing, - cancellation_reason: payment_attempt.cancellation_reason, - payment_token: payment_attempt.payment_token, - metadata: payment_intent.metadata, - manual_retry_allowed: helpers::is_manual_retry_allowed( - &payment_intent.status, - &payment_attempt.status, - connector_request_reference_id_config, - &merchant_id, - ), - order_details: payment_intent.order_details, - frm_message, - connector_transaction_id: payment_attempt.connector_transaction_id, - feature_metadata: payment_intent.feature_metadata, - connector_metadata: payment_intent.connector_metadata, - allowed_payment_method_types: payment_intent.allowed_payment_method_types, - reference_id: payment_attempt.connector_response_reference_id, - ..Default::default() - }), + None => services::ApplicationResponse::JsonWithHeaders(( + api::PaymentsResponse { + payment_id: Some(payment_attempt.payment_id), + merchant_id: Some(payment_attempt.merchant_id), + status: payment_intent.status, + amount: payment_attempt.amount, + amount_capturable: None, + amount_received: payment_intent.amount_captured, + client_secret: payment_intent.client_secret.map(masking::Secret::new), + created: Some(payment_intent.created_at), + currency: currency.to_string(), + customer_id: payment_intent.customer_id, + description: payment_intent.description, + refunds: refunds_response, + disputes: disputes_response, + attempts: attempts_response, + payment_method: payment_attempt.payment_method, + capture_method: payment_attempt.capture_method, + error_message: payment_attempt + .error_reason + .or(payment_attempt.error_message), + error_code: payment_attempt.error_code, + payment_method_data: payment_method_data_response, + email: customer + .as_ref() + .and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())), + name: customer + .as_ref() + .and_then(|cus| cus.name.as_ref().map(|s| s.to_owned())), + phone: customer + .as_ref() + .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), + mandate_id, + shipping: address.shipping, + billing: address.billing, + cancellation_reason: payment_attempt.cancellation_reason, + payment_token: payment_attempt.payment_token, + metadata: payment_intent.metadata, + manual_retry_allowed: helpers::is_manual_retry_allowed( + &payment_intent.status, + &payment_attempt.status, + connector_request_reference_id_config, + &merchant_id, + ), + order_details: payment_intent.order_details, + frm_message, + connector_transaction_id: payment_attempt.connector_transaction_id, + feature_metadata: payment_intent.feature_metadata, + connector_metadata: payment_intent.connector_metadata, + allowed_payment_method_types: payment_intent.allowed_payment_method_types, + reference_id: payment_attempt.connector_response_reference_id, + ..Default::default() + }, + headers, + )), }); metrics::PAYMENT_OPS_COUNT.add( diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index e0eab58ef9..bb301ba086 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -182,6 +182,7 @@ pub async fn construct_payout_router_data<'a, F>( quote_id: None, test_mode, payment_method_balance: None, + connector_http_status_code: None, }; Ok(router_data) @@ -289,6 +290,7 @@ pub async fn construct_refund_router_data<'a, F>( quote_id: None, test_mode, payment_method_balance: None, + connector_http_status_code: None, }; Ok(router_data) @@ -506,6 +508,7 @@ pub async fn construct_accept_dispute_router_data<'a>( quote_id: None, test_mode, payment_method_balance: None, + connector_http_status_code: None, }; Ok(router_data) } @@ -580,6 +583,7 @@ pub async fn construct_submit_evidence_router_data<'a>( #[cfg(feature = "payouts")] quote_id: None, test_mode, + connector_http_status_code: None, }; Ok(router_data) } @@ -655,6 +659,7 @@ pub async fn construct_upload_file_router_data<'a>( #[cfg(feature = "payouts")] quote_id: None, test_mode, + connector_http_status_code: None, }; Ok(router_data) } @@ -732,6 +737,7 @@ pub async fn construct_defend_dispute_router_data<'a>( #[cfg(feature = "payouts")] quote_id: None, test_mode, + connector_http_status_code: None, }; Ok(router_data) } @@ -804,6 +810,7 @@ pub async fn construct_retrieve_file_router_data<'a>( #[cfg(feature = "payouts")] quote_id: None, test_mode, + connector_http_status_code: None, }; Ok(router_data) } diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index b205da1eca..be300861d7 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -93,7 +93,7 @@ pub async fn payments_incoming_webhook_flow( }; match payments_response { - services::ApplicationResponse::Json(payments_response) => { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { let payment_id = payments_response .payment_id .clone() @@ -449,7 +449,7 @@ async fn bank_transfer_webhook_flow( }; match response? { - services::ApplicationResponse::Json(payments_response) => { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { let payment_id = payments_response .payment_id .clone() diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 8c2eb6c4d6..ec85a926b7 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -70,6 +70,12 @@ counter_metric!(REDIRECTION_TRIGGERED, GLOBAL_METER); // Connector Level Metric counter_metric!(REQUEST_BUILD_FAILURE, GLOBAL_METER); counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER); +// Connector http status code metrics +counter_metric!(CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT, GLOBAL_METER); +counter_metric!(CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT, GLOBAL_METER); +counter_metric!(CONNECTOR_HTTP_STATUS_CODE_3XX_COUNT, GLOBAL_METER); +counter_metric!(CONNECTOR_HTTP_STATUS_CODE_4XX_COUNT, GLOBAL_METER); +counter_metric!(CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT, GLOBAL_METER); // Service Level counter_metric!(CARD_LOCKER_FAILURES, GLOBAL_METER); diff --git a/crates/router/src/routes/metrics/request.rs b/crates/router/src/routes/metrics/request.rs index 6ac3d692b4..6d810b3ffe 100644 --- a/crates/router/src/routes/metrics/request.rs +++ b/crates/router/src/routes/metrics/request.rs @@ -57,7 +57,8 @@ pub fn track_response_status_code(response: &ApplicationResponse) -> i64 { | ApplicationResponse::StatusOk | ApplicationResponse::TextPlain(_) | ApplicationResponse::Form(_) - | ApplicationResponse::FileData(_) => 200, + | ApplicationResponse::FileData(_) + | ApplicationResponse::JsonWithHeaders(_) => 200, ApplicationResponse::JsonForRedirection(_) => 302, } } diff --git a/crates/router/src/scheduler/workflows/payment_sync.rs b/crates/router/src/scheduler/workflows/payment_sync.rs index 4dc2b29908..4aa5c9fd34 100644 --- a/crates/router/src/scheduler/workflows/payment_sync.rs +++ b/crates/router/src/scheduler/workflows/payment_sync.rs @@ -50,16 +50,17 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { ) .await?; - let (payment_data, _, _) = payment_flows::payments_operation_core::( - state, - merchant_account.clone(), - key_store, - operations::PaymentStatus, - tracking_data.clone(), - payment_flows::CallConnectorAction::Trigger, - services::AuthFlow::Client, - ) - .await?; + let (payment_data, _, _, _) = + payment_flows::payments_operation_core::( + state, + merchant_account.clone(), + key_store, + operations::PaymentStatus, + tracking_data.clone(), + payment_flows::CallConnectorAction::Trigger, + services::AuthFlow::Client, + ) + .await?; let terminal_status = vec![ enums::AttemptStatus::RouterDeclined, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index c919fc943e..daf6fcfce7 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -285,24 +285,30 @@ where match response { Ok(body) => { let response = match body { - Ok(body) => connector_integration - .handle_response(req, body) - .map_err(|error| { - if error.current_context() - == &errors::ConnectorError::ResponseDeserializationFailed - { - metrics::RESPONSE_DESERIALIZATION_FAILURE.add( - &metrics::CONTEXT, - 1, - &[metrics::request::add_attributes( - "connector", - req.connector.to_string(), - )], - ) - } - error - })?, + Ok(body) => { + let connector_http_status_code = Some(body.status_code); + let mut data = connector_integration + .handle_response(req, body) + .map_err(|error| { + if error.current_context() + == &errors::ConnectorError::ResponseDeserializationFailed + { + metrics::RESPONSE_DESERIALIZATION_FAILURE.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "connector", + req.connector.to_string(), + )], + ) + } + error + })?; + data.connector_http_status_code = connector_http_status_code; + data + } Err(body) => { + router_data.connector_http_status_code = Some(body.status_code); metrics::CONNECTOR_ERROR_RESPONSE_COUNT.add( &metrics::CONTEXT, 1, @@ -528,6 +534,7 @@ pub enum ApplicationResponse { JsonForRedirection(api::RedirectionResponse), Form(Box), FileData((Vec, mime::Mime)), + JsonWithHeaders((R, Vec<(String, String)>)), } #[derive(Debug, Eq, PartialEq)] @@ -704,6 +711,18 @@ where ) .respond_to(request) .map_into_boxed_body(), + Ok(ApplicationResponse::JsonWithHeaders((response, headers))) => { + match serde_json::to_string(&response) { + Ok(res) => http_response_json_with_headers(res, headers), + Err(_) => http_response_err( + r#"{ + "error": { + "message": "Error serializing response from connector" + } + }"#, + ), + } + } Err(error) => log_and_return_error_response(error), }; @@ -769,6 +788,19 @@ pub fn http_response_json(response: T) -> HttpRe .body(response) } +pub fn http_response_json_with_headers( + response: T, + headers: Vec<(String, String)>, +) -> HttpResponse { + let mut response_builder = HttpResponse::Ok(); + for (name, value) in headers { + response_builder.append_header((name, value)); + } + response_builder + .content_type(mime::APPLICATION_JSON) + .body(response) +} + pub fn http_response_plaintext(res: T) -> HttpResponse { HttpResponse::Ok().content_type(mime::TEXT_PLAIN).body(res) } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index c81516ab65..c4ced807e2 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -259,6 +259,7 @@ pub struct RouterData { pub quote_id: Option, pub test_mode: Option, + pub connector_http_status_code: Option, } #[derive(Debug, Clone)] @@ -919,6 +920,7 @@ impl From<(&RouterData, T2)> quote_id: data.quote_id.clone(), test_mode: data.test_mode, payment_method_balance: data.payment_method_balance.clone(), + connector_http_status_code: data.connector_http_status_code, } } } @@ -990,6 +992,7 @@ impl quote_id: data.quote_id.clone(), test_mode: data.test_mode, payment_method_balance: None, + connector_http_status_code: data.connector_http_status_code, } } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 79ff18296d..5db9d782f7 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -365,3 +365,45 @@ pub fn handle_json_response_deserialization_failure( } } } + +pub fn get_http_status_code_type( + status_code: u16, +) -> CustomResult { + let status_code_type = match status_code { + 100..=199 => "1xx", + 200..=299 => "2xx", + 300..=399 => "3xx", + 400..=499 => "4xx", + 500..=599 => "5xx", + _ => Err(errors::ApiErrorResponse::InternalServerError) + .into_report() + .attach_printable("Invalid http status code")?, + }; + Ok(status_code_type.to_string()) +} + +pub fn add_connector_http_status_code_metrics(option_status_code: Option) { + if let Some(status_code) = option_status_code { + let status_code_type = get_http_status_code_type(status_code).ok(); + match status_code_type.as_deref() { + Some("1xx") => { + metrics::CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT.add(&metrics::CONTEXT, 1, &[]) + } + Some("2xx") => { + metrics::CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT.add(&metrics::CONTEXT, 1, &[]) + } + Some("3xx") => { + metrics::CONNECTOR_HTTP_STATUS_CODE_3XX_COUNT.add(&metrics::CONTEXT, 1, &[]) + } + Some("4xx") => { + metrics::CONNECTOR_HTTP_STATUS_CODE_4XX_COUNT.add(&metrics::CONTEXT, 1, &[]) + } + Some("5xx") => { + metrics::CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT.add(&metrics::CONTEXT, 1, &[]) + } + _ => logger::info!("Skip metrics as invalid http status code received from connector"), + }; + } else { + logger::info!("Skip metrics as no http status code received from connector") + } +} diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index c562fab2ee..0287f6258f 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -88,6 +88,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { quote_id: None, test_mode: None, payment_method_balance: None, + connector_http_status_code: None, } } @@ -140,6 +141,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { quote_id: None, test_mode: None, payment_method_balance: None, + connector_http_status_code: None, } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index d52b5995d1..84174f1dbf 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -504,6 +504,7 @@ pub trait ConnectorActions: Connector { quote_id: None, test_mode: None, payment_method_balance: None, + connector_http_status_code: None, } } diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index d5dd2f6028..b08a20e3ce 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -352,7 +352,8 @@ async fn payments_create_core() { mandate_id: None, ..Default::default() }; - let expected_response = services::ApplicationResponse::Json(expected_response); + let expected_response = + services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); let actual_response = payments::payments_core::( &state, @@ -498,21 +499,24 @@ async fn payments_create_core_adyen_no_redirect() { ..Default::default() }; - let expected_response = services::ApplicationResponse::Json(api::PaymentsResponse { - payment_id: Some(payment_id.clone()), - status: api_enums::IntentStatus::Processing, - amount: 6540, - amount_capturable: None, - amount_received: None, - client_secret: None, - created: None, - currency: "USD".to_string(), - customer_id: None, - description: Some("Its my first payment request".to_string()), - refunds: None, - mandate_id: None, - ..Default::default() - }); + let expected_response = services::ApplicationResponse::JsonWithHeaders(( + api::PaymentsResponse { + payment_id: Some(payment_id.clone()), + status: api_enums::IntentStatus::Processing, + amount: 6540, + amount_capturable: None, + amount_received: None, + client_secret: None, + created: None, + currency: "USD".to_string(), + customer_id: None, + description: Some("Its my first payment request".to_string()), + refunds: None, + mandate_id: None, + ..Default::default() + }, + vec![], + )); let actual_response = payments::payments_core::( &state, diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 42c27f4921..93a7badce9 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -112,7 +112,8 @@ async fn payments_create_core() { mandate_id: None, ..Default::default() }; - let expected_response = services::ApplicationResponse::Json(expected_response); + let expected_response = + services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); let actual_response = router::core::payments::payments_core::( &state, @@ -260,21 +261,24 @@ async fn payments_create_core_adyen_no_redirect() { ..Default::default() }; - let expected_response = services::ApplicationResponse::Json(api::PaymentsResponse { - payment_id: Some(payment_id.clone()), - status: api_enums::IntentStatus::Processing, - amount: 6540, - amount_capturable: None, - amount_received: None, - client_secret: None, - created: None, - currency: "USD".to_string(), - customer_id: None, - description: Some("Its my first payment request".to_string()), - refunds: None, - mandate_id: None, - ..Default::default() - }); + let expected_response = services::ApplicationResponse::JsonWithHeaders(( + api::PaymentsResponse { + payment_id: Some(payment_id.clone()), + status: api_enums::IntentStatus::Processing, + amount: 6540, + amount_capturable: None, + amount_received: None, + client_secret: None, + created: None, + currency: "USD".to_string(), + customer_id: None, + description: Some("Its my first payment request".to_string()), + refunds: None, + mandate_id: None, + ..Default::default() + }, + vec![], + )); let actual_response = router::core::payments::payments_core::( &state, diff --git a/crates/test_utils/tests/connectors/stripe_ui.rs b/crates/test_utils/tests/connectors/stripe_ui.rs index e644cbe7da..e69d134ce2 100644 --- a/crates/test_utils/tests/connectors/stripe_ui.rs +++ b/crates/test_utils/tests/connectors/stripe_ui.rs @@ -54,7 +54,7 @@ async fn should_fail_recurring_payment_due_to_authentication( Event::Assert(Assert::IsPresent("man_")),// mandate id starting with man_ Event::Trigger(Trigger::Click(By::Css("#pm-mandate-btn a"))), Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), - Event::Assert(Assert::IsPresent("authentication_required: authentication_required")), + Event::Assert(Assert::IsPresent("Your card was declined. This transaction requires authentication.")), ]).await?; Ok(()) }