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,
}
impl AuthenticationType {
pub fn is_three_ds(&self) -> bool {
matches!(self, Self::ThreeDs)
}
}
/// The status of the capture
#[derive(
Clone,

View File

@ -2283,4 +2283,58 @@ impl ConnectorSpecifications for Cybersource {
fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
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,
TaxCalculationResponseData,
> for $path::$connector{}
impl ConnectorIntegrationV2<
impl ConnectorIntegrationV2<
SdkSessionUpdate,
PaymentFlowData,
SdkPaymentsSessionUpdateData,

View File

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

View File

@ -625,6 +625,38 @@ impl ConnectorValidation 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> {
match self {
Self::Old(connector) => connector.get_supported_payment_methods(),

View File

@ -66,6 +66,7 @@ use hyperswitch_domain_models::{
payments::{self, payment_intent::CustomerData, ClickToPayMetaData},
router_data::AccessToken,
};
use hyperswitch_interfaces::api::ConnectorSpecifications;
use masking::{ExposeInterface, PeekInterface, Secret};
#[cfg(feature = "v2")]
use operations::ValidateStatusForOperation;
@ -4080,7 +4081,7 @@ where
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
#[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,
req_state: ReqState,
merchant_context: &domain::MerchantContext,
@ -4097,7 +4098,7 @@ pub async fn _call_connector_service<F, RouterDReq, ApiRequest, D>(
is_retry_payment: bool,
return_raw_connector_response: Option<bool>,
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,
) -> RouterResult<(
RouterData<F, RouterDReq, router_types::PaymentsResponseData>,
@ -4728,27 +4729,61 @@ where
// Update feature metadata to track Direct routing usage for stickiness
update_gateway_system_in_feature_metadata(payment_data, GatewaySystem::Direct)?;
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
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);
if should_call_connector_service_with_pre_decide_flow {
logger::info!("Calling call_connector_service_with_pre_decide_flow");
call_connector_service_with_pre_decide_flow(
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
} 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")]
@ -4815,28 +4850,61 @@ where
// Update feature metadata to track Direct routing usage for stickiness
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
let result = 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?;
let result = if should_call_connector_service_with_pre_decide_flow {
logger::info!("Calling call_connector_service_with_pre_decide_flow");
call_connector_service_with_pre_decide_flow(
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?
} 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
let direct_router_data = result.0.clone();

View File

@ -26,6 +26,7 @@ use hyperswitch_domain_models::router_flow_types::{
use hyperswitch_domain_models::{
payments as domain_payments, router_request_types::PaymentsCaptureData,
};
use hyperswitch_interfaces::api as api_interfaces;
use crate::{
core::{
@ -112,10 +113,10 @@ pub trait Feature<F, T> {
async fn pre_decide_flows<'a>(
self,
state: &SessionState,
connector: &api::ConnectorData,
merchant_context: &domain::MerchantContext,
pre_decide_inputs: PreDecideFlowInputs<'a>,
_state: &SessionState,
_connector: &api::ConnectorData,
_merchant_context: &domain::MerchantContext,
_pre_decide_inputs: PreDecideFlowInputs<'a>,
) -> RouterResult<(PreDecideFlowOutput, Self)>
where
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>(
&mut self,
_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?;
let should_continue = matches!(
let should_continue_further = matches!(
router_data.response,
Ok(types::PaymentsResponseData::TransactionResponse {
ref redirection_data,
@ -259,6 +259,8 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
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
// 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 {
@ -278,10 +280,16 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
connector_response_reference_id,
session_token,
connector_request,
should_continue_further: should_continue,
should_continue_further,
};
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>(
mut self,
state: &SessionState,
@ -476,10 +484,10 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu
.connector
.get_preprocessing_flow_if_needed(current_flow_info);
match preprocessing_flow_name {
Some(api_interface::PreProcessingFlowName::PreProcessing) | None => {
// Calling this function for None as well to maintain backward compatibility
Some(api_interface::PreProcessingFlowName::PreProcessing) => {
authorize_preprocessing_steps(state, &self, true, connector).await
}
None => Ok(self),
}
}