mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	chore: introduce dedicated function to check for should continue after preprocessing
This commit is contained in:
		| @ -2286,7 +2286,7 @@ impl ConnectorSpecifications for Cybersource { | |||||||
|     fn get_preprocessing_flow_if_needed( |     fn get_preprocessing_flow_if_needed( | ||||||
|         &self, |         &self, | ||||||
|         current_flow_info: api::CurrentFlowInfo<'_>, |         current_flow_info: api::CurrentFlowInfo<'_>, | ||||||
|     ) -> Option<api::PreProcessingFlowDetails> { |     ) -> Option<api::PreProcessingFlowName> { | ||||||
|         match current_flow_info { |         match current_flow_info { | ||||||
|             api::CurrentFlowInfo::Authorize { .. } => { |             api::CurrentFlowInfo::Authorize { .. } => { | ||||||
|                 // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow |                 // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow | ||||||
| @ -2297,42 +2297,40 @@ impl ConnectorSpecifications for Cybersource { | |||||||
|                 let redirect_response = request_data.redirect_response.as_ref()?; |                 let redirect_response = request_data.redirect_response.as_ref()?; | ||||||
|                 match redirect_response.params.as_ref() { |                 match redirect_response.params.as_ref() { | ||||||
|                     Some(param) if !param.peek().is_empty() => { |                     Some(param) if !param.peek().is_empty() => { | ||||||
|                         let flow_name = api::PreProcessingFlowName::Authenticate; |                         Some(api::PreProcessingFlowName::Authenticate) | ||||||
|                         let should_continue = |                     } | ||||||
|                             |authn_result: &api::PreProcessingFlowResponse<'_>| -> bool { |                     Some(_) | None => Some(api::PreProcessingFlowName::PostAuthenticate), | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     fn decide_should_continue_after_preprocessing( | ||||||
|  |         &self, | ||||||
|  |         current_flow: api::CurrentFlowInfo<'_>, | ||||||
|  |         pre_processing_flow_name: api::PreProcessingFlowName, | ||||||
|  |         preprocessing_flow_response: api::PreProcessingFlowResponse<'_>, | ||||||
|  |     ) -> bool { | ||||||
|  |         match (current_flow, pre_processing_flow_name) { | ||||||
|  |             (api::CurrentFlowInfo::Authorize { .. }, _) => { | ||||||
|  |                 // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow | ||||||
|  |                 true | ||||||
|  |             } | ||||||
|  |             ( | ||||||
|  |                 api::CurrentFlowInfo::CompleteAuthorize { .. }, | ||||||
|  |                 api::PreProcessingFlowName::Authenticate, | ||||||
|  |             ) | ||||||
|  |             | ( | ||||||
|  |                 api::CurrentFlowInfo::CompleteAuthorize { .. }, | ||||||
|  |                 api::PreProcessingFlowName::PostAuthenticate, | ||||||
|  |             ) => { | ||||||
|                 (matches!( |                 (matches!( | ||||||
|                                     authn_result.response, |                     preprocessing_flow_response.response, | ||||||
|                     Ok(PaymentsResponseData::TransactionResponse { |                     Ok(PaymentsResponseData::TransactionResponse { | ||||||
|                         ref redirection_data, |                         ref redirection_data, | ||||||
|                         .. |                         .. | ||||||
|                     }) if redirection_data.is_none() |                     }) if redirection_data.is_none() | ||||||
|                                 ) && authn_result.attempt_status |                 ) && preprocessing_flow_response.attempt_status | ||||||
|                     != common_enums::AttemptStatus::AuthenticationFailed) |                     != 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), |  | ||||||
|                         }) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -429,43 +429,26 @@ pub struct PreProcessingFlowResponse<'a> { | |||||||
|     pub attempt_status: enums::AttemptStatus, |     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<dyn Fn(&PreProcessingFlowResponse<'_>) -> 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", |  | ||||||
|                 &"<closure: fn(&PreProcessingFlowResponse) -> bool>", |  | ||||||
|             ) |  | ||||||
|             .finish() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// The trait that provides specifications about the connector | /// The trait that provides specifications about the connector | ||||||
| pub trait ConnectorSpecifications { | pub trait ConnectorSpecifications { | ||||||
|     /// Preprocessing flow name if any, that must be made before the current flow. |     /// Preprocessing flow name if any, that must be made before the current flow. | ||||||
|     fn get_preprocessing_flow_if_needed( |     fn get_preprocessing_flow_if_needed( | ||||||
|         &self, |         &self, | ||||||
|         _current_flow: CurrentFlowInfo<'_>, |         _current_flow: CurrentFlowInfo<'_>, | ||||||
|     ) -> Option<PreProcessingFlowDetails> { |     ) -> Option<PreProcessingFlowName> { | ||||||
|         None |         None | ||||||
|     } |     } | ||||||
|  |     /// Based on the current flow and preprocessing_flow_response, decide if the main flow must be called or not | ||||||
|  |     /// | ||||||
|  |     /// By default, always continue with the main flow after the preprocessing flow. | ||||||
|  |     fn decide_should_continue_after_preprocessing( | ||||||
|  |         &self, | ||||||
|  |         _current_flow: CurrentFlowInfo<'_>, | ||||||
|  |         _pre_processing_flow_name: PreProcessingFlowName, | ||||||
|  |         _preprocessing_flow_response: PreProcessingFlowResponse<'_>, | ||||||
|  |     ) -> bool { | ||||||
|  |         true | ||||||
|  |     } | ||||||
|     /// If Some is returned, the returned api flow must be made instead of the current flow. |     /// If Some is returned, the returned api flow must be made instead of the current flow. | ||||||
|     fn get_alternate_flow_if_needed( |     fn get_alternate_flow_if_needed( | ||||||
|         &self, |         &self, | ||||||
|  | |||||||
| @ -511,7 +511,7 @@ impl ConnectorSpecifications for ConnectorEnum { | |||||||
|     fn get_preprocessing_flow_if_needed( |     fn get_preprocessing_flow_if_needed( | ||||||
|         &self, |         &self, | ||||||
|         current_flow_info: api::CurrentFlowInfo<'_>, |         current_flow_info: api::CurrentFlowInfo<'_>, | ||||||
|     ) -> Option<api::PreProcessingFlowDetails> { |     ) -> Option<api::PreProcessingFlowName> { | ||||||
|         match self { |         match self { | ||||||
|             Self::Old(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), |             Self::Old(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), | ||||||
|             Self::New(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), |             Self::New(connector) => connector.get_preprocessing_flow_if_needed(current_flow_info), | ||||||
|  | |||||||
| @ -0,0 +1,23 @@ | |||||||
|  | Currently in hyperswitch, under authorize flow, there can be a lot of flows that must be called before actually calling the authorize. Like session/access token, customer create, order create etc.  | ||||||
|  | Currently these are scattered all across the handler function 'pub async fn payments_operation_core<F, Req, Op, FData, D>(' . | ||||||
|  | I want to standardize the flows like this. | ||||||
|  |  | ||||||
|  | PrimaryFlows and Secondary Flows. | ||||||
|  | PrimaryFlows: The actual flow. | ||||||
|  | SecondaryFlow: These flows might come as prerequisites before the Primary Flows. There can be multiple Secondary Flows for a PrimaryFlow. | ||||||
|  |  | ||||||
|  | PrimaryFlows can be defined as: | ||||||
|  |     1. A flow where the response is returned to the client. | ||||||
|  |         Eg: Authorize, Capture | ||||||
|  |         Authorize can have SessionToken, OrderCreate etc. | ||||||
|  |  | ||||||
|  | If Authorize is a PrimaryFlow, Then SessionTokena and OrderCreate will be SecondaryFlow. | ||||||
|  | The order will be like SessionToken(2ndary) -> OrderCreate(2ndary) -> Authorize(Primary). | ||||||
|  | Similarly,  | ||||||
|  | * SessionToken(2ndary) -> PreAuthN(Primary). | ||||||
|  | * SessionToken(2ndary) -> AuthN(Primary). | ||||||
|  | * PostAuthN(2ndary) -> Authorization(Primary) | ||||||
|  |  | ||||||
|  | Lets have marker trait for Primary and Secondary Flows. Feel free to come up with a better nomenclature for things. | ||||||
|  |  | ||||||
|  | Execution order must be known at compile time. | ||||||
| @ -219,43 +219,55 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData> | |||||||
|  |  | ||||||
|     async fn call_preprocessing_through_unified_connector_service<'a>( |     async fn call_preprocessing_through_unified_connector_service<'a>( | ||||||
|         self, |         self, | ||||||
|         _state: &SessionState, |         state: &SessionState, | ||||||
|         _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, |         header_payload: &hyperswitch_domain_models::payments::HeaderPayload, | ||||||
|         _lineage_ids: &grpc_client::LineageIds, |         lineage_ids: &grpc_client::LineageIds, | ||||||
|         #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, |         #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType, | ||||||
|         #[cfg(feature = "v2")] |         #[cfg(feature = "v2")] | ||||||
|         _merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, |         merchant_connector_account: domain::MerchantConnectorAccountTypeDetails, | ||||||
|         _merchant_context: &domain::MerchantContext, |         merchant_context: &domain::MerchantContext, | ||||||
|         connector_data: &api::ConnectorData, |         connector_data: &api::ConnectorData, | ||||||
|         _unified_connector_service_execution_mode: common_enums::ExecutionMode, |         unified_connector_service_execution_mode: common_enums::ExecutionMode, | ||||||
|         _merchant_order_reference_id: Option<String>, |         merchant_order_reference_id: Option<String>, | ||||||
|     ) -> RouterResult<(Self, bool)> { |     ) -> RouterResult<(Self, bool)> { | ||||||
|         let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { |         let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { | ||||||
|             request_data: &self.request, |             request_data: &self.request, | ||||||
|         }; |         }; | ||||||
|         if let Some(preprocessing_flow_details) = connector_data |         let optional_preprocessing_flow = connector_data | ||||||
|             .connector |             .connector | ||||||
|             .get_preprocessing_flow_if_needed(current_flow) |             .get_preprocessing_flow_if_needed(current_flow); | ||||||
|         { |         match optional_preprocessing_flow { | ||||||
|             let updated_router_data = match preprocessing_flow_details.flow_name { |             Some(preprocessing_flow) => { | ||||||
|                 api_interface::PreProcessingFlowName::Authenticate => { |                 let updated_router_data = handle_preprocessing_through_unified_connector_service( | ||||||
|                     // Call UCS for Authenticate flow |                     self, | ||||||
|                     self |                     state, | ||||||
|                 } |                     header_payload, | ||||||
|                 api_interface::PreProcessingFlowName::PostAuthenticate => { |                     lineage_ids, | ||||||
|                     // Call UCS for PostAuthenticate flow |                     merchant_connector_account.clone(), | ||||||
|                     self |                     merchant_context, | ||||||
|                 } |                     connector_data, | ||||||
|             }; |                     unified_connector_service_execution_mode, | ||||||
|  |                     merchant_order_reference_id.clone(), | ||||||
|  |                     preprocessing_flow, | ||||||
|  |                 ) | ||||||
|  |                 .await?; | ||||||
|                 let pre_processing_flow_response = api_interface::PreProcessingFlowResponse { |                 let pre_processing_flow_response = api_interface::PreProcessingFlowResponse { | ||||||
|                     response: &updated_router_data.response, |                     response: &updated_router_data.response, | ||||||
|                     attempt_status: updated_router_data.status, |                     attempt_status: updated_router_data.status, | ||||||
|                 }; |                 }; | ||||||
|             let should_continue = |                 let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { | ||||||
|                 (preprocessing_flow_details.should_continue)(&pre_processing_flow_response); |                     request_data: &updated_router_data.request, | ||||||
|  |                 }; | ||||||
|  |                 let should_continue = connector_data | ||||||
|  |                     .connector | ||||||
|  |                     .decide_should_continue_after_preprocessing( | ||||||
|  |                         current_flow, | ||||||
|  |                         preprocessing_flow, | ||||||
|  |                         pre_processing_flow_response, | ||||||
|  |                     ); | ||||||
|                 Ok((updated_router_data, should_continue)) |                 Ok((updated_router_data, should_continue)) | ||||||
|         } else { |             } | ||||||
|             Ok((self, true)) |             None => Ok((self, true)), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -277,6 +289,41 @@ impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData> | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async fn handle_preprocessing_through_unified_connector_service( | ||||||
|  |     router_data: types::RouterData< | ||||||
|  |         api::CompleteAuthorize, | ||||||
|  |         types::CompleteAuthorizeData, | ||||||
|  |         types::PaymentsResponseData, | ||||||
|  |     >, | ||||||
|  |     _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, | ||||||
|  |     _merchant_order_reference_id: Option<String>, | ||||||
|  |     preprocessing_flow_name: api_interface::PreProcessingFlowName, | ||||||
|  | ) -> RouterResult< | ||||||
|  |     types::RouterData< | ||||||
|  |         api::CompleteAuthorize, | ||||||
|  |         types::CompleteAuthorizeData, | ||||||
|  |         types::PaymentsResponseData, | ||||||
|  |     >, | ||||||
|  | > { | ||||||
|  |     match preprocessing_flow_name { | ||||||
|  |         api_interface::PreProcessingFlowName::Authenticate => { | ||||||
|  |             // Call UCS for Authenticate flow | ||||||
|  |             Ok(router_data) | ||||||
|  |         } | ||||||
|  |         api_interface::PreProcessingFlowName::PostAuthenticate => { | ||||||
|  |             // Call UCS for PostAuthenticate flow | ||||||
|  |             Ok(router_data) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn complete_authorize_preprocessing_steps<F: Clone>( | pub async fn complete_authorize_preprocessing_steps<F: Clone>( | ||||||
|     state: &SessionState, |     state: &SessionState, | ||||||
|     router_data: &types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>, |     router_data: &types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>, | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 hrithikesh026
					hrithikesh026