refactor(fix): [Adyen] Fix bug in Adyen (#1375)

Co-authored-by: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
Co-authored-by: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com>
This commit is contained in:
Swangi Kumari
2023-06-16 20:14:24 +05:30
committed by GitHub
parent 6c818ef336
commit d3a69060b4
8 changed files with 134 additions and 303 deletions

View File

@ -5,6 +5,7 @@ use std::fmt::Debug;
use api_models::webhooks::IncomingWebhookEvent; use api_models::webhooks::IncomingWebhookEvent;
use base64::Engine; use base64::Engine;
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use ring::hmac;
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
use storage_models::enums as storage_enums; 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 base64_signature = notif_item.additional_data.hmac_signature;
let signature = consts::BASE64_ENGINE Ok(base64_signature.as_bytes().to_vec())
.decode(base64_signature.as_bytes())
.into_report()
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
Ok(signature)
} }
fn get_webhook_source_verification_message( fn get_webhook_source_verification_message(
@ -835,6 +831,33 @@ impl api::IncomingWebhook for Adyen {
.unwrap_or_default()) .unwrap_or_default())
} }
async fn verify_webhook_source(
&self,
db: &dyn StorageInterface,
request: &api::IncomingWebhookRequestDetails<'_>,
merchant_id: &str,
) -> CustomResult<bool, errors::ConnectorError> {
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( fn get_webhook_object_reference_id(
&self, &self,
request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,

View File

@ -1,6 +1,5 @@
use api_models::{enums, payments, webhooks}; use api_models::{enums, payments, webhooks};
use cards::CardNumber; use cards::CardNumber;
use error_stack::ResultExt;
use masking::PeekInterface; use masking::PeekInterface;
use reqwest::Url; use reqwest::Url;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,7 +7,8 @@ use time::PrimitiveDateTime;
use crate::{ use crate::{
connector::utils::{ connector::utils::{
self, CardData, MandateReferenceData, PaymentsAuthorizeRequestData, RouterData, self, BrowserInformationData, CardData, MandateReferenceData, PaymentsAuthorizeRequestData,
RouterData,
}, },
consts, consts,
core::errors, core::errors,
@ -20,7 +20,6 @@ use crate::{
storage::enums as storage_enums, storage::enums as storage_enums,
transformers::ForeignFrom, transformers::ForeignFrom,
}, },
utils::OptionExt,
}; };
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
@ -847,65 +846,17 @@ fn get_browser_info(
if item.auth_type == storage_enums::AuthenticationType::ThreeDs if item.auth_type == storage_enums::AuthenticationType::ThreeDs
|| item.payment_method == storage_enums::PaymentMethod::BankRedirect || item.payment_method == storage_enums::PaymentMethod::BankRedirect
{ {
item.request let info = item.request.get_browser_info()?;
.browser_info Ok(Some(AdyenBrowserInfo {
.as_ref() accept_header: info.get_accept_header()?,
.map(|info| { language: info.get_language()?,
Ok(AdyenBrowserInfo { screen_height: info.get_screen_height()?,
accept_header: info screen_width: info.get_screen_width()?,
.accept_header color_depth: info.get_color_depth()?,
.clone() user_agent: info.get_user_agent()?,
.get_required_value("accept_header") time_zone_offset: info.get_time_zone()?,
.change_context(errors::ConnectorError::MissingRequiredField { java_enabled: info.get_java_enabled()?,
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 { } else {
Ok(None) Ok(None)
} }
@ -1075,7 +1026,7 @@ impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> {
payment_type: PaymentType::Scheme, payment_type: PaymentType::Scheme,
number: card.card_number.clone(), number: card.card_number.clone(),
expiry_month: card.card_exp_month.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()), cvc: Some(card.card_cvc.clone()),
brand: None, brand: None,
network_payment_reference: None, network_payment_reference: None,
@ -1693,8 +1644,9 @@ pub fn get_adyen_response(
let mandate_reference = response let mandate_reference = response
.additional_data .additional_data
.as_ref() .as_ref()
.map(|data| types::MandateReference { .and_then(|data| data.recurring_detail_reference.to_owned())
connector_mandate_id: data.recurring_detail_reference.to_owned(), .map(|mandate_id| types::MandateReference {
connector_mandate_id: Some(mandate_id),
payment_method_id: None, payment_method_id: None,
}); });
let network_txn_id = response let network_txn_id = response

View File

@ -5,12 +5,11 @@ use masking::Secret;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use crate::{ use crate::{
connector::utils::PaymentsAuthorizeRequestData, connector::utils::{BrowserInformationData, PaymentsAuthorizeRequestData},
consts, consts,
core::errors, core::errors,
services, services,
types::{self, api, storage::enums}, types::{self, api, storage::enums},
utils::OptionExt,
}; };
#[derive(Default, Debug, Serialize, Eq, PartialEq)] #[derive(Default, Debug, Serialize, Eq, PartialEq)]
@ -65,63 +64,15 @@ fn get_browser_info(
.as_ref() .as_ref()
.map(|info| { .map(|info| {
Ok(BamboraBrowserInfo { Ok(BamboraBrowserInfo {
accept_header: info accept_header: info.get_accept_header()?,
.accept_header java_enabled: info.get_java_enabled()?,
.clone() language: info.get_language()?,
.get_required_value("accept_header") screen_height: info.get_screen_height()?,
.change_context(errors::ConnectorError::MissingRequiredField { screen_width: info.get_screen_width()?,
field_name: "accept_header", color_depth: info.get_color_depth()?,
})?, user_agent: info.get_user_agent()?,
java_enabled: info time_zone: info.get_time_zone()?,
.java_enabled javascript_enabled: info.get_java_script_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() .transpose()

View File

@ -1,7 +1,7 @@
use api_models::payments; use api_models::payments;
use common_utils::{ use common_utils::{
crypto::{self, GenerateDigest}, crypto::{self, GenerateDigest},
date_time, fp_utils, date_time, fp_utils, pii,
pii::Email, pii::Email,
}; };
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
connector::utils::{ connector::utils::{
self, AddressDetailsData, MandateData, PaymentsAuthorizeRequestData, self, AddressDetailsData, BrowserInformationData, MandateData,
PaymentsCancelRequestData, RouterData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, RouterData,
}, },
consts, consts,
core::errors, core::errors,
@ -288,7 +288,7 @@ pub enum PlatformType {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BrowserDetails { pub struct BrowserDetails {
pub accept_header: String, pub accept_header: String,
pub ip: Option<std::net::IpAddr>, pub ip: Secret<String, pii::IpAddress>,
pub java_enabled: String, pub java_enabled: String,
pub java_script_enabled: String, pub java_script_enabled: String,
pub language: String, pub language: String,
@ -760,72 +760,19 @@ fn get_card_info<F>(
let three_d = if item.is_three_ds() { let three_d = if item.is_three_ds() {
Some(ThreeD { Some(ThreeD {
browser_details: Some(BrowserDetails { browser_details: Some(BrowserDetails {
accept_header: browser_info accept_header: browser_info.get_accept_header()?,
.accept_header ip: browser_info.get_ip_address()?,
.get_required_value("accept_header") java_enabled: browser_info.get_java_enabled()?.to_string().to_uppercase(),
.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(),
java_script_enabled: browser_info java_script_enabled: browser_info
.java_script_enabled .get_java_script_enabled()?
.get_required_value("java_script_enabled")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "java_script_enabled",
})?
.to_string() .to_string()
.to_uppercase(), .to_uppercase(),
language: browser_info language: browser_info.get_language()?,
.language screen_height: browser_info.get_screen_height()?,
.get_required_value("language") screen_width: browser_info.get_screen_width()?,
.change_context(errors::ConnectorError::MissingRequiredField { color_depth: browser_info.get_color_depth()?,
field_name: "language", user_agent: browser_info.get_user_agent()?,
})?, time_zone: browser_info.get_time_zone()?,
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, v2_additional_params: additional_params,
notification_url: item.request.complete_authorize_url.clone(), notification_url: item.request.complete_authorize_url.clone(),

View File

@ -16,7 +16,6 @@ use crate::{
core::errors, core::errors,
services, services,
types::{self, api, storage::enums, BrowserInformation}, types::{self, api, storage::enums, BrowserInformation},
utils::OptionExt,
}; };
type Error = error_stack::Report<errors::ConnectorError>; type Error = error_stack::Report<errors::ConnectorError>;
@ -239,69 +238,15 @@ fn get_card_request_data(
billing_postcode: params.billing_postcode, billing_postcode: params.billing_postcode,
customer_email: email, customer_email: email,
customer_ip_address, customer_ip_address,
browser_accept_header: browser_info browser_accept_header: browser_info.get_accept_header()?,
.accept_header browser_language: browser_info.get_language()?,
.clone() browser_screen_height: browser_info.get_screen_height()?.to_string(),
.get_required_value("accept_header") browser_screen_width: browser_info.get_screen_width()?.to_string(),
.change_context(errors::ConnectorError::MissingRequiredField { browser_timezone: browser_info.get_time_zone()?.to_string(),
field_name: "accept_header", browser_user_agent: browser_info.get_user_agent()?,
})?, browser_java_enabled: browser_info.get_java_enabled()?.to_string(),
browser_language: browser_info browser_java_script_enabled: browser_info.get_java_script_enabled()?.to_string(),
.language browser_screen_color_depth: browser_info.get_color_depth()?.to_string(),
.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(), browser_challenge_window: "1".to_string(),
payment_action: None, payment_action: None,
payment_type: "Plain".to_string(), payment_type: "Plain".to_string(),

View File

@ -263,6 +263,15 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData {
} }
pub trait BrowserInformationData { pub trait BrowserInformationData {
fn get_accept_header(&self) -> Result<String, Error>;
fn get_language(&self) -> Result<String, Error>;
fn get_screen_height(&self) -> Result<u32, Error>;
fn get_screen_width(&self) -> Result<u32, Error>;
fn get_color_depth(&self) -> Result<u8, Error>;
fn get_user_agent(&self) -> Result<String, Error>;
fn get_time_zone(&self) -> Result<i32, Error>;
fn get_java_enabled(&self) -> Result<bool, Error>;
fn get_java_script_enabled(&self) -> Result<bool, Error>;
fn get_ip_address(&self) -> Result<Secret<String, IpAddress>, Error>; fn get_ip_address(&self) -> Result<Secret<String, IpAddress>, Error>;
} }
@ -273,6 +282,45 @@ impl BrowserInformationData for types::BrowserInformation {
.ok_or_else(missing_field_err("browser_info.ip_address"))?; .ok_or_else(missing_field_err("browser_info.ip_address"))?;
Ok(Secret::new(ip_address.to_string())) Ok(Secret::new(ip_address.to_string()))
} }
fn get_accept_header(&self) -> Result<String, Error> {
self.accept_header
.clone()
.ok_or_else(missing_field_err("browser_info.accept_header"))
}
fn get_language(&self) -> Result<String, Error> {
self.language
.clone()
.ok_or_else(missing_field_err("browser_info.language"))
}
fn get_screen_height(&self) -> Result<u32, Error> {
self.screen_height
.ok_or_else(missing_field_err("browser_info.screen_height"))
}
fn get_screen_width(&self) -> Result<u32, Error> {
self.screen_width
.ok_or_else(missing_field_err("browser_info.screen_width"))
}
fn get_color_depth(&self) -> Result<u8, Error> {
self.color_depth
.ok_or_else(missing_field_err("browser_info.color_depth"))
}
fn get_user_agent(&self) -> Result<String, Error> {
self.user_agent
.clone()
.ok_or_else(missing_field_err("browser_info.user_agent"))
}
fn get_time_zone(&self) -> Result<i32, Error> {
self.time_zone
.ok_or_else(missing_field_err("browser_info.time_zone"))
}
fn get_java_enabled(&self) -> Result<bool, Error> {
self.java_enabled
.ok_or_else(missing_field_err("browser_info.java_enabled"))
}
fn get_java_script_enabled(&self) -> Result<bool, Error> {
self.java_script_enabled
.ok_or_else(missing_field_err("browser_info.java_script_enabled"))
}
} }
pub trait PaymentsCompleteAuthorizeRequestData { pub trait PaymentsCompleteAuthorizeRequestData {

View File

@ -433,49 +433,14 @@ fn get_browser_details(
.to_string(); .to_string();
Ok(ZenBrowserDetails { Ok(ZenBrowserDetails {
color_depth: browser_info color_depth: browser_info.get_color_depth()?.to_string(),
.color_depth java_enabled: browser_info.get_java_enabled()?,
.get_required_value("color_depth") lang: browser_info.get_language()?,
.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_height: screen_height.to_string(),
screen_width: screen_width.to_string(), screen_width: screen_width.to_string(),
timezone: browser_info timezone: browser_info.get_time_zone()?.to_string(),
.time_zone accept_header: browser_info.get_accept_header()?,
.get_required_value("time_zone") user_agent: browser_info.get_user_agent()?,
.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, window_size,
}) })
} }

View File

@ -681,7 +681,7 @@ pub async fn webhooks_core<W: api::OutgoingWebhookType>(
.await .await
.switch() .switch()
.attach_printable("There was an issue in incoming webhook source verification")?; .attach_printable("There was an issue in incoming webhook source verification")?;
logger::info!(source_verified=?source_verified);
let object_ref_id = connector let object_ref_id = connector
.get_webhook_object_reference_id(&request_details) .get_webhook_object_reference_id(&request_details)
.switch() .switch()