diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource.rs b/crates/hyperswitch_connectors/src/connectors/cybersource.rs index e8e8aede29..1020736eb9 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource.rs @@ -2286,12 +2286,54 @@ impl ConnectorSpecifications for Cybersource { fn get_preprocessing_flow_if_needed( &self, current_flow_info: api::CurrentFlowInfo<'_>, - ) -> Option { + ) -> Option { match current_flow_info { api::CurrentFlowInfo::Authorize { .. } => { // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow None } + api::CurrentFlowInfo::CompleteAuthorize { request_data } => { + // TODO: add logic before deciding the pre processing flow Authenticate or PostAuthenticate + let redirect_response = request_data.redirect_response.as_ref()?; + match redirect_response.params.as_ref() { + Some(param) if !param.peek().is_empty() => { + let flow_name = api::PreProcessingFlowName::Authenticate; + let should_continue = + |authn_result: &api::PreProcessingFlowResponse<'_>| -> bool { + (matches!( + authn_result.response, + Ok(PaymentsResponseData::TransactionResponse { + ref redirection_data, + .. + }) if redirection_data.is_none() + ) && authn_result.attempt_status + != common_enums::AttemptStatus::AuthenticationFailed) + }; + Some(api::PreProcessingFlowDetails { + flow_name, + should_continue: Box::new(should_continue), + }) + } + Some(_) | None => { + let flow_name = api::PreProcessingFlowName::PostAuthenticate; + let should_continue = + |authn_result: &api::PreProcessingFlowResponse<'_>| -> bool { + (matches!( + authn_result.response, + Ok(PaymentsResponseData::TransactionResponse { + ref redirection_data, + .. + }) if redirection_data.is_none() + ) && authn_result.attempt_status + != common_enums::AttemptStatus::AuthenticationFailed) + }; + Some(api::PreProcessingFlowDetails { + flow_name, + should_continue: Box::new(should_continue), + }) + } + } + } } } fn get_alternate_flow_if_needed( @@ -2309,6 +2351,8 @@ impl ConnectorSpecifications for Cybersource { None } } + // No alternate flow for complete authorize + api::CurrentFlowInfo::CompleteAuthorize { .. } => None, } } } diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 995fd9f2f3..8d9a73bfef 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -655,12 +655,12 @@ pub struct PaymentsAuthenticateData { pub amount: Option, pub email: Option, pub currency: Option, - pub payment_method_type: Option, - pub router_return_url: Option, + // pub payment_method_type: Option, + // pub router_return_url: Option, pub complete_authorize_url: Option, pub browser_info: Option, - pub connector_transaction_id: Option, - pub enrolled_for_3ds: bool, + // pub connector_transaction_id: Option, + // pub enrolled_for_3ds: bool, pub redirect_response: Option, // New amount for amount frame work @@ -677,13 +677,30 @@ impl TryFrom for PaymentsAuthenticateData { minor_amount: Some(data.minor_amount), email: data.email, currency: Some(data.currency), - payment_method_type: data.payment_method_type, - router_return_url: data.router_return_url, + // payment_method_type: data.payment_method_type, + // router_return_url: data.router_return_url, complete_authorize_url: data.complete_authorize_url, browser_info: data.browser_info, - connector_transaction_id: None, + // connector_transaction_id: None, redirect_response: None, - enrolled_for_3ds: data.enrolled_for_3ds, + // enrolled_for_3ds: data.enrolled_for_3ds, + }) + } +} + +impl TryFrom for PaymentsAuthenticateData { + type Error = error_stack::Report; + + fn try_from(data: CompleteAuthorizeData) -> Result { + Ok(Self { + payment_method_data: data.payment_method_data, + amount: Some(data.amount), + minor_amount: Some(data.minor_amount), + email: data.email, + currency: Some(data.currency), + complete_authorize_url: data.complete_authorize_url, + browser_info: data.browser_info, + redirect_response: data.redirect_response, }) } } diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index d91d609a9b..0baf18d9ef 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -57,17 +57,18 @@ use hyperswitch_domain_models::{ AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, VerifyWebhookSource, }, router_request_types::{ + self, unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, UasConfirmationRequestData, UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AccessTokenAuthenticationRequestData, AccessTokenRequestData, MandateRevokeRequestData, - PaymentsAuthorizeData, VerifyWebhookSourceRequestData, + VerifyWebhookSourceRequestData, }, router_response_types::{ - ConnectorInfo, MandateRevokeResponseData, PaymentMethodDetails, SupportedPaymentMethods, - VerifyWebhookSourceResponseData, + self, ConnectorInfo, MandateRevokeResponseData, PaymentMethodDetails, + SupportedPaymentMethods, VerifyWebhookSourceResponseData, }, }; use masking::Maskable; @@ -390,7 +391,12 @@ pub enum CurrentFlowInfo<'a> { /// The authentication type being used auth_type: &'a enums::AuthenticationType, /// The payment authorize request data - request_data: &'a PaymentsAuthorizeData, + request_data: &'a router_request_types::PaymentsAuthorizeData, + }, + /// CompleteAuthorize flow information + CompleteAuthorize { + /// The payment authorize request data + request_data: &'a router_request_types::CompleteAuthorizeData, }, } @@ -406,11 +412,49 @@ pub enum AlternateFlow { /// /// For example, PreProcessing flow must be made before Authorize flow. /// Or PostAuthenticate flow must be made before CompleteAuthorize flow for cybersource. -#[derive(Default, Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum PreProcessingFlowName { - #[default] - /// Pre-processing flow - PreProcessing, + /// Authentication flow must be made before the actual flow + Authenticate, + /// Post-authentication flow must be made before the actual flow + PostAuthenticate, +} + +/// Response of the preprocessing flow +#[derive(Debug)] +pub struct PreProcessingFlowResponse<'a> { + /// Payment response data from the preprocessing flow + pub response: &'a Result, + /// Attempt status after the preprocessing flow + pub attempt_status: enums::AttemptStatus, +} + +/// Details related to preprocessing flow +pub struct PreProcessingFlowDetails { + /// Name of the preprocessing flow + pub flow_name: PreProcessingFlowName, + + /// Based on the response of the preprocessing flow, decide whether to continue with the current flow or not + pub should_continue: Box) -> bool>, +} + +/// Custom Debug implementation for PreProcessingFlowDetails +/// +/// Since Closure does not implement Debug trait +impl Debug for PreProcessingFlowDetails { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + flow_name, + should_continue: _, + } = self; + f.debug_struct("PreProcessingFlowDetails") + .field("flow_name", flow_name) + .field( + "should_continue", + &" bool>", + ) + .finish() + } } /// The trait that provides specifications about the connector @@ -419,8 +463,8 @@ pub trait ConnectorSpecifications { fn get_preprocessing_flow_if_needed( &self, _current_flow: CurrentFlowInfo<'_>, - ) -> Option { - Some(PreProcessingFlowName::default()) + ) -> Option { + None } /// If Some is returned, the returned api flow must be made instead of the current flow. fn get_alternate_flow_if_needed( @@ -766,7 +810,7 @@ pub trait ConnectorValidation: ConnectorCommon + ConnectorSpecifications { /// fn validate_psync_reference_id fn validate_psync_reference_id( &self, - data: &hyperswitch_domain_models::router_request_types::PaymentsSyncData, + data: &router_request_types::PaymentsSyncData, _is_three_ds: bool, _status: enums::AttemptStatus, _connector_meta_data: Option, diff --git a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs index aa3740b3c2..edcf0ed2f3 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_interface.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_interface.rs @@ -511,7 +511,7 @@ impl ConnectorSpecifications for ConnectorEnum { fn get_preprocessing_flow_if_needed( &self, current_flow_info: api::CurrentFlowInfo<'_>, - ) -> Option { + ) -> 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), diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index bc3fce2e8c..45519815fb 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -79,19 +79,19 @@ pub trait ConstructFlowSpecificData { } } -pub struct PreDecideFlowOutput { - pub connector_response_reference_id: Option, - pub session_token: Option, - pub connector_request: Option, - pub should_continue_further: bool, -} +// pub struct PreDecideFlowOutput { +// pub connector_response_reference_id: Option, +// pub session_token: Option, +// pub connector_request: Option, +// pub should_continue_further: bool, +// } -pub struct PreDecideFlowInputs<'a> { - pub call_connector_action: &'a payments::CallConnectorAction, - pub tokenization_action: &'a payments::TokenizationAction, - pub is_retry_payment: bool, - pub creds_identifier: Option<&'a str>, -} +// pub struct PreDecideFlowInputs<'a> { +// pub call_connector_action: &'a payments::CallConnectorAction, +// pub tokenization_action: &'a payments::TokenizationAction, +// pub is_retry_payment: bool, +// pub creds_identifier: Option<&'a str>, +// } #[allow(clippy::too_many_arguments)] #[async_trait] @@ -227,6 +227,28 @@ pub trait Feature { fn get_current_flow_info(&self) -> Option> { None } + + async fn call_preprocessing_through_unified_connector_service<'a>( + self, + _state: &SessionState, + _header_payload: &domain_payments::HeaderPayload, + _lineage_ids: &grpc_client::LineageIds, + #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + _merchant_context: &domain::MerchantContext, + _connector_data: &api::ConnectorData, + _unified_connector_service_execution_mode: ExecutionMode, + ) -> RouterResult<(Self, bool)> + where + F: Clone, + Self: Sized, + dyn api::Connector: services::ConnectorIntegration, + { + // Default behaviour is to do nothing and continue further + Ok((self, true)) + } + async fn call_unified_connector_service<'a>( &mut self, _state: &SessionState, diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index fbca96d93f..87dfb83a2f 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -1,4 +1,6 @@ use async_trait::async_trait; +use external_services::grpc_client; +use hyperswitch_interfaces::{api as api_interface, api::ConnectorSpecifications}; use masking::ExposeInterface; use super::{ConstructFlowSpecificData, Feature}; @@ -214,6 +216,63 @@ impl Feature ) -> RouterResult { complete_authorize_preprocessing_steps(state, &self, true, connector).await } + + async fn call_preprocessing_through_unified_connector_service<'a>( + self, + _state: &SessionState, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _lineage_ids: &grpc_client::LineageIds, + #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + _merchant_context: &domain::MerchantContext, + connector_data: &api::ConnectorData, + _unified_connector_service_execution_mode: common_enums::ExecutionMode, + ) -> RouterResult<(Self, bool)> { + let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { + request_data: &self.request, + }; + if let Some(preprocessing_flow_details) = connector_data + .connector + .get_preprocessing_flow_if_needed(current_flow) + { + let updated_router_data = match preprocessing_flow_details.flow_name { + api_interface::PreProcessingFlowName::Authenticate => { + // Call UCS for Authenticate flow + self + } + api_interface::PreProcessingFlowName::PostAuthenticate => { + // Call UCS for PostAuthenticate flow + self + } + }; + let pre_processing_flow_response = api_interface::PreProcessingFlowResponse { + response: &updated_router_data.response, + attempt_status: updated_router_data.status, + }; + let should_continue = + (preprocessing_flow_details.should_continue)(&pre_processing_flow_response); + Ok((updated_router_data, should_continue)) + } else { + Ok((self, true)) + } + } + + async fn call_unified_connector_service<'a>( + &mut self, + _state: &SessionState, + _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, + _lineage_ids: grpc_client::LineageIds, + #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, + #[cfg(feature = "v2")] + _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, + _merchant_context: &domain::MerchantContext, + _connector_data: &api::ConnectorData, + _unified_connector_service_execution_mode: common_enums::ExecutionMode, + ) -> RouterResult<()> { + // Call UCS for Authorize flow + Ok(()) + } } pub async fn complete_authorize_preprocessing_steps( diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index cbbd27af9c..e353b7dafe 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -7873,7 +7873,7 @@ pub async fn process_through_ucs<'a, F, RouterDReq, ApiRequest, D>( business_profile: &'a domain::Profile, merchant_connector_account: MerchantConnectorAccountType, connector_data: &api::ConnectorData, - mut router_data: RouterData, + router_data: RouterData, ) -> RouterResult<( RouterData, MerchantConnectorAccountType, @@ -7917,6 +7917,22 @@ where GatewaySystem::UnifiedConnectorService, )?; + let lineage_ids = grpc_client::LineageIds::new( + business_profile.merchant_id.clone(), + business_profile.get_id().clone(), + ); + let (mut router_data, should_continue) = router_data + .call_preprocessing_through_unified_connector_service( + state, + &header_payload, + &lineage_ids, + merchant_connector_account.clone(), + merchant_context, + connector_data, + ExecutionMode::Primary, // UCS is called in primary mode + ) + .await?; + // Update trackers (_, *payment_data) = operation .to_update_tracker()? @@ -7933,23 +7949,20 @@ where ) .await?; - // Call UCS - let lineage_ids = grpc_client::LineageIds::new( - business_profile.merchant_id.clone(), - business_profile.get_id().clone(), - ); - - router_data - .call_unified_connector_service( - state, - &header_payload, - lineage_ids, - merchant_connector_account.clone(), - merchant_context, - connector_data, - ExecutionMode::Primary, // UCS is called in primary mode - ) - .await?; + // Based on the preprocessing response, decide whether to continue with UCS call + if should_continue { + router_data + .call_unified_connector_service( + state, + &header_payload, + lineage_ids, + merchant_connector_account.clone(), + merchant_context, + connector_data, + ExecutionMode::Primary, // UCS is called in primary mode + ) + .await?; + } Ok((router_data, merchant_connector_account)) }