From 94e4d9330ca6be2601d5609deb1380a6229a231f Mon Sep 17 00:00:00 2001 From: hrithikesh026 Date: Tue, 14 Oct 2025 00:28:51 +0530 Subject: [PATCH] chore: add logic to use pre decide flow --- crates/common_enums/src/enums.rs | 6 + .../src/connectors/cybersource.rs | 54 ++++++ .../src/default_implementations_v2.rs | 2 +- crates/hyperswitch_interfaces/src/api.rs | 7 + .../src/connector_integration_interface.rs | 32 ++++ crates/router/src/core/payments.rs | 156 +++++++++++++----- crates/router/src/core/payments/flows.rs | 12 +- .../src/core/payments/flows/authorize_flow.rs | 16 +- 8 files changed, 232 insertions(+), 53 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 6739cde8f8..7368ce505e 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -348,6 +348,12 @@ pub enum AuthenticationType { NoThreeDs, } +impl AuthenticationType { + pub fn is_three_ds(&self) -> bool { + matches!(self, Self::ThreeDs) + } +} + /// The status of the capture #[derive( Clone, diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource.rs b/crates/hyperswitch_connectors/src/connectors/cybersource.rs index 03f3de84d6..5ad072ce07 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource.rs @@ -2283,4 +2283,58 @@ impl ConnectorSpecifications for Cybersource { fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { Some(&CYBERSOURCE_SUPPORTED_WEBHOOK_FLOWS) } + fn should_call_connector_service_with_pre_decide_flow( + &self, + current_flow_info: api::CurrentFlowInfo<'_>, + ) -> bool { + match current_flow_info { + api::CurrentFlowInfo::Authorize { + request_data, + auth_type, + } => self.is_3ds_setup_required(request_data, *auth_type), + } + } + fn get_preprocessing_flow_if_needed( + &self, + current_flow_info: api::CurrentFlowInfo<'_>, + ) -> Option { + match current_flow_info { + api::CurrentFlowInfo::Authorize { .. } => { + // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow + None + } + } + } + fn get_alternate_flow_if_needed( + &self, + current_flow: api::CurrentFlowInfo<'_>, + ) -> Option { + match current_flow { + api::CurrentFlowInfo::Authorize { + request_data, + auth_type, + } => { + if self.is_3ds_setup_required(request_data, *auth_type) { + Some(api::AlternateFlow::PreAuthenticate) + } else { + None + } + } + } + } +} + +impl Cybersource { + pub fn is_3ds_setup_required( + &self, + request: &PaymentsAuthorizeData, + auth_type: common_enums::AuthenticationType, + ) -> bool { + router_env::logger::info!(router_data_request=?request, auth_type=?auth_type, "Checking if 3DS setup is required for Cybersource"); + auth_type.is_three_ds() + && request.is_card() + && (request.connector_mandate_id().is_none() + && request.get_optional_network_transaction_id().is_none()) + && request.authentication_data.is_none() + } } diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 90a29475b9..c8b6e8bb64 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -249,7 +249,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { PaymentsTaxCalculationData, TaxCalculationResponseData, > for $path::$connector{} - impl ConnectorIntegrationV2< + impl ConnectorIntegrationV2< SdkSessionUpdate, PaymentFlowData, SdkPaymentsSessionUpdateData, diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index d15a4f2b72..6f8aab02e1 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -415,6 +415,13 @@ pub enum PreProcessingFlowName { /// The trait that provides specifications about the connector pub trait ConnectorSpecifications { + /// indicates whether the new pre-decide flow should be called + fn should_call_connector_service_with_pre_decide_flow( + &self, + _current_flow: CurrentFlowInfo<'_>, + ) -> bool { + false + } /// Preprocessing flow name if any, that must be made before the current flow. fn get_preprocessing_flow_if_needed( &self, diff --git a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs index ddb066489f..dffbfb72cf 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs @@ -625,6 +625,38 @@ impl ConnectorValidation for ConnectorEnum { } impl ConnectorSpecifications for ConnectorEnum { + fn should_call_connector_service_with_pre_decide_flow( + &self, + current_flow: api::CurrentFlowInfo<'_>, + ) -> bool { + match self { + Self::Old(connector) => { + connector.should_call_connector_service_with_pre_decide_flow(current_flow) + } + Self::New(connector) => { + connector.should_call_connector_service_with_pre_decide_flow(current_flow) + } + } + } + fn get_preprocessing_flow_if_needed( + &self, + current_flow_info: api::CurrentFlowInfo<'_>, + ) -> Option { + match self { + Self::Old(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), + Self::New(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), + } + } + fn get_alternate_flow_if_needed( + &self, + current_flow: api::CurrentFlowInfo<'_>, + ) -> Option { + match self { + Self::Old(connector) => connector.get_alternate_flow_if_needed(current_flow), + Self::New(connector) => connector.get_alternate_flow_if_needed(current_flow), + } + } + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { match self { Self::Old(connector) => connector.get_supported_payment_methods(), diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 5cf0eb9928..f41262aabe 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -66,6 +66,7 @@ use hyperswitch_domain_models::{ payments::{self, payment_intent::CustomerData, ClickToPayMetaData}, router_data::AccessToken, }; +use hyperswitch_interfaces::api::ConnectorSpecifications; use masking::{ExposeInterface, PeekInterface, Secret}; #[cfg(feature = "v2")] use operations::ValidateStatusForOperation; @@ -4080,7 +4081,7 @@ where #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] -pub async fn _call_connector_service( +pub async fn call_connector_service_with_pre_decide_flow( state: &SessionState, req_state: ReqState, merchant_context: &domain::MerchantContext, @@ -4097,7 +4098,7 @@ pub async fn _call_connector_service( is_retry_payment: bool, return_raw_connector_response: Option, merchant_connector_account: helpers::MerchantConnectorAccountType, - mut router_data: RouterData, + router_data: RouterData, tokenization_action: TokenizationAction, ) -> RouterResult<( RouterData, @@ -4728,27 +4729,61 @@ where // Update feature metadata to track Direct routing usage for stickiness update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?; - call_connector_service( - state, - req_state, - merchant_context, - connector, - operation, - payment_data, - customer, - call_connector_action, - validate_result, - schedule_time, - header_payload, - frm_suggestion, - business_profile, - is_retry_payment, - all_keys_required, - merchant_connector_account, - router_data, - tokenization_action, - ) - .await + let should_call_connector_service_with_pre_decide_flow = router_data + .get_current_flow_info() + .map(|current_flow_info| { + connector + .connector + .should_call_connector_service_with_pre_decide_flow(current_flow_info) + }) + .unwrap_or(false); + + if should_call_connector_service_with_pre_decide_flow { + logger::info!("Calling call_connector_service_with_pre_decide_flow"); + call_connector_service_with_pre_decide_flow( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + validate_result, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + all_keys_required, + merchant_connector_account, + router_data, + tokenization_action, + ) + .await + } else { + call_connector_service( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + validate_result, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + all_keys_required, + merchant_connector_account, + router_data, + tokenization_action, + ) + .await + } } #[cfg(feature = "v1")] @@ -4815,28 +4850,61 @@ where // Update feature metadata to track Direct routing usage for stickiness update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?; + let should_call_connector_service_with_pre_decide_flow = router_data + .get_current_flow_info() + .map(|current_flow_info| { + connector + .connector + .should_call_connector_service_with_pre_decide_flow(current_flow_info) + }) + .unwrap_or(false); // Call Direct connector service - let result = call_connector_service( - state, - req_state, - merchant_context, - connector, - operation, - payment_data, - customer, - call_connector_action, - validate_result, - schedule_time, - header_payload, - frm_suggestion, - business_profile, - is_retry_payment, - all_keys_required, - merchant_connector_account, - router_data, - tokenization_action, - ) - .await?; + let result = if should_call_connector_service_with_pre_decide_flow { + logger::info!("Calling call_connector_service_with_pre_decide_flow"); + call_connector_service_with_pre_decide_flow( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + validate_result, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + all_keys_required, + merchant_connector_account, + router_data, + tokenization_action, + ) + .await? + } else { + call_connector_service( + state, + req_state, + merchant_context, + connector, + operation, + payment_data, + customer, + call_connector_action, + validate_result, + schedule_time, + header_payload, + frm_suggestion, + business_profile, + is_retry_payment, + all_keys_required, + merchant_connector_account, + router_data, + tokenization_action, + ) + .await? + }; // Spawn shadow UCS call in background let direct_router_data = result.0.clone(); diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index cd010e7976..67cf7169a8 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -26,6 +26,7 @@ use hyperswitch_domain_models::router_flow_types::{ use hyperswitch_domain_models::{ payments as domain_payments, router_request_types::PaymentsCaptureData, }; +use hyperswitch_interfaces::api as api_interfaces; use crate::{ core::{ @@ -112,10 +113,10 @@ pub trait Feature { async fn pre_decide_flows<'a>( self, - state: &SessionState, - connector: &api::ConnectorData, - merchant_context: &domain::MerchantContext, - pre_decide_inputs: PreDecideFlowInputs<'a>, + _state: &SessionState, + _connector: &api::ConnectorData, + _merchant_context: &domain::MerchantContext, + _pre_decide_inputs: PreDecideFlowInputs<'a>, ) -> RouterResult<(PreDecideFlowOutput, Self)> where Self: Sized, @@ -246,6 +247,9 @@ pub trait Feature { ) { } + fn get_current_flow_info(&self) -> Option> { + None + } async fn call_unified_connector_service<'a>( &mut self, _state: &SessionState, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 9482b6b15d..b84393498d 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -240,7 +240,7 @@ impl Feature for types::PaymentsAu ); router_data = router_data.preprocessing_steps(state, connector).await?; - let should_continue = matches!( + let should_continue_further = matches!( router_data.response, Ok(types::PaymentsResponseData::TransactionResponse { ref redirection_data, @@ -259,6 +259,8 @@ impl Feature for types::PaymentsAu None }; + // TODO add logic to do connector specific pre tasks if any + // In case of authorize flow, pre-task and post-tasks are being called in build request // if we do not want to proceed further, then the function will return Ok(None, false) let (connector_request, should_continue_further) = if should_continue_further { @@ -278,10 +280,16 @@ impl Feature for types::PaymentsAu connector_response_reference_id, session_token, connector_request, - should_continue_further: should_continue, + should_continue_further, }; Ok((pre_decide_output, router_data)) } + fn get_current_flow_info(&self) -> Option> { + Some(api_interface::CurrentFlowInfo::Authorize { + auth_type: &self.auth_type, + request_data: &self.request, + }) + } async fn decide_flows<'a>( mut self, state: &SessionState, @@ -476,10 +484,10 @@ impl Feature for types::PaymentsAu .connector .get_preprocessing_flow_if_needed(current_flow_info); match preprocessing_flow_name { - Some(api_interface::PreProcessingFlowName::PreProcessing) | None => { - // Calling this function for None as well to maintain backward compatibility + Some(api_interface::PreProcessingFlowName::PreProcessing) => { authorize_preprocessing_steps(state, &self, true, connector).await } + None => Ok(self), } }