mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-30 17:47:54 +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( | ||||
|         &self, | ||||
|         current_flow_info: api::CurrentFlowInfo<'_>, | ||||
|     ) -> Option<api::PreProcessingFlowDetails> { | ||||
|     ) -> Option<api::PreProcessingFlowName> { | ||||
|         match current_flow_info { | ||||
|             api::CurrentFlowInfo::Authorize { .. } => { | ||||
|                 // during authorize flow, there is no pre processing flow. Only alternate PreAuthenticate flow | ||||
| @ -2297,45 +2297,43 @@ impl ConnectorSpecifications for Cybersource { | ||||
|                 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), | ||||
|                         }) | ||||
|                         Some(api::PreProcessingFlowName::Authenticate) | ||||
|                     } | ||||
|                     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!( | ||||
|                     preprocessing_flow_response.response, | ||||
|                     Ok(PaymentsResponseData::TransactionResponse { | ||||
|                         ref redirection_data, | ||||
|                         .. | ||||
|                     }) if redirection_data.is_none() | ||||
|                 ) && preprocessing_flow_response.attempt_status | ||||
|                     != common_enums::AttemptStatus::AuthenticationFailed) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn get_alternate_flow_if_needed( | ||||
|         &self, | ||||
|         current_flow: api::CurrentFlowInfo<'_>, | ||||
|  | ||||
| @ -429,43 +429,26 @@ pub struct PreProcessingFlowResponse<'a> { | ||||
|     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 | ||||
| pub trait ConnectorSpecifications { | ||||
|     /// Preprocessing flow name if any, that must be made before the current flow. | ||||
|     fn get_preprocessing_flow_if_needed( | ||||
|         &self, | ||||
|         _current_flow: CurrentFlowInfo<'_>, | ||||
|     ) -> Option<PreProcessingFlowDetails> { | ||||
|     ) -> Option<PreProcessingFlowName> { | ||||
|         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. | ||||
|     fn get_alternate_flow_if_needed( | ||||
|         &self, | ||||
|  | ||||
| @ -511,7 +511,7 @@ impl ConnectorSpecifications for ConnectorEnum { | ||||
|     fn get_preprocessing_flow_if_needed( | ||||
|         &self, | ||||
|         current_flow_info: api::CurrentFlowInfo<'_>, | ||||
|     ) -> Option<api::PreProcessingFlowDetails> { | ||||
|     ) -> Option<api::PreProcessingFlowName> { | ||||
|         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), | ||||
|  | ||||
| @ -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>( | ||||
|         self, | ||||
|         _state: &SessionState, | ||||
|         _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, | ||||
|         _lineage_ids: &grpc_client::LineageIds, | ||||
|         #[cfg(feature = "v1")] _merchant_connector_account: helpers::MerchantConnectorAccountType, | ||||
|         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, | ||||
|         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>, | ||||
|         unified_connector_service_execution_mode: common_enums::ExecutionMode, | ||||
|         merchant_order_reference_id: Option<String>, | ||||
|     ) -> RouterResult<(Self, bool)> { | ||||
|         let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { | ||||
|             request_data: &self.request, | ||||
|         }; | ||||
|         if let Some(preprocessing_flow_details) = connector_data | ||||
|         let optional_preprocessing_flow = 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)) | ||||
|             .get_preprocessing_flow_if_needed(current_flow); | ||||
|         match optional_preprocessing_flow { | ||||
|             Some(preprocessing_flow) => { | ||||
|                 let updated_router_data = handle_preprocessing_through_unified_connector_service( | ||||
|                     self, | ||||
|                     state, | ||||
|                     header_payload, | ||||
|                     lineage_ids, | ||||
|                     merchant_connector_account.clone(), | ||||
|                     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 { | ||||
|                     response: &updated_router_data.response, | ||||
|                     attempt_status: updated_router_data.status, | ||||
|                 }; | ||||
|                 let current_flow = api_interface::CurrentFlowInfo::CompleteAuthorize { | ||||
|                     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)) | ||||
|             } | ||||
|             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>( | ||||
|     state: &SessionState, | ||||
|     router_data: &types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 hrithikesh026
					hrithikesh026