chore: introduce dedicated function to check for should continue after preprocessing

This commit is contained in:
hrithikesh026
2025-10-22 18:23:16 +05:30
parent 314b938ec6
commit aae6a6b75e
5 changed files with 146 additions and 95 deletions

View File

@ -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,42 +2297,40 @@ 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 {
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!(
authn_result.response,
preprocessing_flow_response.response,
Ok(PaymentsResponseData::TransactionResponse {
ref redirection_data,
..
}) if redirection_data.is_none()
) && authn_result.attempt_status
) && preprocessing_flow_response.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),
})
}
}
}
}
}

View File

@ -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,

View File

@ -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),

View File

@ -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.

View File

@ -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
}
};
.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 should_continue =
(preprocessing_flow_details.should_continue)(&pre_processing_flow_response);
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))
} 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>(
state: &SessionState,
router_data: &types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>,