chore: add logic to use pre decide flow

This commit is contained in:
hrithikesh026
2025-10-14 00:28:51 +05:30
parent 42a451c19a
commit 94e4d9330c
8 changed files with 232 additions and 53 deletions

View File

@ -348,6 +348,12 @@ pub enum AuthenticationType {
NoThreeDs, NoThreeDs,
} }
impl AuthenticationType {
pub fn is_three_ds(&self) -> bool {
matches!(self, Self::ThreeDs)
}
}
/// The status of the capture /// The status of the capture
#[derive( #[derive(
Clone, Clone,

View File

@ -2283,4 +2283,58 @@ impl ConnectorSpecifications for Cybersource {
fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
Some(&CYBERSOURCE_SUPPORTED_WEBHOOK_FLOWS) 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<api::PreProcessingFlowName> {
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<api::AlternateFlow> {
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()
}
} }

View File

@ -249,7 +249,7 @@ macro_rules! default_imp_for_new_connector_integration_payment {
PaymentsTaxCalculationData, PaymentsTaxCalculationData,
TaxCalculationResponseData, TaxCalculationResponseData,
> for $path::$connector{} > for $path::$connector{}
impl ConnectorIntegrationV2< impl ConnectorIntegrationV2<
SdkSessionUpdate, SdkSessionUpdate,
PaymentFlowData, PaymentFlowData,
SdkPaymentsSessionUpdateData, SdkPaymentsSessionUpdateData,

View File

@ -415,6 +415,13 @@ pub enum PreProcessingFlowName {
/// The trait that provides specifications about the connector /// The trait that provides specifications about the connector
pub trait ConnectorSpecifications { 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. /// 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,

View File

@ -625,6 +625,38 @@ impl ConnectorValidation for ConnectorEnum {
} }
impl ConnectorSpecifications 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<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),
}
}
fn get_alternate_flow_if_needed(
&self,
current_flow: api::CurrentFlowInfo<'_>,
) -> Option<api::AlternateFlow> {
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> { fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
match self { match self {
Self::Old(connector) => connector.get_supported_payment_methods(), Self::Old(connector) => connector.get_supported_payment_methods(),

View File

@ -66,6 +66,7 @@ use hyperswitch_domain_models::{
payments::{self, payment_intent::CustomerData, ClickToPayMetaData}, payments::{self, payment_intent::CustomerData, ClickToPayMetaData},
router_data::AccessToken, router_data::AccessToken,
}; };
use hyperswitch_interfaces::api::ConnectorSpecifications;
use masking::{ExposeInterface, PeekInterface, Secret}; use masking::{ExposeInterface, PeekInterface, Secret};
#[cfg(feature = "v2")] #[cfg(feature = "v2")]
use operations::ValidateStatusForOperation; use operations::ValidateStatusForOperation;
@ -4080,7 +4081,7 @@ where
#[cfg(feature = "v1")] #[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn _call_connector_service<F, RouterDReq, ApiRequest, D>( pub async fn call_connector_service_with_pre_decide_flow<F, RouterDReq, ApiRequest, D>(
state: &SessionState, state: &SessionState,
req_state: ReqState, req_state: ReqState,
merchant_context: &domain::MerchantContext, merchant_context: &domain::MerchantContext,
@ -4097,7 +4098,7 @@ pub async fn _call_connector_service<F, RouterDReq, ApiRequest, D>(
is_retry_payment: bool, is_retry_payment: bool,
return_raw_connector_response: Option<bool>, return_raw_connector_response: Option<bool>,
merchant_connector_account: helpers::MerchantConnectorAccountType, merchant_connector_account: helpers::MerchantConnectorAccountType,
mut router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>, router_data: RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
tokenization_action: TokenizationAction, tokenization_action: TokenizationAction,
) -> RouterResult<( ) -> RouterResult<(
RouterData<F, RouterDReq, router_types::PaymentsResponseData>, RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
@ -4728,27 +4729,61 @@ where
// Update feature metadata to track Direct routing usage for stickiness // Update feature metadata to track Direct routing usage for stickiness
update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?; update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?;
call_connector_service( let should_call_connector_service_with_pre_decide_flow = router_data
state, .get_current_flow_info()
req_state, .map(|current_flow_info| {
merchant_context, connector
connector, .connector
operation, .should_call_connector_service_with_pre_decide_flow(current_flow_info)
payment_data, })
customer, .unwrap_or(false);
call_connector_action,
validate_result, if should_call_connector_service_with_pre_decide_flow {
schedule_time, logger::info!("Calling call_connector_service_with_pre_decide_flow");
header_payload, call_connector_service_with_pre_decide_flow(
frm_suggestion, state,
business_profile, req_state,
is_retry_payment, merchant_context,
all_keys_required, connector,
merchant_connector_account, operation,
router_data, payment_data,
tokenization_action, customer,
) call_connector_action,
.await 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")] #[cfg(feature = "v1")]
@ -4815,28 +4850,61 @@ where
// Update feature metadata to track Direct routing usage for stickiness // Update feature metadata to track Direct routing usage for stickiness
update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?; 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 // Call Direct connector service
let result = call_connector_service( let result = if should_call_connector_service_with_pre_decide_flow {
state, logger::info!("Calling call_connector_service_with_pre_decide_flow");
req_state, call_connector_service_with_pre_decide_flow(
merchant_context, state,
connector, req_state,
operation, merchant_context,
payment_data, connector,
customer, operation,
call_connector_action, payment_data,
validate_result, customer,
schedule_time, call_connector_action,
header_payload, validate_result,
frm_suggestion, schedule_time,
business_profile, header_payload,
is_retry_payment, frm_suggestion,
all_keys_required, business_profile,
merchant_connector_account, is_retry_payment,
router_data, all_keys_required,
tokenization_action, merchant_connector_account,
) router_data,
.await?; 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 // Spawn shadow UCS call in background
let direct_router_data = result.0.clone(); let direct_router_data = result.0.clone();

View File

@ -26,6 +26,7 @@ use hyperswitch_domain_models::router_flow_types::{
use hyperswitch_domain_models::{ use hyperswitch_domain_models::{
payments as domain_payments, router_request_types::PaymentsCaptureData, payments as domain_payments, router_request_types::PaymentsCaptureData,
}; };
use hyperswitch_interfaces::api as api_interfaces;
use crate::{ use crate::{
core::{ core::{
@ -112,10 +113,10 @@ pub trait Feature<F, T> {
async fn pre_decide_flows<'a>( async fn pre_decide_flows<'a>(
self, self,
state: &SessionState, _state: &SessionState,
connector: &api::ConnectorData, _connector: &api::ConnectorData,
merchant_context: &domain::MerchantContext, _merchant_context: &domain::MerchantContext,
pre_decide_inputs: PreDecideFlowInputs<'a>, _pre_decide_inputs: PreDecideFlowInputs<'a>,
) -> RouterResult<(PreDecideFlowOutput, Self)> ) -> RouterResult<(PreDecideFlowOutput, Self)>
where where
Self: Sized, Self: Sized,
@ -246,6 +247,9 @@ pub trait Feature<F, T> {
) { ) {
} }
fn get_current_flow_info(&self) -> Option<api_interfaces::CurrentFlowInfo<'_>> {
None
}
async fn call_unified_connector_service<'a>( async fn call_unified_connector_service<'a>(
&mut self, &mut self,
_state: &SessionState, _state: &SessionState,

View File

@ -240,7 +240,7 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
); );
router_data = router_data.preprocessing_steps(state, connector).await?; router_data = router_data.preprocessing_steps(state, connector).await?;
let should_continue = matches!( let should_continue_further = matches!(
router_data.response, router_data.response,
Ok(types::PaymentsResponseData::TransactionResponse { Ok(types::PaymentsResponseData::TransactionResponse {
ref redirection_data, ref redirection_data,
@ -259,6 +259,8 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
None 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 // 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) // 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 { let (connector_request, should_continue_further) = if should_continue_further {
@ -278,10 +280,16 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
connector_response_reference_id, connector_response_reference_id,
session_token, session_token,
connector_request, connector_request,
should_continue_further: should_continue, should_continue_further,
}; };
Ok((pre_decide_output, router_data)) Ok((pre_decide_output, router_data))
} }
fn get_current_flow_info(&self) -> Option<api_interface::CurrentFlowInfo<'_>> {
Some(api_interface::CurrentFlowInfo::Authorize {
auth_type: &self.auth_type,
request_data: &self.request,
})
}
async fn decide_flows<'a>( async fn decide_flows<'a>(
mut self, mut self,
state: &SessionState, state: &SessionState,
@ -476,10 +484,10 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
.connector .connector
.get_preprocessing_flow_if_needed(current_flow_info); .get_preprocessing_flow_if_needed(current_flow_info);
match preprocessing_flow_name { match preprocessing_flow_name {
Some(api_interface::PreProcessingFlowName::PreProcessing) | None => { Some(api_interface::PreProcessingFlowName::PreProcessing) => {
// Calling this function for None as well to maintain backward compatibility
authorize_preprocessing_steps(state, &self, true, connector).await authorize_preprocessing_steps(state, &self, true, connector).await
} }
None => Ok(self),
} }
} }