From aa001b4579a6be022b46eb0cc3e65c52ec9d10bb Mon Sep 17 00:00:00 2001 From: cb-alfredjoseph <97145230+cb-alfredjoseph@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:21:38 +0530 Subject: [PATCH] fix(router): [nuvei] Nuvei recurring MIT fix and mandatory details fix (#3602) Co-authored-by: Arjun Karthik --- .../src/connector/nuvei/transformers.rs | 238 +++++++++++------- crates/router/src/connector/utils.rs | 9 + 2 files changed, 154 insertions(+), 93 deletions(-) diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index 13d522b86c..eef33616ce 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -20,7 +20,7 @@ use crate::{ consts, core::errors, services, - types::{self, api, storage::enums, transformers::ForeignTryFrom}, + types::{self, api, storage::enums, transformers::ForeignTryFrom, BrowserInformation}, utils::OptionExt, }; @@ -75,6 +75,7 @@ pub struct NuveiPaymentsRequest { pub transaction_type: TransactionType, pub is_rebilling: Option, pub payment_option: PaymentOption, + pub device_details: Option, pub checksum: String, pub billing_address: Option, pub related_transaction_id: Option, @@ -135,7 +136,6 @@ pub struct PaymentOption { pub card: Option, pub redirect_url: Option, pub user_payment_option_id: Option, - pub device_details: Option, pub alternative_payment_method: Option, pub billing_address: Option, } @@ -315,7 +315,7 @@ pub struct V2AdditionalParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DeviceDetails { - pub ip_address: String, + pub ip_address: Secret, } impl From for TransactionType { @@ -749,6 +749,7 @@ impl let item = data.0; let request_data = match item.request.payment_method_data.clone() { api::PaymentMethodData::Card(card) => get_card_info(item, &card), + api::PaymentMethodData::MandatePayment => Self::try_from(item), api::PaymentMethodData::Wallet(wallet) => match wallet { payments::WalletData::GooglePay(gpay_data) => Self::try_from(gpay_data), payments::WalletData::ApplePay(apple_pay_data) => Ok(Self::from(apple_pay_data)), @@ -848,7 +849,6 @@ impl payments::PaymentMethodData::BankDebit(_) | payments::PaymentMethodData::BankTransfer(_) | payments::PaymentMethodData::Crypto(_) - | payments::PaymentMethodData::MandatePayment | payments::PaymentMethodData::Reward | payments::PaymentMethodData::Upi(_) | payments::PaymentMethodData::Voucher(_) @@ -877,6 +877,7 @@ impl related_transaction_id: request_data.related_transaction_id, payment_option: request_data.payment_option, billing_address: request_data.billing_address, + device_details: request_data.device_details, url_details: Some(UrlDetails { success_url: return_url.clone(), failure_url: return_url.clone(), @@ -891,104 +892,110 @@ fn get_card_info( item: &types::RouterData, card_details: &payments::Card, ) -> Result> { - let browser_info = item.request.get_browser_info()?; + let browser_information = item.request.browser_info.clone(); let related_transaction_id = if item.is_three_ds() { item.request.related_transaction_id.clone() } else { None }; - let connector_mandate_id = &item.request.connector_mandate_id(); - if connector_mandate_id.is_some() { - Ok(NuveiPaymentsRequest { - related_transaction_id, - is_rebilling: Some("1".to_string()), // In case of second installment, rebilling should be 1 - user_token_id: Some(item.request.get_email()?), - payment_option: PaymentOption { - user_payment_option_id: connector_mandate_id.clone(), - ..Default::default() - }, + + let address = item.get_billing_address_details_as_optional(); + + let billing_address = match address { + Some(address) => Some(BillingAddress { + first_name: Some(address.get_first_name()?.clone()), + last_name: Some(address.get_last_name()?.clone()), + email: item.request.get_email()?, + country: item.get_billing_country()?, + }), + None => None, + }; + let (is_rebilling, additional_params, user_token_id) = + match item.request.setup_mandate_details.clone() { + Some(mandate_data) => { + let details = match mandate_data + .mandate_type + .get_required_value("mandate_type") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "mandate_type", + })? { + MandateDataType::SingleUse(details) => details, + MandateDataType::MultiUse(details) => { + details.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "mandate_data.mandate_type.multi_use", + })? + } + }; + let mandate_meta: NuveiMandateMeta = utils::to_connector_meta_from_secret(Some( + details.get_metadata().ok_or_else(utils::missing_field_err( + "mandate_data.mandate_type.{multi_use|single_use}.metadata", + ))?, + ))?; + ( + Some("0".to_string()), // In case of first installment, rebilling should be 0 + Some(V2AdditionalParams { + rebill_expiry: Some( + details + .get_end_date(date_time::DateFormat::YYYYMMDD) + .change_context(errors::ConnectorError::DateFormattingFailed)? + .ok_or_else(utils::missing_field_err( + "mandate_data.mandate_type.{multi_use|single_use}.end_date", + ))?, + ), + rebill_frequency: Some(mandate_meta.frequency), + challenge_window_size: None, + }), + Some(item.request.get_email()?), + ) + } + _ => (None, None, None), + }; + let three_d = if item.is_three_ds() { + let browser_details = match &browser_information { + Some(browser_info) => Some(BrowserDetails { + accept_header: browser_info.get_accept_header()?, + ip: browser_info.get_ip_address()?, + java_enabled: browser_info.get_java_enabled()?.to_string().to_uppercase(), + java_script_enabled: browser_info + .get_java_script_enabled()? + .to_string() + .to_uppercase(), + language: browser_info.get_language()?, + screen_height: browser_info.get_screen_height()?, + screen_width: browser_info.get_screen_width()?, + color_depth: browser_info.get_color_depth()?, + user_agent: browser_info.get_user_agent()?, + time_zone: browser_info.get_time_zone()?, + }), + None => None, + }; + Some(ThreeD { + browser_details, + v2_additional_params: additional_params, + notification_url: item.request.complete_authorize_url.clone(), + merchant_url: item.return_url.clone(), + platform_type: Some(PlatformType::Browser), + method_completion_ind: Some(MethodCompletion::Unavailable), ..Default::default() }) } else { - let (is_rebilling, additional_params, user_token_id) = - match item.request.setup_mandate_details.clone() { - Some(mandate_data) => { - let details = match mandate_data - .mandate_type - .get_required_value("mandate_type") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "mandate_type", - })? { - MandateDataType::SingleUse(details) => details, - MandateDataType::MultiUse(details) => { - details.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "mandate_data.mandate_type.multi_use", - })? - } - }; - let mandate_meta: NuveiMandateMeta = utils::to_connector_meta_from_secret( - Some(details.get_metadata().ok_or_else(utils::missing_field_err( - "mandate_data.mandate_type.{multi_use|single_use}.metadata", - ))?), - )?; - ( - Some("0".to_string()), // In case of first installment, rebilling should be 0 - Some(V2AdditionalParams { - rebill_expiry: Some( - details - .get_end_date(date_time::DateFormat::YYYYMMDD) - .change_context(errors::ConnectorError::DateFormattingFailed)? - .ok_or_else(utils::missing_field_err( - "mandate_data.mandate_type.{multi_use|single_use}.end_date", - ))?, - ), - rebill_frequency: Some(mandate_meta.frequency), - challenge_window_size: None, - }), - Some(item.request.get_email()?), - ) - } - _ => (None, None, None), - }; - let three_d = if item.is_three_ds() { - Some(ThreeD { - browser_details: Some(BrowserDetails { - accept_header: browser_info.get_accept_header()?, - ip: browser_info.get_ip_address()?, - java_enabled: browser_info.get_java_enabled()?.to_string().to_uppercase(), - java_script_enabled: browser_info - .get_java_script_enabled()? - .to_string() - .to_uppercase(), - language: browser_info.get_language()?, - screen_height: browser_info.get_screen_height()?, - screen_width: browser_info.get_screen_width()?, - color_depth: browser_info.get_color_depth()?, - user_agent: browser_info.get_user_agent()?, - time_zone: browser_info.get_time_zone()?, - }), - v2_additional_params: additional_params, - notification_url: item.request.complete_authorize_url.clone(), - merchant_url: item.return_url.clone(), - platform_type: Some(PlatformType::Browser), - method_completion_ind: Some(MethodCompletion::Unavailable), - ..Default::default() - }) - } else { - None - }; + None + }; - Ok(NuveiPaymentsRequest { - related_transaction_id, - is_rebilling, - user_token_id, - payment_option: PaymentOption::from(NuveiCardDetails { - card: card_details.clone(), - three_d, - }), - ..Default::default() - }) - } + Ok(NuveiPaymentsRequest { + related_transaction_id, + is_rebilling, + user_token_id, + device_details: Option::::foreign_try_from( + &item.request.browser_info.clone(), + )?, + payment_option: PaymentOption::from(NuveiCardDetails { + card: card_details.clone(), + three_d, + }), + billing_address, + ..Default::default() + }) } impl From for PaymentOption { fn from(card_details: NuveiCardDetails) -> Self { @@ -1535,6 +1542,51 @@ impl TryFrom } } +impl TryFrom<&types::RouterData> + for NuveiPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + data: &types::RouterData, + ) -> Result { + { + let item = data; + let connector_mandate_id = &item.request.connector_mandate_id(); + let related_transaction_id = if item.is_three_ds() { + item.request.related_transaction_id.clone() + } else { + None + }; + Ok(Self { + related_transaction_id, + device_details: Option::::foreign_try_from( + &item.request.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()?), + payment_option: PaymentOption { + user_payment_option_id: connector_mandate_id.clone(), + ..Default::default() + }, + ..Default::default() + }) + } + } +} + +impl ForeignTryFrom<&Option> for Option { + type Error = error_stack::Report; + fn foreign_try_from(browser_info: &Option) -> Result { + let device_details = match browser_info { + Some(browser_info) => Some(DeviceDetails { + ip_address: browser_info.get_ip_address()?, + }), + None => None, + }; + Ok(device_details) + } +} + fn get_refund_response( response: NuveiPaymentsResponse, http_code: u16, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index fde9195143..996e06adfc 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -86,6 +86,7 @@ pub trait RouterData { fn get_payout_method_data(&self) -> Result; #[cfg(feature = "payouts")] fn get_quote_id(&self) -> Result; + fn get_billing_address_details_as_optional(&self) -> Option; } pub trait PaymentResponseRouterData { @@ -182,6 +183,14 @@ impl RouterData for types::RouterData Option { + self.address + .billing + .as_ref() + .and_then(|a| a.address.as_ref()) + .cloned() + } + fn get_billing_address_with_phone_number(&self) -> Result<&api::Address, Error> { self.address .billing