From 11a827a76d9efb81b70b4439a681eb17de73b94f Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Fri, 9 Jun 2023 12:30:39 +0530 Subject: [PATCH] feat(payment): customer ip field inclusion (#1370) Co-authored-by: shashank_attarde --- .../src/connector/adyen/transformers.rs | 84 ++++++++--- .../src/connector/bambora/transformers.rs | 80 +++++++++-- .../src/connector/nuvei/transformers.rs | 75 ++++++++-- .../src/connector/trustpay/transformers.rs | 135 +++++++++++++----- .../router/src/connector/zen/transformers.rs | 78 ++++++++-- crates/router/src/core/payments.rs | 6 +- crates/router/src/lib.rs | 1 + crates/router/src/routes/payments.rs | 6 + crates/router/src/routes/payments/helpers.rs | 67 +++++++++ crates/router/src/types.rs | 18 +-- crates/router/tests/connectors/trustpay.rs | 18 +-- crates/router/tests/connectors/utils.rs | 18 +-- 12 files changed, 462 insertions(+), 124 deletions(-) create mode 100644 crates/router/src/routes/payments/helpers.rs diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index ffd4a57db5..e57e13167d 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1,5 +1,6 @@ use api_models::{enums, payments, webhooks}; use cards::CardNumber; +use error_stack::ResultExt; use masking::PeekInterface; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -19,6 +20,7 @@ use crate::{ storage::enums as storage_enums, transformers::ForeignFrom, }, + utils::OptionExt, }; type Error = error_stack::Report; @@ -839,25 +841,73 @@ fn get_recurring_processing_model( } } -fn get_browser_info(item: &types::PaymentsAuthorizeRouterData) -> Option { +fn get_browser_info( + item: &types::PaymentsAuthorizeRouterData, +) -> Result, Error> { if item.auth_type == storage_enums::AuthenticationType::ThreeDs || item.payment_method == storage_enums::PaymentMethod::BankRedirect { item.request .browser_info .as_ref() - .map(|info| AdyenBrowserInfo { - accept_header: info.accept_header.clone(), - language: info.language.clone(), - screen_height: info.screen_height, - screen_width: info.screen_width, - color_depth: info.color_depth, - user_agent: info.user_agent.clone(), - time_zone_offset: info.time_zone, - java_enabled: info.java_enabled, + .map(|info| { + Ok(AdyenBrowserInfo { + accept_header: info + .accept_header + .clone() + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?, + language: info + .language + .clone() + .get_required_value("language") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "language", + })?, + screen_height: info + .screen_height + .get_required_value("screen_height") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_height", + })?, + screen_width: info + .screen_width + .get_required_value("screen_width") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_width", + })?, + color_depth: info + .color_depth + .get_required_value("color_depth") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "color_depth", + })?, + user_agent: info + .user_agent + .clone() + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?, + time_zone_offset: info + .time_zone + .get_required_value("time_zone_offset") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "time_zone_offset", + })?, + java_enabled: info + .java_enabled + .get_required_value("java_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_enabled", + })?, + }) }) + .transpose() } else { - None + Ok(None) } } @@ -1246,7 +1296,7 @@ impl<'a> let shopper_interaction = AdyenShopperInteraction::from(item); let (recurring_processing_model, store_payment_method, shopper_reference) = get_recurring_processing_model(item)?; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; let payment_method = match mandate_ref_id { @@ -1318,7 +1368,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay let shopper_interaction = AdyenShopperInteraction::from(item); let (recurring_processing_model, store_payment_method, shopper_reference) = get_recurring_processing_model(item)?; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; let payment_method = AdyenPaymentMethod::try_from(card_data)?; @@ -1365,7 +1415,7 @@ impl<'a> let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; let shopper_interaction = AdyenShopperInteraction::from(item); let recurring_processing_model = get_recurring_processing_model(item)?.0; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; let payment_method = AdyenPaymentMethod::try_from(bank_debit_data)?; @@ -1414,7 +1464,7 @@ impl<'a> let shopper_interaction = AdyenShopperInteraction::from(item); let (recurring_processing_model, store_payment_method, shopper_reference) = get_recurring_processing_model(item)?; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let return_url = item.request.get_return_url()?; let payment_method = AdyenPaymentMethod::try_from(bank_redirect_data)?; @@ -1478,7 +1528,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)> let (item, wallet_data) = value; let amount = get_amount_data(item); let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let payment_method = AdyenPaymentMethod::try_from(wallet_data)?; let shopper_interaction = AdyenShopperInteraction::from(item); @@ -1519,7 +1569,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)> let (item, paylater_data) = value; let amount = get_amount_data(item); let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; - let browser_info = get_browser_info(item); + let browser_info = get_browser_info(item)?; let additional_data = get_additional_data(item); let payment_method = AdyenPaymentMethod::try_from(paylater_data)?; let shopper_interaction = AdyenShopperInteraction::from(item); diff --git a/crates/router/src/connector/bambora/transformers.rs b/crates/router/src/connector/bambora/transformers.rs index e3b02b995b..7417d0459d 100644 --- a/crates/router/src/connector/bambora/transformers.rs +++ b/crates/router/src/connector/bambora/transformers.rs @@ -10,6 +10,7 @@ use crate::{ core::errors, services, types::{self, api, storage::enums}, + utils::OptionExt, }; #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -55,24 +56,77 @@ pub struct BamboraPaymentsRequest { card: BamboraCard, } -fn get_browser_info(item: &types::PaymentsAuthorizeRouterData) -> Option { +fn get_browser_info( + item: &types::PaymentsAuthorizeRouterData, +) -> Result, error_stack::Report> { if matches!(item.auth_type, enums::AuthenticationType::ThreeDs) { item.request .browser_info .as_ref() - .map(|info| BamboraBrowserInfo { - accept_header: info.accept_header.clone(), - java_enabled: info.java_enabled, - language: info.language.clone(), - color_depth: info.color_depth, - screen_height: info.screen_height, - screen_width: info.screen_width, - time_zone: info.time_zone, - user_agent: info.user_agent.clone(), - javascript_enabled: info.java_script_enabled, + .map(|info| { + Ok(BamboraBrowserInfo { + accept_header: info + .accept_header + .clone() + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?, + java_enabled: info + .java_enabled + .get_required_value("java_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_enabled", + })?, + language: info + .language + .clone() + .get_required_value("language") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "language", + })?, + screen_height: info + .screen_height + .get_required_value("screen_height") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_height", + })?, + screen_width: info + .screen_width + .get_required_value("screen_width") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_width", + })?, + color_depth: info + .color_depth + .get_required_value("color_depth") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "color_depth", + })?, + user_agent: info + .user_agent + .clone() + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?, + time_zone: info + .time_zone + .get_required_value("time_zone") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "time_zone", + })?, + javascript_enabled: info + .java_script_enabled + .get_required_value("javascript_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "javascript_enabled", + })?, + }) }) + .transpose() } else { - None + Ok(None) } } @@ -104,7 +158,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for BamboraPaymentsRequest { let three_ds = match item.auth_type { enums::AuthenticationType::ThreeDs => Some(ThreeDSecure { enabled: true, - browser: get_browser_info(item), + browser: get_browser_info(item)?, version: Some(2), auth_required: Some(true), }), diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index 35ff9833ba..232d7bdaf3 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -760,19 +760,72 @@ fn get_card_info( let three_d = if item.is_three_ds() { Some(ThreeD { browser_details: Some(BrowserDetails { - accept_header: browser_info.accept_header, - ip: browser_info.ip_address, - java_enabled: browser_info.java_enabled.to_string().to_uppercase(), - java_script_enabled: browser_info - .java_script_enabled + accept_header: browser_info + .accept_header + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?, + ip: Some( + browser_info + .ip_address + .get_required_value("ip_address") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "ip_address", + })?, + ), + java_enabled: browser_info + .java_enabled + .get_required_value("java_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_enabled", + })? .to_string() .to_uppercase(), - language: browser_info.language, - color_depth: browser_info.color_depth, - screen_height: browser_info.screen_height, - screen_width: browser_info.screen_width, - time_zone: browser_info.time_zone, - user_agent: browser_info.user_agent, + java_script_enabled: browser_info + .java_script_enabled + .get_required_value("java_script_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_script_enabled", + })? + .to_string() + .to_uppercase(), + language: browser_info + .language + .get_required_value("language") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "language", + })?, + color_depth: browser_info + .color_depth + .get_required_value("color_depth") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "color_depth", + })?, + screen_height: browser_info + .screen_height + .get_required_value("screen_height") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_height", + })?, + screen_width: browser_info + .screen_width + .get_required_value("screen_width") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_width", + })?, + time_zone: browser_info + .time_zone + .get_required_value("time_zone_offset") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "time_zone_offset", + })?, + user_agent: browser_info + .user_agent + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?, }), v2_additional_params: additional_params, notification_url: item.request.complete_authorize_url.clone(), diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 35124b5236..babe63278e 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -15,6 +15,7 @@ use crate::{ core::errors, services, types::{self, api, storage::enums, BrowserInformation}, + utils::OptionExt, }; type Error = error_stack::Report; @@ -218,35 +219,91 @@ fn get_card_request_data( amount: String, ccard: &api_models::payments::Card, return_url: String, -) -> TrustpayPaymentsRequest { - TrustpayPaymentsRequest::CardsPaymentRequest(Box::new(PaymentRequestCards { - amount, - currency: item.request.currency.to_string(), - pan: ccard.card_number.clone(), - cvv: ccard.card_cvc.clone(), - expiry_date: ccard.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned()), - cardholder: ccard.card_holder_name.clone(), - reference: item.attempt_id.clone(), - redirect_url: return_url, - billing_city: params.billing_city, - billing_country: params.billing_country, - billing_street1: params.billing_street1, - billing_postcode: params.billing_postcode, - customer_email: item.request.email.clone(), - customer_ip_address: browser_info.ip_address, - browser_accept_header: browser_info.accept_header.clone(), - browser_language: browser_info.language.clone(), - browser_screen_height: browser_info.screen_height.clone().to_string(), - browser_screen_width: browser_info.screen_width.clone().to_string(), - browser_timezone: browser_info.time_zone.clone().to_string(), - browser_user_agent: browser_info.user_agent.clone(), - browser_java_enabled: browser_info.java_enabled.clone().to_string(), - browser_java_script_enabled: browser_info.java_script_enabled.clone().to_string(), - browser_screen_color_depth: browser_info.color_depth.clone().to_string(), - browser_challenge_window: "1".to_string(), - payment_action: None, - payment_type: "Plain".to_string(), - })) +) -> Result { + Ok(TrustpayPaymentsRequest::CardsPaymentRequest(Box::new( + PaymentRequestCards { + amount, + currency: item.request.currency.to_string(), + pan: ccard.card_number.clone(), + cvv: ccard.card_cvc.clone(), + expiry_date: ccard.get_card_expiry_month_year_2_digit_with_delimiter("/".to_owned()), + cardholder: ccard.card_holder_name.clone(), + reference: item.attempt_id.clone(), + redirect_url: return_url, + billing_city: params.billing_city, + billing_country: params.billing_country, + billing_street1: params.billing_street1, + billing_postcode: params.billing_postcode, + customer_email: item.request.email.clone(), + customer_ip_address: browser_info.ip_address, + browser_accept_header: browser_info + .accept_header + .clone() + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?, + browser_language: browser_info + .language + .clone() + .get_required_value("language") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "language", + })?, + browser_screen_height: browser_info + .screen_height + .get_required_value("screen_height") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_height", + })? + .to_string(), + browser_screen_width: browser_info + .screen_width + .get_required_value("screen_width") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_width", + })? + .to_string(), + browser_timezone: browser_info + .time_zone + .get_required_value("time_zone_offset") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "time_zone_offset", + })? + .to_string(), + browser_user_agent: browser_info + .user_agent + .clone() + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?, + browser_java_enabled: browser_info + .java_enabled + .get_required_value("java_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_enabled", + })? + .to_string(), + browser_java_script_enabled: browser_info + .java_script_enabled + .get_required_value("java_script_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_script_enabled", + })? + .to_string(), + browser_screen_color_depth: browser_info + .color_depth + .get_required_value("color_depth") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "color_depth", + })? + .to_string(), + browser_challenge_window: "1".to_string(), + payment_action: None, + payment_type: "Plain".to_string(), + }, + ))) } fn get_bank_redirection_request_data( @@ -284,15 +341,15 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TrustpayPaymentsRequest { type Error = Error; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { let default_browser_info = BrowserInformation { - color_depth: 24, - java_enabled: false, - java_script_enabled: true, - language: "en-US".to_string(), - screen_height: 1080, - screen_width: 1920, - time_zone: 3600, - accept_header: "*".to_string(), - user_agent: "none".to_string(), + color_depth: Some(24), + java_enabled: Some(false), + java_script_enabled: Some(true), + language: Some("en-US".to_string()), + screen_height: Some(1080), + screen_width: Some(1920), + time_zone: Some(3600), + accept_header: Some("*".to_string()), + user_agent: Some("none".to_string()), ip_address: None, }; let browser_info = item @@ -318,7 +375,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TrustpayPaymentsRequest { amount, ccard, item.request.get_return_url()?, - )), + )?), api::PaymentMethodData::BankRedirect(ref bank_redirection_data) => { get_bank_redirection_request_data(item, bank_redirection_data, amount, auth) } diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index 9a8055cab8..fb986efa5c 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -13,11 +13,11 @@ use crate::{ connector::utils::{ self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData, }, - core::errors, + core::errors::{self, CustomResult}, services::{self, Method}, - types::{self, api, storage::enums, transformers::ForeignTryFrom, BrowserInformation}, + types::{self, api, storage::enums, transformers::ForeignTryFrom}, + utils::OptionExt, }; - // Auth Struct pub struct ZenAuthType { pub(super) api_key: String, @@ -345,9 +345,23 @@ fn get_item_object( } fn get_browser_details( - browser_info: &BrowserInformation, -) -> Result> { - let window_size = match (browser_info.screen_height, browser_info.screen_width) { + browser_info: &types::BrowserInformation, +) -> CustomResult { + let screen_height = browser_info + .screen_height + .get_required_value("screen_height") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_height", + })?; + + let screen_width = browser_info + .screen_width + .get_required_value("screen_width") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "screen_width", + })?; + + let window_size = match (screen_height, screen_width) { (250, 400) => "01", (390, 400) => "02", (500, 600) => "03", @@ -355,16 +369,52 @@ fn get_browser_details( _ => "05", } .to_string(); + Ok(ZenBrowserDetails { - color_depth: browser_info.color_depth.to_string(), - java_enabled: browser_info.java_enabled, - lang: browser_info.language.clone(), - screen_height: browser_info.screen_height.to_string(), - screen_width: browser_info.screen_width.to_string(), - timezone: browser_info.time_zone.to_string(), - accept_header: browser_info.accept_header.clone(), + color_depth: browser_info + .color_depth + .get_required_value("color_depth") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "color_depth", + })? + .to_string(), + java_enabled: browser_info + .java_enabled + .get_required_value("java_enabled") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "java_enabled", + })?, + lang: browser_info + .language + .clone() + .get_required_value("language") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "language", + })?, + screen_height: screen_height.to_string(), + screen_width: screen_width.to_string(), + timezone: browser_info + .time_zone + .get_required_value("time_zone") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "time_zone", + })? + .to_string(), + accept_header: browser_info + .accept_header + .clone() + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?, + user_agent: browser_info + .user_agent + .clone() + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?, window_size, - user_agent: browser_info.user_agent.clone(), }) } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c746be71c5..5346992506 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -9,7 +9,7 @@ pub mod transformers; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant}; use api_models::payments::Metadata; -use common_utils::pii::Email; +use common_utils::pii; use error_stack::{IntoReport, ResultExt}; use futures::future::join_all; use masking::Secret; @@ -970,7 +970,7 @@ where pub disputes: Vec, pub sessions_token: Vec, pub card_cvc: Option>, - pub email: Option, + pub email: Option, pub creds_identifier: Option, pub pm_token: Option, pub connector_customer_id: Option, @@ -982,7 +982,7 @@ where pub struct CustomerDetails { pub customer_id: Option, pub name: Option>, - pub email: Option, + pub email: Option, pub phone: Option>, pub phone_country_code: Option, } diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 380448bab7..a81a619ceb 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -56,6 +56,7 @@ pub mod headers { pub const TOKEN: &str = "token"; pub const X_API_KEY: &str = "X-API-KEY"; pub const X_API_VERSION: &str = "X-ApiVersion"; + pub const X_FORWARDED_FOR: &str = "X-Forwarded-For"; pub const X_MERCHANT_ID: &str = "X-Merchant-Id"; pub const X_LOGIN: &str = "X-Login"; pub const X_TRANS_KEY: &str = "X-Trans-Key"; diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 25724a549b..49935c1488 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1,3 +1,5 @@ +pub mod helpers; + use actix_web::{web, Responder}; use error_stack::report; use router_env::{instrument, tracing, Flow}; @@ -321,6 +323,10 @@ pub async fn payments_confirm( return http_not_implemented(); }; + if let Err(err) = helpers::populate_ip_into_browser_info(&req, &mut payload) { + return api::log_and_return_error_response(err); + } + let payment_id = path.into_inner(); payload.payment_id = Some(payment_types::PaymentIdType::PaymentIntentId(payment_id)); payload.confirm = Some(true); diff --git a/crates/router/src/routes/payments/helpers.rs b/crates/router/src/routes/payments/helpers.rs new file mode 100644 index 0000000000..f5a87eee19 --- /dev/null +++ b/crates/router/src/routes/payments/helpers.rs @@ -0,0 +1,67 @@ +use error_stack::ResultExt; + +use crate::{ + core::errors::{self, RouterResult}, + headers, logger, + types::{self, api::payments as payment_types}, + utils::{Encode, ValueExt}, +}; + +pub fn populate_ip_into_browser_info( + req: &actix_web::HttpRequest, + payload: &mut payment_types::PaymentsRequest, +) -> RouterResult<()> { + let mut browser_info: types::BrowserInformation = payload + .browser_info + .clone() + .map(|v| v.parse_value("BrowserInformation")) + .transpose() + .change_context_lazy(|| errors::ApiErrorResponse::InvalidRequestData { + message: "invalid format for 'browser_info' provided".to_string(), + })? + .unwrap_or(types::BrowserInformation { + color_depth: None, + java_enabled: None, + java_script_enabled: None, + language: None, + screen_height: None, + screen_width: None, + time_zone: None, + accept_header: None, + user_agent: None, + ip_address: None, + }); + + browser_info.ip_address = browser_info + .ip_address + .or_else(|| { + // Parse the IP Address from the "X-Forwarded-For" header + // This header will contain multiple IP addresses for each ALB hop which has + // a comma separated list of IP addresses: 'X.X.X.X, Y.Y.Y.Y, Z.Z.Z.Z' + // The first one here will be the client IP which we want to retrieve + req.headers() + .get(headers::X_FORWARDED_FOR) + .map(|val| val.to_str()) + .transpose() + .unwrap_or_else(|e| { + logger::error!(error=?e, message="failed to retrieve ip address from X-Forwarded-For header"); + None + }) + .and_then(|ips| ips.split(',').next()) + .map(|ip| ip.parse()) + .transpose() + .unwrap_or_else(|e| { + logger::error!(error=?e, message="failed to parse ip address from X-Forwarded-For"); + None + }) + }); + + let encoded = Encode::::encode_to_value(&browser_info) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "failed to re-encode browser information to json after setting ip address", + )?; + + payload.browser_info = Some(encoded); + Ok(()) +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index cb9647a291..6cbe85f52f 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -444,16 +444,16 @@ pub struct RefundsData { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct BrowserInformation { - pub color_depth: u8, - pub java_enabled: bool, - pub java_script_enabled: bool, - pub language: String, - pub screen_height: u32, - pub screen_width: u32, - pub time_zone: i32, + pub color_depth: Option, + pub java_enabled: Option, + pub java_script_enabled: Option, + pub language: Option, + pub screen_height: Option, + pub screen_width: Option, + pub time_zone: Option, pub ip_address: Option, - pub accept_header: String, - pub user_agent: String, + pub accept_header: Option, + pub user_agent: Option, } #[derive(Debug, Clone)] diff --git a/crates/router/tests/connectors/trustpay.rs b/crates/router/tests/connectors/trustpay.rs index 9d8355d17b..7ce38336da 100644 --- a/crates/router/tests/connectors/trustpay.rs +++ b/crates/router/tests/connectors/trustpay.rs @@ -36,15 +36,15 @@ impl utils::Connector for TrustpayTest { fn get_default_browser_info() -> BrowserInformation { BrowserInformation { - color_depth: 24, - java_enabled: false, - java_script_enabled: true, - language: "en-US".to_string(), - screen_height: 1080, - screen_width: 1920, - time_zone: 3600, - accept_header: "*".to_string(), - user_agent: "none".to_string(), + color_depth: Some(24), + java_enabled: Some(false), + java_script_enabled: Some(true), + language: Some("en-US".to_string()), + screen_height: Some(1080), + screen_width: Some(1920), + time_zone: Some(3600), + accept_header: Some("*".to_string()), + user_agent: Some("none".to_string()), ip_address: None, } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 6f0a39dc41..81b2677c9b 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -549,15 +549,15 @@ impl Default for PaymentCancelType { impl Default for BrowserInfoType { fn default() -> Self { let data = types::BrowserInformation { - user_agent: "".to_string(), - accept_header: "".to_string(), - language: "nl-NL".to_string(), - color_depth: 24, - screen_height: 723, - screen_width: 1536, - time_zone: 0, - java_enabled: true, - java_script_enabled: true, + user_agent: Some("".to_string()), + accept_header: Some("".to_string()), + language: Some("nl-NL".to_string()), + color_depth: Some(24), + screen_height: Some(723), + screen_width: Some(1536), + time_zone: Some(0), + java_enabled: Some(true), + java_script_enabled: Some(true), ip_address: Some("127.0.0.1".parse().unwrap()), }; Self(data)