diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index f4396e1b03..26951824d6 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -5,6 +5,7 @@ use std::fmt::Debug; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; use error_stack::{IntoReport, ResultExt}; +use ring::hmac; use router_env::{instrument, tracing}; use storage_models::enums as storage_enums; @@ -785,12 +786,7 @@ impl api::IncomingWebhook for Adyen { let base64_signature = notif_item.additional_data.hmac_signature; - let signature = consts::BASE64_ENGINE - .decode(base64_signature.as_bytes()) - .into_report() - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - Ok(signature) + Ok(base64_signature.as_bytes().to_vec()) } fn get_webhook_source_verification_message( @@ -835,6 +831,33 @@ impl api::IncomingWebhook for Adyen { .unwrap_or_default()) } + async fn verify_webhook_source( + &self, + db: &dyn StorageInterface, + request: &api::IncomingWebhookRequestDetails<'_>, + merchant_id: &str, + ) -> CustomResult { + let signature = self + .get_webhook_source_verification_signature(request) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + let secret = self + .get_webhook_source_verification_merchant_secret(db, merchant_id) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + let message = self + .get_webhook_source_verification_message(request, merchant_id, &secret) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let raw_key = hex::decode(secret) + .into_report() + .change_context(errors::ConnectorError::WebhookSignatureNotFound)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &raw_key); + let signed_messaged = hmac::sign(&signing_key, &message); + let payload_sign = consts::BASE64_ENGINE.encode(signed_messaged.as_ref()); + Ok(payload_sign.as_bytes().eq(&signature)) + } + fn get_webhook_object_reference_id( &self, request: &api::IncomingWebhookRequestDetails<'_>, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index aeeee648db..432040a2b1 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1,6 +1,5 @@ use api_models::{enums, payments, webhooks}; use cards::CardNumber; -use error_stack::ResultExt; use masking::PeekInterface; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -8,7 +7,8 @@ use time::PrimitiveDateTime; use crate::{ connector::utils::{ - self, CardData, MandateReferenceData, PaymentsAuthorizeRequestData, RouterData, + self, BrowserInformationData, CardData, MandateReferenceData, PaymentsAuthorizeRequestData, + RouterData, }, consts, core::errors, @@ -20,7 +20,6 @@ use crate::{ storage::enums as storage_enums, transformers::ForeignFrom, }, - utils::OptionExt, }; type Error = error_stack::Report; @@ -847,65 +846,17 @@ fn get_browser_info( if item.auth_type == storage_enums::AuthenticationType::ThreeDs || item.payment_method == storage_enums::PaymentMethod::BankRedirect { - item.request - .browser_info - .as_ref() - .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() + let info = item.request.get_browser_info()?; + Ok(Some(AdyenBrowserInfo { + accept_header: info.get_accept_header()?, + language: info.get_language()?, + screen_height: info.get_screen_height()?, + screen_width: info.get_screen_width()?, + color_depth: info.get_color_depth()?, + user_agent: info.get_user_agent()?, + time_zone_offset: info.get_time_zone()?, + java_enabled: info.get_java_enabled()?, + })) } else { Ok(None) } @@ -1075,7 +1026,7 @@ impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> { payment_type: PaymentType::Scheme, number: card.card_number.clone(), expiry_month: card.card_exp_month.clone(), - expiry_year: card.card_exp_year.clone(), + expiry_year: card.get_expiry_year_4_digit(), cvc: Some(card.card_cvc.clone()), brand: None, network_payment_reference: None, @@ -1693,8 +1644,9 @@ pub fn get_adyen_response( let mandate_reference = response .additional_data .as_ref() - .map(|data| types::MandateReference { - connector_mandate_id: data.recurring_detail_reference.to_owned(), + .and_then(|data| data.recurring_detail_reference.to_owned()) + .map(|mandate_id| types::MandateReference { + connector_mandate_id: Some(mandate_id), payment_method_id: None, }); let network_txn_id = response diff --git a/crates/router/src/connector/bambora/transformers.rs b/crates/router/src/connector/bambora/transformers.rs index c689e8ffd0..4e0ca740b9 100644 --- a/crates/router/src/connector/bambora/transformers.rs +++ b/crates/router/src/connector/bambora/transformers.rs @@ -5,12 +5,11 @@ use masking::Secret; use serde::{Deserialize, Deserializer, Serialize}; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{BrowserInformationData, PaymentsAuthorizeRequestData}, consts, core::errors, services, types::{self, api, storage::enums}, - utils::OptionExt, }; #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -65,63 +64,15 @@ fn get_browser_info( .as_ref() .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", - })?, + accept_header: info.get_accept_header()?, + java_enabled: info.get_java_enabled()?, + language: info.get_language()?, + screen_height: info.get_screen_height()?, + screen_width: info.get_screen_width()?, + color_depth: info.get_color_depth()?, + user_agent: info.get_user_agent()?, + time_zone: info.get_time_zone()?, + javascript_enabled: info.get_java_script_enabled()?, }) }) .transpose() diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index 4392d3df98..669e5e13b9 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -1,7 +1,7 @@ use api_models::payments; use common_utils::{ crypto::{self, GenerateDigest}, - date_time, fp_utils, + date_time, fp_utils, pii, pii::Email, }; use error_stack::{IntoReport, ResultExt}; @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ - self, AddressDetailsData, MandateData, PaymentsAuthorizeRequestData, - PaymentsCancelRequestData, RouterData, + self, AddressDetailsData, BrowserInformationData, MandateData, + PaymentsAuthorizeRequestData, PaymentsCancelRequestData, RouterData, }, consts, core::errors, @@ -288,7 +288,7 @@ pub enum PlatformType { #[serde(rename_all = "camelCase")] pub struct BrowserDetails { pub accept_header: String, - pub ip: Option, + pub ip: Secret, pub java_enabled: String, pub java_script_enabled: String, pub language: String, @@ -760,72 +760,19 @@ fn get_card_info( let three_d = if item.is_three_ds() { Some(ThreeD { browser_details: Some(BrowserDetails { - 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(), + 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 - .java_script_enabled - .get_required_value("java_script_enabled") - .change_context(errors::ConnectorError::MissingRequiredField { - field_name: "java_script_enabled", - })? + .get_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", - })?, + 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(), diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index d652ae86fc..ebb53d6683 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -16,7 +16,6 @@ use crate::{ core::errors, services, types::{self, api, storage::enums, BrowserInformation}, - utils::OptionExt, }; type Error = error_stack::Report; @@ -239,69 +238,15 @@ fn get_card_request_data( billing_postcode: params.billing_postcode, customer_email: email, customer_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_accept_header: browser_info.get_accept_header()?, + browser_language: browser_info.get_language()?, + browser_screen_height: browser_info.get_screen_height()?.to_string(), + browser_screen_width: browser_info.get_screen_width()?.to_string(), + browser_timezone: browser_info.get_time_zone()?.to_string(), + browser_user_agent: browser_info.get_user_agent()?, + browser_java_enabled: browser_info.get_java_enabled()?.to_string(), + browser_java_script_enabled: browser_info.get_java_script_enabled()?.to_string(), + browser_screen_color_depth: browser_info.get_color_depth()?.to_string(), browser_challenge_window: "1".to_string(), payment_action: None, payment_type: "Plain".to_string(), diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 48b025d3a9..e03661c53c 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -263,6 +263,15 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { } pub trait BrowserInformationData { + fn get_accept_header(&self) -> Result; + fn get_language(&self) -> Result; + fn get_screen_height(&self) -> Result; + fn get_screen_width(&self) -> Result; + fn get_color_depth(&self) -> Result; + fn get_user_agent(&self) -> Result; + fn get_time_zone(&self) -> Result; + fn get_java_enabled(&self) -> Result; + fn get_java_script_enabled(&self) -> Result; fn get_ip_address(&self) -> Result, Error>; } @@ -273,6 +282,45 @@ impl BrowserInformationData for types::BrowserInformation { .ok_or_else(missing_field_err("browser_info.ip_address"))?; Ok(Secret::new(ip_address.to_string())) } + fn get_accept_header(&self) -> Result { + self.accept_header + .clone() + .ok_or_else(missing_field_err("browser_info.accept_header")) + } + fn get_language(&self) -> Result { + self.language + .clone() + .ok_or_else(missing_field_err("browser_info.language")) + } + fn get_screen_height(&self) -> Result { + self.screen_height + .ok_or_else(missing_field_err("browser_info.screen_height")) + } + fn get_screen_width(&self) -> Result { + self.screen_width + .ok_or_else(missing_field_err("browser_info.screen_width")) + } + fn get_color_depth(&self) -> Result { + self.color_depth + .ok_or_else(missing_field_err("browser_info.color_depth")) + } + fn get_user_agent(&self) -> Result { + self.user_agent + .clone() + .ok_or_else(missing_field_err("browser_info.user_agent")) + } + fn get_time_zone(&self) -> Result { + self.time_zone + .ok_or_else(missing_field_err("browser_info.time_zone")) + } + fn get_java_enabled(&self) -> Result { + self.java_enabled + .ok_or_else(missing_field_err("browser_info.java_enabled")) + } + fn get_java_script_enabled(&self) -> Result { + self.java_script_enabled + .ok_or_else(missing_field_err("browser_info.java_script_enabled")) + } } pub trait PaymentsCompleteAuthorizeRequestData { diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index fa11f67f20..958dbe35ff 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -433,49 +433,14 @@ fn get_browser_details( .to_string(); Ok(ZenBrowserDetails { - 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", - })?, + color_depth: browser_info.get_color_depth()?.to_string(), + java_enabled: browser_info.get_java_enabled()?, + lang: browser_info.get_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", - })?, + timezone: browser_info.get_time_zone()?.to_string(), + accept_header: browser_info.get_accept_header()?, + user_agent: browser_info.get_user_agent()?, window_size, }) } diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index e0c7854cc1..9b04c4e778 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -681,7 +681,7 @@ pub async fn webhooks_core( .await .switch() .attach_printable("There was an issue in incoming webhook source verification")?; - + logger::info!(source_verified=?source_verified); let object_ref_id = connector .get_webhook_object_reference_id(&request_details) .switch()