feat(connector): [Nuvei] Implement setup mandate flow for cards (#9012)

Co-authored-by: Vani Gupta <vani.gupta@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Vani Gupta
2025-08-26 17:34:41 +05:30
committed by GitHub
parent 30925ca5dd
commit 58ff01bab2
8 changed files with 603 additions and 103 deletions

View File

@ -19,7 +19,10 @@ use crate::{
},
routes::SessionState,
services,
types::{self, api, domain, transformers::ForeignTryFrom},
types::{
self, api, domain,
transformers::{ForeignFrom, ForeignTryFrom},
},
};
#[cfg(feature = "v1")]
@ -155,6 +158,38 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
.await
}
async fn add_session_token<'a>(
self,
state: &SessionState,
connector: &api::ConnectorData,
) -> RouterResult<Self>
where
Self: Sized,
{
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
api::AuthorizeSessionToken,
types::AuthorizeSessionTokenData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::foreign_from((
&self,
types::AuthorizeSessionTokenData::foreign_from(&self),
));
let resp = services::execute_connector_processing_step(
state,
connector_integration,
authorize_data,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.to_payment_failed_response()?;
let mut router_data = self;
router_data.session_token = resp.session_token;
Ok(router_data)
}
async fn add_payment_method_token<'a>(
&mut self,
state: &SessionState,
@ -213,6 +248,14 @@ impl Feature<api::SetupMandate, types::SetupMandateRequestData> for types::Setup
}
}
async fn preprocessing_steps<'a>(
self,
state: &SessionState,
connector: &api::ConnectorData,
) -> RouterResult<Self> {
setup_mandate_preprocessing_steps(state, &self, true, connector).await
}
async fn call_unified_connector_service<'a>(
&mut self,
state: &SessionState,
@ -301,3 +344,66 @@ impl mandate::MandateBehaviour for types::SetupMandateRequestData {
self.customer_acceptance.clone()
}
}
pub async fn setup_mandate_preprocessing_steps<F: Clone>(
state: &SessionState,
router_data: &types::RouterData<F, types::SetupMandateRequestData, types::PaymentsResponseData>,
confirm: bool,
connector: &api::ConnectorData,
) -> RouterResult<types::RouterData<F, types::SetupMandateRequestData, types::PaymentsResponseData>>
{
if confirm {
let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
api::PreProcessing,
types::PaymentsPreProcessingData,
types::PaymentsResponseData,
> = connector.connector.get_connector_integration();
let preprocessing_request_data =
types::PaymentsPreProcessingData::try_from(router_data.request.clone())?;
let preprocessing_response_data: Result<types::PaymentsResponseData, types::ErrorResponse> =
Err(types::ErrorResponse::default());
let preprocessing_router_data =
helpers::router_data_type_conversion::<_, api::PreProcessing, _, _, _, _>(
router_data.clone(),
preprocessing_request_data,
preprocessing_response_data,
);
let resp = services::execute_connector_processing_step(
state,
connector_integration,
&preprocessing_router_data,
payments::CallConnectorAction::Trigger,
None,
None,
)
.await
.to_payment_failed_response()?;
let mut setup_mandate_router_data = helpers::router_data_type_conversion::<_, F, _, _, _, _>(
resp.clone(),
router_data.request.to_owned(),
resp.response.clone(),
);
if connector.connector_name == api_models::enums::Connector::Nuvei {
let (enrolled_for_3ds, related_transaction_id) =
match &setup_mandate_router_data.response {
Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse {
enrolled_v2,
related_transaction_id,
}) => (*enrolled_v2, related_transaction_id.clone()),
_ => (false, None),
};
setup_mandate_router_data.request.enrolled_for_3ds = enrolled_for_3ds;
setup_mandate_router_data.request.related_transaction_id = related_transaction_id;
}
Ok(setup_mandate_router_data)
} else {
Ok(router_data.clone())
}
}

View File

@ -1301,6 +1301,8 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>(
customer_id: None,
enable_partial_authorization: None,
payment_channel: None,
enrolled_for_3ds: true,
related_transaction_id: None,
};
let connector_mandate_request_reference_id = payment_data
.payment_attempt
@ -5118,6 +5120,8 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::SetupMandateRequ
customer_id: payment_data.payment_intent.customer_id,
enable_partial_authorization: payment_data.payment_intent.enable_partial_authorization,
payment_channel: payment_data.payment_intent.payment_channel,
related_transaction_id: None,
enrolled_for_3ds: true,
})
}
}
@ -5369,6 +5373,8 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPreProce
enrolled_for_3ds: true,
split_payments: payment_data.payment_intent.split_payments,
metadata: payment_data.payment_intent.metadata.map(Secret::new),
customer_acceptance: payment_data.customer_acceptance,
setup_future_usage: payment_data.payment_intent.setup_future_usage,
})
}
}

View File

@ -1158,6 +1158,17 @@ impl ForeignFrom<&ExternalVaultProxyPaymentsRouterData> for AuthorizeSessionToke
}
}
impl<'a> ForeignFrom<&'a SetupMandateRouterData> for AuthorizeSessionTokenData {
fn foreign_from(data: &'a SetupMandateRouterData) -> Self {
Self {
amount_to_capture: data.request.amount,
currency: data.request.currency,
connector_transaction_id: data.payment_id.clone(),
amount: data.request.amount,
}
}
}
pub trait Tokenizable {
fn set_session_token(&mut self, token: Option<String>);
}