From e7acaa5716e93a7ebcd497fe18bb3748e04e890c Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:51:09 +0530 Subject: [PATCH] refactor(connector): convert init payment flow to preprocessing flow for nuvei (#4878) --- .../src/router_request_types.rs | 6 + crates/router/src/connector/nuvei.rs | 75 ++---- .../src/connector/nuvei/transformers.rs | 220 ++++++++++++++---- crates/router/src/connector/utils.rs | 11 + crates/router/src/core/payments.rs | 6 + crates/router/src/core/payments/flows.rs | 1 - .../src/core/payments/flows/authorize_flow.rs | 10 + .../router/src/core/payments/transformers.rs | 2 + crates/router/src/types.rs | 2 + 9 files changed, 233 insertions(+), 100 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index f6e7e891ad..66d0da168c 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -241,6 +241,8 @@ pub struct PaymentsPreProcessingData { pub surcharge_details: Option, pub browser_info: Option, pub connector_transaction_id: Option, + pub mandate_id: Option, + pub related_transaction_id: Option, pub redirect_response: Option, } @@ -263,6 +265,8 @@ impl TryFrom for PaymentsPreProcessingData { browser_info: data.browser_info, surcharge_details: data.surcharge_details, connector_transaction_id: None, + mandate_id: data.mandate_id, + related_transaction_id: data.related_transaction_id, redirect_response: None, }) } @@ -287,6 +291,8 @@ impl TryFrom for PaymentsPreProcessingData { browser_info: data.browser_info, surcharge_details: None, connector_transaction_id: data.connector_transaction_id, + mandate_id: data.mandate_id, + related_transaction_id: None, redirect_response: data.redirect_response, }) } diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 455acdc275..316a5e7ab4 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -26,7 +26,6 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, storage::enums, - transformers::ForeignFrom, ErrorResponse, Response, }, utils::ByteSliceExt, @@ -124,6 +123,7 @@ impl api::RefundExecute for Nuvei {} impl api::RefundSync for Nuvei {} impl api::PaymentsCompleteAuthorize for Nuvei {} impl api::ConnectorAccessToken for Nuvei {} +impl api::PaymentsPreProcessing for Nuvei {} impl ConnectorIntegration< @@ -524,53 +524,6 @@ impl ConnectorIntegration CustomResult<(), errors::ConnectorError> { - let (enrolled_for_3ds, related_transaction_id) = - match (router_data.auth_type, router_data.payment_method) { - ( - diesel_models::enums::AuthenticationType::ThreeDs, - diesel_models::enums::PaymentMethod::Card, - ) => { - let integ: Box< - &(dyn ConnectorIntegration< - api::InitPayment, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > + Send - + Sync - + 'static), - > = Box::new(&Self); - let init_data = &types::PaymentsInitRouterData::foreign_from(( - &router_data.to_owned(), - router_data.request.clone(), - )); - let init_resp = services::execute_connector_processing_step( - app_state, - integ, - init_data, - payments::CallConnectorAction::Trigger, - None, - ) - .await?; - match init_resp.response { - Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { - enrolled_v2, - related_transaction_id, - }) => (enrolled_v2, related_transaction_id), - _ => (false, None), - } - } - _ => (false, None), - }; - - router_data.request.enrolled_for_3ds = enrolled_for_3ds; - router_data.request.related_transaction_id = related_transaction_id; - Ok(()) - } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, @@ -725,14 +678,14 @@ impl impl ConnectorIntegration< - api::InitPayment, - types::PaymentsAuthorizeData, + api::PreProcessing, + types::PaymentsPreProcessingData, types::PaymentsResponseData, > for Nuvei { fn get_headers( &self, - req: &types::PaymentsInitRouterData, + req: &types::PaymentsPreProcessingRouterData, connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) @@ -744,7 +697,7 @@ impl fn get_url( &self, - _req: &types::PaymentsInitRouterData, + _req: &types::PaymentsPreProcessingRouterData, connectors: &settings::Connectors, ) -> CustomResult { Ok(format!( @@ -755,7 +708,7 @@ impl fn get_request_body( &self, - req: &types::PaymentsInitRouterData, + req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, ) -> CustomResult { let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; @@ -765,16 +718,20 @@ impl fn build_request( &self, - req: &types::PaymentsInitRouterData, + req: &types::PaymentsPreProcessingRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( services::RequestBuilder::new() .method(services::Method::Post) - .url(&types::PaymentsInitType::get_url(self, req, connectors)?) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) .attach_default_headers() - .headers(types::PaymentsInitType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsInitType::get_request_body( + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( self, req, connectors, )?) .build(), @@ -783,10 +740,10 @@ impl fn handle_response( &self, - data: &types::PaymentsInitRouterData, + data: &types::PaymentsPreProcessingRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index c4c30ac476..2f3b9459ee 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -6,7 +6,7 @@ use common_utils::{ pii::{Email, IpAddress}, }; use error_stack::ResultExt; -use hyperswitch_domain_models::mandates::MandateDataType; +use hyperswitch_domain_models::mandates::{MandateData, MandateDataType}; use masking::{ExposeInterface, PeekInterface, Secret}; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ self, AddressDetailsData, BrowserInformationData, PaymentsAuthorizeRequestData, - PaymentsCancelRequestData, RouterData, + PaymentsCancelRequestData, PaymentsPreProcessingData, RouterData, }, consts, core::errors, @@ -23,6 +23,133 @@ use crate::{ utils::OptionExt, }; +trait NuveiAuthorizePreprocessingCommon { + fn get_browser_info(&self) -> Option; + fn get_related_transaction_id(&self) -> Option; + fn get_email_required(&self) -> Result>; + fn get_setup_mandate_details(&self) -> Option; + fn get_complete_authorize_url(&self) -> Option; + fn get_connector_mandate_id(&self) -> Option; + fn get_return_url_required( + &self, + ) -> Result>; + fn get_capture_method(&self) -> Option; + fn get_amount_required(&self) -> Result>; + fn get_currency_required( + &self, + ) -> Result>; + fn get_payment_method_data_required( + &self, + ) -> Result>; +} + +impl NuveiAuthorizePreprocessingCommon for types::PaymentsAuthorizeData { + fn get_browser_info(&self) -> Option { + self.browser_info.clone() + } + + fn get_related_transaction_id(&self) -> Option { + self.related_transaction_id.clone() + } + + fn get_email_required(&self) -> Result> { + self.get_email() + } + + fn get_setup_mandate_details(&self) -> Option { + self.setup_mandate_details.clone() + } + + fn get_complete_authorize_url(&self) -> Option { + self.complete_authorize_url.clone() + } + + fn get_connector_mandate_id(&self) -> Option { + self.connector_mandate_id().clone() + } + + fn get_return_url_required( + &self, + ) -> Result> { + self.get_return_url() + } + + fn get_capture_method(&self) -> Option { + self.capture_method + } + + fn get_amount_required(&self) -> Result> { + Ok(self.amount) + } + + fn get_currency_required( + &self, + ) -> Result> { + Ok(self.currency) + } + fn get_payment_method_data_required( + &self, + ) -> Result> { + Ok(self.payment_method_data.clone()) + } +} + +impl NuveiAuthorizePreprocessingCommon for types::PaymentsPreProcessingData { + fn get_browser_info(&self) -> Option { + self.browser_info.clone() + } + + fn get_related_transaction_id(&self) -> Option { + self.related_transaction_id.clone() + } + + fn get_email_required(&self) -> Result> { + self.get_email() + } + + fn get_setup_mandate_details(&self) -> Option { + self.setup_mandate_details.clone() + } + + fn get_complete_authorize_url(&self) -> Option { + self.complete_authorize_url.clone() + } + + fn get_connector_mandate_id(&self) -> Option { + self.connector_mandate_id() + } + + fn get_return_url_required( + &self, + ) -> Result> { + self.get_return_url() + } + + fn get_capture_method(&self) -> Option { + self.capture_method + } + + fn get_amount_required(&self) -> Result> { + self.get_amount() + } + + fn get_currency_required( + &self, + ) -> Result> { + self.get_currency() + } + fn get_payment_method_data_required( + &self, + ) -> Result> { + self.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_data", + } + .into(), + ) + } +} + #[derive(Debug, Serialize, Default, Deserialize)] pub struct NuveiMeta { pub session_token: Secret, @@ -628,26 +755,28 @@ impl TryFrom for NuveiBIC { } } -impl +impl ForeignTryFrom<( AlternativePaymentMethodType, Option, - &types::RouterData, + &types::RouterData, )> for NuveiPaymentsRequest +where + Req: NuveiAuthorizePreprocessingCommon, { type Error = error_stack::Report; fn foreign_try_from( data: ( AlternativePaymentMethodType, Option, - &types::RouterData, + &types::RouterData, ), ) -> Result { let (payment_method, redirect, item) = data; let (billing_address, bank_id) = match (&payment_method, redirect) { (AlternativePaymentMethodType::Expresscheckout, _) => ( Some(BillingAddress { - email: item.request.get_email()?, + email: item.request.get_email_required()?, country: item.get_billing_country()?, ..Default::default() }), @@ -655,7 +784,7 @@ impl ), (AlternativePaymentMethodType::Giropay, _) => ( Some(BillingAddress { - email: item.request.get_email()?, + email: item.request.get_email_required()?, country: item.get_billing_country()?, ..Default::default() }), @@ -668,7 +797,7 @@ impl Some(BillingAddress { first_name: Some(first_name.clone()), last_name: Some(address.get_last_name().unwrap_or(first_name).clone()), - email: item.request.get_email()?, + email: item.request.get_email_required()?, country: item.get_billing_country()?, }), None, @@ -686,7 +815,7 @@ impl last_name: Some( address.get_last_name().ok().unwrap_or(&first_name).clone(), ), - email: item.request.get_email()?, + email: item.request.get_email_required()?, country: item.get_billing_country()?, }), bank_name.map(NuveiBIC::try_from).transpose()?, @@ -710,10 +839,13 @@ impl } } -fn get_pay_later_info( +fn get_pay_later_info( payment_method_type: AlternativePaymentMethodType, - item: &types::RouterData, -) -> Result> { + item: &types::RouterData, +) -> Result> +where + Req: NuveiAuthorizePreprocessingCommon, +{ let address = item .get_billing()? .address @@ -728,7 +860,7 @@ fn get_pay_later_info( ..Default::default() }), billing_address: Some(BillingAddress { - email: item.request.get_email()?, + email: item.request.get_email_required()?, first_name: Some(first_name.clone()), last_name: Some(address.get_last_name().unwrap_or(first_name).clone()), country: address.get_country()?.to_owned(), @@ -739,21 +871,23 @@ fn get_pay_later_info( }) } -impl +impl TryFrom<( - &types::RouterData, + &types::RouterData, String, )> for NuveiPaymentsRequest +where + Req: NuveiAuthorizePreprocessingCommon, { type Error = error_stack::Report; fn try_from( data: ( - &types::RouterData, + &types::RouterData, String, ), ) -> Result { let item = data.0; - let request_data = match item.request.payment_method_data.clone() { + let request_data = match item.request.get_payment_method_data_required()?.clone() { domain::PaymentMethodData::Card(card) => get_card_info(item, &card), domain::PaymentMethodData::MandatePayment => Self::try_from(item), domain::PaymentMethodData::Wallet(wallet) => match wallet { @@ -866,16 +1000,17 @@ impl .into()) } }?; + let currency = item.request.get_currency_required()?; let request = Self::try_from(NuveiPaymentRequestData { - amount: utils::to_currency_base_unit(item.request.amount, item.request.currency)?, - currency: item.request.currency, + amount: utils::to_currency_base_unit(item.request.get_amount_required()?, currency)?, + currency, connector_auth_type: item.connector_auth_type.clone(), client_request_id: item.connector_request_reference_id.clone(), session_token: Secret::new(data.1), - capture_method: item.request.capture_method, + capture_method: item.request.get_capture_method(), ..Default::default() })?; - let return_url = item.request.get_return_url()?; + let return_url = item.request.get_return_url_required()?; Ok(Self { is_rebilling: request_data.is_rebilling, user_token_id: request_data.user_token_id, @@ -893,13 +1028,16 @@ impl } } -fn get_card_info( - item: &types::RouterData, +fn get_card_info( + item: &types::RouterData, card_details: &domain::Card, -) -> Result> { - let browser_information = item.request.browser_info.clone(); +) -> Result> +where + Req: NuveiAuthorizePreprocessingCommon, +{ + let browser_information = item.request.get_browser_info().clone(); let related_transaction_id = if item.is_three_ds() { - item.request.related_transaction_id.clone() + item.request.get_related_transaction_id().clone() } else { None }; @@ -914,14 +1052,14 @@ fn get_card_info( Some(BillingAddress { first_name: Some(first_name.clone()), last_name: Some(address.get_last_name().ok().unwrap_or(&first_name).clone()), - email: item.request.get_email()?, + email: item.request.get_email_required()?, country: item.get_billing_country()?, }) } None => None, }; let (is_rebilling, additional_params, user_token_id) = - match item.request.setup_mandate_details.clone() { + match item.request.get_setup_mandate_details().clone() { Some(mandate_data) => { let details = match mandate_data .mandate_type @@ -955,7 +1093,7 @@ fn get_card_info( rebill_frequency: Some(mandate_meta.frequency), challenge_window_size: None, }), - Some(item.request.get_email()?), + Some(item.request.get_email_required()?), ) } _ => (None, None, None), @@ -982,7 +1120,7 @@ fn get_card_info( Some(ThreeD { browser_details, v2_additional_params: additional_params, - notification_url: item.request.complete_authorize_url.clone(), + notification_url: item.request.get_complete_authorize_url().clone(), merchant_url: item.return_url.clone(), platform_type: Some(PlatformType::Browser), method_completion_ind: Some(MethodCompletion::Unavailable), @@ -997,7 +1135,7 @@ fn get_card_info( is_rebilling, user_token_id, device_details: Option::::foreign_try_from( - &item.request.browser_info.clone(), + &item.request.get_browser_info().clone(), )?, payment_option: PaymentOption::from(NuveiCardDetails { card: card_details.clone(), @@ -1479,12 +1617,12 @@ where } } -impl TryFrom> - for types::PaymentsInitRouterData +impl TryFrom> + for types::PaymentsPreProcessingRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsInitResponseRouterData, + item: types::PaymentsPreprocessingResponseRouterData, ) -> Result { let response = item.response; let is_enrolled_for_3ds = response @@ -1558,28 +1696,30 @@ impl TryFrom } } -impl TryFrom<&types::RouterData> +impl TryFrom<&types::RouterData> for NuveiPaymentsRequest +where + Req: NuveiAuthorizePreprocessingCommon, { type Error = error_stack::Report; fn try_from( - data: &types::RouterData, + data: &types::RouterData, ) -> Result { { let item = data; - let connector_mandate_id = &item.request.connector_mandate_id(); + let connector_mandate_id = &item.request.get_connector_mandate_id(); let related_transaction_id = if item.is_three_ds() { - item.request.related_transaction_id.clone() + item.request.get_related_transaction_id().clone() } else { None }; Ok(Self { related_transaction_id, device_details: Option::::foreign_try_from( - &item.request.browser_info.clone(), + &item.request.get_browser_info().clone(), )?, is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1 - user_token_id: Some(item.request.get_email()?), + user_token_id: Some(item.request.get_email_required()?), payment_option: PaymentOption { user_payment_option_id: connector_mandate_id.clone(), ..Default::default() diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 963b04826b..f911ea9555 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -615,6 +615,7 @@ pub trait PaymentsPreProcessingData { fn get_return_url(&self) -> Result; fn get_browser_info(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; + fn connector_mandate_id(&self) -> Option; } impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { @@ -664,6 +665,16 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { .clone() .ok_or_else(missing_field_err("complete_authorize_url")) } + fn connector_mandate_id(&self) -> Option { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.connector_mandate_id.clone() + } + _ => None, + }) + } } pub trait PaymentsCaptureRequestData { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8d827d5865..6d3aced7c3 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1965,6 +1965,12 @@ where ) && router_data.status != common_enums::AttemptStatus::AuthenticationFailed; (router_data, should_continue) + } else if connector.connector_name == router_types::Connector::Nuvei + && router_data.auth_type == common_enums::AuthenticationType::ThreeDs + && !is_operation_complete_authorize(&operation) + { + router_data = router_data.preprocessing_steps(state, connector).await?; + (router_data, should_continue_payment) } else { (router_data, should_continue_payment) } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 64254ae14b..a37f2f8efd 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -978,7 +978,6 @@ default_imp_for_pre_processing_steps!( connector::Netcetera, connector::Nexinets, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payeezy, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 064f6c45aa..4005ae78e6 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -361,6 +361,16 @@ pub async fn authorize_preprocessing_steps( ); if connector.connector_name == api_models::enums::Connector::Airwallex { authorize_router_data.reference_id = resp.reference_id; + } else if connector.connector_name == api_models::enums::Connector::Nuvei { + let (enrolled_for_3ds, related_transaction_id) = match &authorize_router_data.response { + Ok(types::PaymentsResponseData::ThreeDSEnrollmentResponse { + enrolled_v2, + related_transaction_id, + }) => (*enrolled_v2, related_transaction_id.clone()), + _ => (false, None), + }; + authorize_router_data.request.enrolled_for_3ds = enrolled_for_3ds; + authorize_router_data.request.related_transaction_id = related_transaction_id; } Ok(authorize_router_data) } else { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 959724696a..01df5a09b9 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1797,6 +1797,8 @@ impl TryFrom> for types::PaymentsPreProce surcharge_details: payment_data.surcharge_details, connector_transaction_id: payment_data.payment_attempt.connector_transaction_id, redirect_response: None, + mandate_id: payment_data.mandate_id, + related_transaction_id: None, }) } } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index ff2df97f28..b01b6a8461 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -120,6 +120,8 @@ pub type PaymentsInitResponseRouterData = ResponseRouterData; pub type PaymentsCaptureResponseRouterData = ResponseRouterData; +pub type PaymentsPreprocessingResponseRouterData = + ResponseRouterData; pub type TokenizationResponseRouterData = ResponseRouterData< api::PaymentMethodToken, R,