diff --git a/Cargo.lock b/Cargo.lock index 2c33920066..68792185b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3914,10 +3914,14 @@ dependencies = [ name = "hyperswitch_interfaces" version = "0.1.0" dependencies = [ + "actix-web", + "api_models", "async-trait", "bytes 1.6.0", + "common_enums", "common_utils", "dyn-clone", + "error-stack", "http 0.2.12", "hyperswitch_domain_models", "masking", diff --git a/crates/hyperswitch_domain_models/src/api.rs b/crates/hyperswitch_domain_models/src/api.rs new file mode 100644 index 0000000000..bb768d21dd --- /dev/null +++ b/crates/hyperswitch_domain_models/src/api.rs @@ -0,0 +1,105 @@ +use std::fmt::Display; + +use common_utils::{ + events::{ApiEventMetric, ApiEventsType}, + impl_misc_api_event_type, +}; + +#[derive(Debug, Eq, PartialEq)] +pub enum ApplicationResponse { + Json(R), + StatusOk, + TextPlain(String), + JsonForRedirection(api_models::payments::RedirectionResponse), + Form(Box), + PaymentLinkForm(Box), + FileData((Vec, mime::Mime)), + JsonWithHeaders((R, Vec<(String, masking::Maskable)>)), + GenericLinkForm(Box), +} + +impl ApiEventMetric for ApplicationResponse { + fn get_api_event_type(&self) -> Option { + match self { + Self::Json(r) => r.get_api_event_type(), + Self::JsonWithHeaders((r, _)) => r.get_api_event_type(), + _ => None, + } + } +} + +impl_misc_api_event_type!(PaymentLinkFormData, GenericLinkFormData); + +#[derive(Debug, Eq, PartialEq)] +pub struct RedirectionFormData { + pub redirect_form: crate::router_response_types::RedirectForm, + pub payment_method_data: Option, + pub amount: String, + pub currency: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum PaymentLinkAction { + PaymentLinkFormData(PaymentLinkFormData), + PaymentLinkStatus(PaymentLinkStatusData), +} + +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentLinkFormData { + pub js_script: String, + pub css_script: String, + pub sdk_url: String, + pub html_meta_tags: String, +} + +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentLinkStatusData { + pub js_script: String, + pub css_script: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum GenericLinks { + ExpiredLink(GenericExpiredLinkData), + PaymentMethodCollect(GenericLinkFormData), + PayoutLink(GenericLinkFormData), + PayoutLinkStatus(GenericLinkStatusData), + PaymentMethodCollectStatus(GenericLinkStatusData), +} + +impl Display for GenericLinks { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::ExpiredLink(_) => "ExpiredLink", + Self::PaymentMethodCollect(_) => "PaymentMethodCollect", + Self::PayoutLink(_) => "PayoutLink", + Self::PayoutLinkStatus(_) => "PayoutLinkStatus", + Self::PaymentMethodCollectStatus(_) => "PaymentMethodCollectStatus", + } + ) + } +} + +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct GenericExpiredLinkData { + pub title: String, + pub message: String, + pub theme: String, +} + +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct GenericLinkFormData { + pub js_data: String, + pub css_data: String, + pub sdk_url: String, + pub html_meta_tags: String, +} + +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +pub struct GenericLinkStatusData { + pub js_data: String, + pub css_data: String, +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index fdafe308a4..fa3db72b08 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -1,3 +1,4 @@ +pub mod api; pub mod errors; pub mod mandates; pub mod merchant_account; diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 219b50b47c..3c481caef1 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -12,9 +12,11 @@ dummy_connector = [] payouts = [] [dependencies] +actix-web = "4.5.1" async-trait = "0.1.79" bytes = "1.6.0" dyn-clone = "1.0.17" +error-stack = "0.4.1" http = "0.2.12" mime = "0.3.17" once_cell = "1.19.0" @@ -25,6 +27,8 @@ thiserror = "1.0.58" time = "0.3.35" # First party crates +api_models = { version = "0.1.0", path = "../api_models" } +common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils" } hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } masking = { version = "0.1.0", path = "../masking" } diff --git a/crates/hyperswitch_interfaces/src/authentication.rs b/crates/hyperswitch_interfaces/src/authentication.rs new file mode 100644 index 0000000000..960672e1d1 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/authentication.rs @@ -0,0 +1,12 @@ +//! Authentication interface + +/// struct ExternalAuthenticationPayload +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] +pub struct ExternalAuthenticationPayload { + /// trans_status + pub trans_status: common_enums::TransactionStatus, + /// authentication_value + pub authentication_value: Option, + /// eci + pub eci: Option, +} diff --git a/crates/hyperswitch_interfaces/src/disputes.rs b/crates/hyperswitch_interfaces/src/disputes.rs new file mode 100644 index 0000000000..acc7b56e90 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/disputes.rs @@ -0,0 +1,28 @@ +//! Disputes interface + +use time::PrimitiveDateTime; + +/// struct DisputePayload +#[derive(Default, Debug)] +pub struct DisputePayload { + /// amount + pub amount: String, + /// currency + pub currency: String, + /// dispute_stage + pub dispute_stage: common_enums::enums::DisputeStage, + /// connector_status + pub connector_status: String, + /// connector_dispute_id + pub connector_dispute_id: String, + /// connector_reason + pub connector_reason: Option, + /// connector_reason_code + pub connector_reason_code: Option, + /// challenge_required_by + pub challenge_required_by: Option, + /// created_at + pub created_at: Option, + /// updated_at + pub updated_at: Option, +} diff --git a/crates/hyperswitch_interfaces/src/lib.rs b/crates/hyperswitch_interfaces/src/lib.rs index 7eb35d46a4..2acbe5c7f9 100644 --- a/crates/hyperswitch_interfaces/src/lib.rs +++ b/crates/hyperswitch_interfaces/src/lib.rs @@ -2,10 +2,12 @@ #![warn(missing_docs, missing_debug_implementations)] pub mod api; +pub mod authentication; pub mod configs; /// definition of the new connector integration trait pub mod connector_integration_v2; pub mod consts; +pub mod disputes; pub mod encryption_interface; pub mod errors; pub mod events; @@ -14,3 +16,4 @@ pub mod integrity; pub mod metrics; pub mod secrets_interface; pub mod types; +pub mod webhooks; diff --git a/crates/hyperswitch_interfaces/src/webhooks.rs b/crates/hyperswitch_interfaces/src/webhooks.rs new file mode 100644 index 0000000000..b9148d6701 --- /dev/null +++ b/crates/hyperswitch_interfaces/src/webhooks.rs @@ -0,0 +1,229 @@ +//! Webhooks interface + +use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; +use error_stack::ResultExt; +use hyperswitch_domain_models::api::ApplicationResponse; +use masking::{ExposeInterface, Secret}; + +use crate::{api::ConnectorCommon, errors}; + +/// struct IncomingWebhookRequestDetails +#[derive(Debug)] +pub struct IncomingWebhookRequestDetails<'a> { + /// method + pub method: http::Method, + /// uri + pub uri: http::Uri, + /// headers + pub headers: &'a actix_web::http::header::HeaderMap, + /// body + pub body: &'a [u8], + /// query_params + pub query_params: String, +} + +/// Trait defining incoming webhook +#[async_trait::async_trait] +pub trait IncomingWebhook: ConnectorCommon + Sync { + /// fn get_webhook_body_decoding_algorithm + fn get_webhook_body_decoding_algorithm( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::NoAlgorithm)) + } + + /// fn get_webhook_body_decoding_message + fn get_webhook_body_decoding_message( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + /// fn decode_webhook_body + async fn decode_webhook_body( + &self, + request: &IncomingWebhookRequestDetails<'_>, + merchant_id: &str, + connector_webhook_details: Option, + connector_name: &str, + ) -> CustomResult, errors::ConnectorError> { + let algorithm = self.get_webhook_body_decoding_algorithm(request)?; + + let message = self + .get_webhook_body_decoding_message(request) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let secret = self + .get_webhook_source_verification_merchant_secret( + merchant_id, + connector_name, + connector_webhook_details, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + algorithm + .decode_message(&secret.secret, message.into()) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) + } + + /// fn get_webhook_source_verification_algorithm + fn get_webhook_source_verification_algorithm( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::NoAlgorithm)) + } + + /// fn get_webhook_source_verification_merchant_secret + async fn get_webhook_source_verification_merchant_secret( + &self, + merchant_id: &str, + connector_name: &str, + connector_webhook_details: Option, + ) -> CustomResult { + let debug_suffix = format!( + "For merchant_id: {}, and connector_name: {}", + merchant_id, connector_name + ); + let default_secret = "default_secret".to_string(); + let merchant_secret = match connector_webhook_details { + Some(merchant_connector_webhook_details) => { + let connector_webhook_details = merchant_connector_webhook_details + .parse_value::( + "MerchantConnectorWebhookDetails", + ) + .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable_lazy(|| { + format!( + "Deserializing MerchantConnectorWebhookDetails failed {}", + debug_suffix + ) + })?; + api_models::webhooks::ConnectorWebhookSecrets { + secret: connector_webhook_details + .merchant_secret + .expose() + .into_bytes(), + additional_secret: connector_webhook_details.additional_secret, + } + } + + None => api_models::webhooks::ConnectorWebhookSecrets { + secret: default_secret.into_bytes(), + additional_secret: None, + }, + }; + + //need to fetch merchant secret from config table with caching in future for enhanced performance + + //If merchant has not set the secret for webhook source verification, "default_secret" is returned. + //So it will fail during verification step and goes to psync flow. + Ok(merchant_secret) + } + + /// fn get_webhook_source_verification_signature + fn get_webhook_source_verification_signature( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(Vec::new()) + } + + /// fn get_webhook_source_verification_message + fn get_webhook_source_verification_message( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(Vec::new()) + } + + /// fn verify_webhook_source + async fn verify_webhook_source( + &self, + request: &IncomingWebhookRequestDetails<'_>, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, + connector_name: &str, + ) -> CustomResult { + let algorithm = self + .get_webhook_source_verification_algorithm(request) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_id, + connector_name, + connector_webhook_details, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signature = self + .get_webhook_source_verification_signature(request, &connector_webhook_secrets) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let message = self + .get_webhook_source_verification_message( + request, + merchant_id, + &connector_webhook_secrets, + ) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + algorithm + .verify_signature(&connector_webhook_secrets.secret, &signature, &message) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) + } + + /// fn get_webhook_object_reference_id + fn get_webhook_object_reference_id( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult; + + /// fn get_webhook_event_type + fn get_webhook_event_type( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult; + + /// fn get_webhook_resource_object + fn get_webhook_resource_object( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError>; + + /// fn get_webhook_api_response + fn get_webhook_api_response( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(ApplicationResponse::StatusOk) + } + + /// fn get_dispute_details + fn get_dispute_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into()) + } + + /// fn get_external_authentication_details + fn get_external_authentication_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult + { + Err(errors::ConnectorError::NotImplemented( + "get_external_authentication_details method".to_string(), + ) + .into()) + } +} diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index fd2fd2865a..4fee5ea9bb 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -7,7 +7,7 @@ use base64::Engine; use common_utils::request::RequestContent; use diesel_models::{enums as storage_enums, enums}; use error_stack::{report, ResultExt}; -use masking::ExposeInterface; +use masking::{ExposeInterface, Secret}; use ring::hmac; use router_env::{instrument, tracing}; @@ -1754,15 +1754,16 @@ impl api::IncomingWebhook for Adyen { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -1774,7 +1775,7 @@ impl api::IncomingWebhook for Adyen { let message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 8964c06aaf..5115280b93 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -7,7 +7,7 @@ use base64::Engine; use common_utils::{crypto, ext_traits::XmlExt, request::RequestContent}; use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, Secret}; use ring::hmac; use sha1::{Digest, Sha1}; @@ -31,7 +31,6 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, transformers::ForeignFrom, ErrorResponse, }, @@ -180,7 +179,7 @@ impl ConnectorValidation for Braintree { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: domain::payments::PaymentMethodData, + pm_data: hyperswitch_domain_models::payment_method_data::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) @@ -1377,15 +1376,16 @@ impl api::IncomingWebhook for Braintree { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -1397,7 +1397,7 @@ impl api::IncomingWebhook for Braintree { let message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/cashtocode.rs b/crates/router/src/connector/cashtocode.rs index ec8c60002f..2b06e0de69 100644 --- a/crates/router/src/connector/cashtocode.rs +++ b/crates/router/src/connector/cashtocode.rs @@ -24,7 +24,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, storage, ErrorResponse, Response, + storage, ErrorResponse, Response, }, utils::{ByteSliceExt, BytesExt}, }; @@ -383,15 +383,16 @@ impl api::IncomingWebhook for Cashtocode { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: common_utils::crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/netcetera.rs b/crates/router/src/connector/netcetera.rs index 714873cfb5..edf56365f4 100644 --- a/crates/router/src/connector/netcetera.rs +++ b/crates/router/src/connector/netcetera.rs @@ -5,6 +5,7 @@ use std::fmt::Debug; use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::ResultExt; +use hyperswitch_interfaces::authentication::ExternalAuthenticationPayload; use transformers as netcetera; use crate::{ @@ -203,12 +204,12 @@ impl api::IncomingWebhook for Netcetera { fn get_external_authentication_details( &self, request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + ) -> CustomResult { let webhook_body: netcetera::ResultsResponseData = request .body .parse_struct("netcetera ResultsResponseData") .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - Ok(api::ExternalAuthenticationPayload { + Ok(ExternalAuthenticationPayload { trans_status: webhook_body .trans_status .unwrap_or(common_enums::TransactionStatus::InformationOnly), diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index d7a05cfa30..588ba7e712 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -11,7 +11,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{Report, ResultExt}; -use masking::ExposeInterface; +use masking::{ExposeInterface, Secret}; use transformers as payme; use crate::{ @@ -1160,8 +1160,9 @@ impl api::IncomingWebhook for Payme { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let algorithm = self @@ -1170,9 +1171,9 @@ impl api::IncomingWebhook for Payme { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -1184,7 +1185,7 @@ impl api::IncomingWebhook for Payme { let mut message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/rapyd.rs b/crates/router/src/connector/rapyd.rs index f4bd099762..714f8d5462 100644 --- a/crates/router/src/connector/rapyd.rs +++ b/crates/router/src/connector/rapyd.rs @@ -9,7 +9,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{Report, ResultExt}; -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, Secret}; use rand::distributions::{Alphanumeric, DistString}; use ring::hmac; use transformers as rapyd; @@ -29,7 +29,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon}, - domain, ErrorResponse, + ErrorResponse, }, utils::{self, crypto, ByteSliceExt, BytesExt}, }; @@ -819,15 +819,16 @@ impl api::IncomingWebhook for Rapyd { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -837,7 +838,7 @@ impl api::IncomingWebhook for Rapyd { let message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/razorpay.rs b/crates/router/src/connector/razorpay.rs index 195508dffa..15fac49212 100644 --- a/crates/router/src/connector/razorpay.rs +++ b/crates/router/src/connector/razorpay.rs @@ -7,7 +7,6 @@ use common_utils::{ use error_stack::{Report, ResultExt}; use masking::ExposeInterface; use transformers as razorpay; -use types::domain; use super::utils::{self as connector_utils}; use crate::{ @@ -644,8 +643,11 @@ impl api::IncomingWebhook for Razorpay { async fn verify_webhook_source( &self, _request: &api::IncomingWebhookRequestDetails<'_>, - _merchant_account: &domain::MerchantAccount, - _merchant_connector_account: domain::MerchantConnectorAccount, + _merchant_id: &str, + _connector_webhook_details: Option, + _connector_account_details: common_utils::crypto::Encryptable< + masking::Secret, + >, _connector_label: &str, ) -> CustomResult { Ok(false) diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 28678bbd93..b37e794ec8 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -8,7 +8,7 @@ use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; #[cfg(feature = "frm")] use error_stack::ResultExt; #[cfg(feature = "frm")] -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, Secret}; #[cfg(feature = "frm")] use ring::hmac; #[cfg(feature = "frm")] @@ -31,9 +31,7 @@ use crate::{ events::connector_api_logs::ConnectorEvent, headers, services::request, - types::{ - api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, - }, + types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, utils::BytesExt, }; @@ -572,15 +570,16 @@ impl api::IncomingWebhook for Riskified { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -592,7 +591,7 @@ impl api::IncomingWebhook for Riskified { let message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 9e591a336f..c6b3e01ab5 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -8,7 +8,7 @@ use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; #[cfg(feature = "frm")] use error_stack::ResultExt; #[cfg(feature = "frm")] -use masking::PeekInterface; +use masking::{PeekInterface, Secret}; #[cfg(feature = "frm")] use ring::hmac; #[cfg(feature = "frm")] @@ -30,9 +30,7 @@ use crate::{ use crate::{ consts, events::connector_api_logs::ConnectorEvent, - types::{ - api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, - }, + types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, utils::BytesExt, }; @@ -689,15 +687,16 @@ impl api::IncomingWebhook for Signifyd { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; @@ -709,7 +708,7 @@ impl api::IncomingWebhook for Signifyd { let message = self .get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, ) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; diff --git a/crates/router/src/connector/stax.rs b/crates/router/src/connector/stax.rs index 4604e9da87..d49a9f2b4f 100644 --- a/crates/router/src/connector/stax.rs +++ b/crates/router/src/connector/stax.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::ResultExt; -use masking::PeekInterface; +use masking::{PeekInterface, Secret}; use transformers as stax; use self::stax::StaxWebhookEventType; @@ -24,7 +24,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, ErrorResponse, Response, + ErrorResponse, Response, }, utils::BytesExt, }; @@ -852,8 +852,9 @@ impl api::IncomingWebhook for Stax { async fn verify_webhook_source( &self, _request: &api::IncomingWebhookRequestDetails<'_>, - _merchant_account: &domain::MerchantAccount, - _merchant_connector_account: domain::MerchantConnectorAccount, + _merchant_id: &str, + _connector_webhook_details: Option, + _connector_account_details: common_utils::crypto::Encryptable>, _connector_label: &str, ) -> CustomResult { Ok(false) diff --git a/crates/router/src/connector/zen.rs b/crates/router/src/connector/zen.rs index b28ad2e968..e76216f4dc 100644 --- a/crates/router/src/connector/zen.rs +++ b/crates/router/src/connector/zen.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::ResultExt; -use masking::PeekInterface; +use masking::{PeekInterface, Secret}; use transformers as zen; use uuid::Uuid; @@ -608,16 +608,17 @@ impl api::IncomingWebhook for Zen { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, connector_label: &str, ) -> CustomResult { let algorithm = self.get_webhook_source_verification_algorithm(request)?; let connector_webhook_secrets = self .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_label, - merchant_connector_account, + connector_webhook_details, ) .await?; let signature = @@ -625,7 +626,7 @@ impl api::IncomingWebhook for Zen { let mut message = self.get_webhook_source_verification_message( request, - &merchant_account.merchant_id, + merchant_id, &connector_webhook_secrets, )?; let mut secret = connector_webhook_secrets.secret; diff --git a/crates/router/src/connector/zsl.rs b/crates/router/src/connector/zsl.rs index 5d5a95b0a8..f71a5e86b5 100644 --- a/crates/router/src/connector/zsl.rs +++ b/crates/router/src/connector/zsl.rs @@ -5,7 +5,7 @@ use std::fmt::Debug; use common_utils::ext_traits::ValueExt; use diesel_models::enums; use error_stack::ResultExt; -use masking::ExposeInterface; +use masking::{ExposeInterface, Secret}; use transformers as zsl; use crate::{ @@ -418,12 +418,12 @@ impl api::IncomingWebhook for Zsl { async fn verify_webhook_source( &self, request: &api::IncomingWebhookRequestDetails<'_>, - _merchant_account: &types::domain::MerchantAccount, - merchant_connector_account: types::domain::MerchantConnectorAccount, + _merchant_id: &str, + _connector_webhook_details: Option, + connector_account_details: common_utils::crypto::Encryptable>, _connector_label: &str, ) -> CustomResult { - let connector_account_details = merchant_connector_account - .connector_account_details + let connector_account_details = connector_account_details .parse_value::("ConnectorAuthType") .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed)?; let auth_type = zsl::ZslAuthType::try_from(&connector_account_details)?; diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index b876af45dd..44528e5c68 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -9,6 +9,11 @@ use api_models::{ }; use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_request_types::VerifyWebhookSourceRequestData, + router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, +}; +use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails; use masking::ExposeInterface; use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId}; @@ -19,6 +24,7 @@ use crate::{ api_locking, errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt}, metrics, payments, refunds, utils as core_utils, + webhooks::utils::construct_webhook_router_data, }, db::StorageInterface, events::api_logs::ApiEvent, @@ -32,7 +38,10 @@ use crate::{ ConnectorValidation, }, types::{ - api::{self, mandates::MandateResponseExt, ConnectorCommon, IncomingWebhook}, + api::{ + self, mandates::MandateResponseExt, ConnectorCommon, ConnectorData, GetToken, + IncomingWebhook, + }, domain, storage::{self, enums}, transformers::{ForeignFrom, ForeignInto, ForeignTryFrom}, @@ -129,7 +138,7 @@ async fn incoming_webhooks_core( merchant_account.merchant_id.clone(), )], ); - let mut request_details = api::IncomingWebhookRequestDetails { + let mut request_details = IncomingWebhookRequestDetails { method: req.method().clone(), uri: req.uri().clone(), headers: req.headers(), @@ -150,9 +159,14 @@ async fn incoming_webhooks_core( let decoded_body = connector .decode_webhook_body( - &*state.clone().store, &request_details, &merchant_account.merchant_id, + merchant_connector_account + .clone() + .and_then(|merchant_connector_account| { + merchant_connector_account.connector_webhook_details + }), + connector_name.as_str(), ) .await .switch() @@ -258,30 +272,32 @@ async fn incoming_webhooks_core( .connectors_with_webhook_source_verification_call .contains(&connector_enum) { - connector - .verify_webhook_source_verification_call( - &state, - &merchant_account, - merchant_connector_account.clone(), - &connector_name, - &request_details, - ) - .await - .or_else(|error| match error.current_context() { - errors::ConnectorError::WebhookSourceVerificationFailed => { - logger::error!(?error, "Source Verification Failed"); - Ok(false) - } - _ => Err(error), - }) - .switch() - .attach_printable("There was an issue in incoming webhook source verification")? + verify_webhook_source_verification_call( + connector.clone(), + &state, + &merchant_account, + merchant_connector_account.clone(), + &connector_name, + &request_details, + ) + .await + .or_else(|error| match error.current_context() { + errors::ConnectorError::WebhookSourceVerificationFailed => { + logger::error!(?error, "Source Verification Failed"); + Ok(false) + } + _ => Err(error), + }) + .switch() + .attach_printable("There was an issue in incoming webhook source verification")? } else { connector + .clone() .verify_webhook_source( &request_details, - &merchant_account, - merchant_connector_account.clone(), + &merchant_account.merchant_id, + merchant_connector_account.connector_webhook_details.clone(), + merchant_connector_account.connector_account_details.clone(), connector_name.as_str(), ) .await @@ -970,7 +986,7 @@ async fn external_authentication_incoming_webhook_flow( key_store: domain::MerchantKeyStore, source_verified: bool, event_type: webhooks::IncomingWebhookEvent, - request_details: &api::IncomingWebhookRequestDetails<'_>, + request_details: &IncomingWebhookRequestDetails<'_>, connector: &ConnectorEnum, object_ref_id: api::ObjectReferenceId, business_profile: diesel_models::business_profile::BusinessProfile, @@ -1346,7 +1362,7 @@ async fn disputes_incoming_webhook_flow( webhook_details: api::IncomingWebhookDetails, source_verified: bool, connector: &ConnectorEnum, - request_details: &api::IncomingWebhookRequestDetails<'_>, + request_details: &IncomingWebhookRequestDetails<'_>, event_type: webhooks::IncomingWebhookEvent, ) -> CustomResult { metrics::INCOMING_DISPUTE_WEBHOOK_METRIC.add(&metrics::CONTEXT, 1, &[]); @@ -1534,6 +1550,66 @@ async fn get_payment_id( .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) } +#[inline] +async fn verify_webhook_source_verification_call( + connector: ConnectorEnum, + state: &SessionState, + merchant_account: &domain::MerchantAccount, + merchant_connector_account: domain::MerchantConnectorAccount, + connector_name: &str, + request_details: &IncomingWebhookRequestDetails<'_>, +) -> CustomResult { + let connector_data = ConnectorData::get_connector_by_name( + &state.conf.connectors, + connector_name, + GetToken::Connector, + None, + ) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable("invalid connector name received in payment attempt")?; + let connector_integration: services::BoxedWebhookSourceVerificationConnectorIntegrationInterface< + hyperswitch_domain_models::router_flow_types::VerifyWebhookSource, + VerifyWebhookSourceRequestData, + VerifyWebhookSourceResponseData, + > = connector_data.connector.get_connector_integration(); + let connector_webhook_secrets = connector + .get_webhook_source_verification_merchant_secret( + &merchant_account.merchant_id, + connector_name, + merchant_connector_account.connector_webhook_details.clone(), + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let router_data = construct_webhook_router_data( + connector_name, + merchant_connector_account, + merchant_account, + &connector_webhook_secrets, + request_details, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) + .attach_printable("Failed while constructing webhook router data")?; + + let response = services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await?; + + let verification_result = response + .response + .map(|response| response.verify_webhook_status); + match verification_result { + Ok(VerifyWebhookStatus::SourceVerified) => Ok(true), + _ => Ok(false), + } +} + fn get_connector_by_connector_name( state: &SessionState, connector_name: &str, @@ -1562,10 +1638,10 @@ fn get_connector_by_connector_name( authentication_connector_data.connector_name.to_string(), ) } else { - let connector_data = api::ConnectorData::get_connector_by_name( + let connector_data = ConnectorData::get_connector_by_name( &state.conf.connectors, connector_name, - api::GetToken::Connector, + GetToken::Connector, merchant_connector_id, ) .change_context(errors::ApiErrorResponse::InvalidRequestData { diff --git a/crates/router/src/events/api_logs.rs b/crates/router/src/events/api_logs.rs index 6da8d697ac..7bf66dd095 100644 --- a/crates/router/src/events/api_logs.rs +++ b/crates/router/src/events/api_logs.rs @@ -15,10 +15,7 @@ use crate::routes::dummy_connector::types::{ }; use crate::{ core::payments::PaymentsRedirectResponseData, - services::{ - authentication::AuthenticationType, kafka::KafkaMessage, ApplicationResponse, - GenericLinkFormData, PaymentLinkFormData, - }, + services::{authentication::AuthenticationType, kafka::KafkaMessage}, types::api::{ AttachEvidenceRequest, Config, ConfigUpdate, CreateFileRequest, DisputeId, FileId, PollId, }, @@ -101,22 +98,11 @@ impl KafkaMessage for ApiEvent { } } -impl ApiEventMetric for ApplicationResponse { - fn get_api_event_type(&self) -> Option { - match self { - Self::Json(r) => r.get_api_event_type(), - Self::JsonWithHeaders((r, _)) => r.get_api_event_type(), - _ => None, - } - } -} impl_misc_api_event_type!( Config, CreateFileRequest, FileId, AttachEvidenceRequest, - PaymentLinkFormData, - GenericLinkFormData, ConfigUpdate ); diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 8de3a5ca4d..d65def0c97 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -4,7 +4,7 @@ pub mod request; use std::{ collections::{HashMap, HashSet}, error::Error, - fmt::{Debug, Display}, + fmt::Debug, future::Future, str, sync::Arc, @@ -26,7 +26,14 @@ use common_utils::{ }; use error_stack::{report, Report, ResultExt}; use hyperswitch_domain_models::router_data_v2::flow_common_types as common_types; -pub use hyperswitch_domain_models::router_response_types::RedirectForm; +pub use hyperswitch_domain_models::{ + api::{ + ApplicationResponse, GenericExpiredLinkData, GenericLinkFormData, GenericLinkStatusData, + GenericLinks, PaymentLinkAction, PaymentLinkFormData, PaymentLinkStatusData, + RedirectionFormData, + }, + router_response_types::RedirectForm, +}; pub use hyperswitch_interfaces::{ api::{ BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorIntegrationAny, @@ -713,93 +720,6 @@ async fn handle_response( .await } -#[derive(Debug, Eq, PartialEq)] -pub enum ApplicationResponse { - Json(R), - StatusOk, - TextPlain(String), - JsonForRedirection(api::RedirectionResponse), - Form(Box), - PaymentLinkForm(Box), - FileData((Vec, mime::Mime)), - JsonWithHeaders((R, Vec<(String, Maskable)>)), - GenericLinkForm(Box), -} - -#[derive(Debug, Eq, PartialEq)] -pub enum GenericLinks { - ExpiredLink(GenericExpiredLinkData), - PaymentMethodCollect(GenericLinkFormData), - PayoutLink(GenericLinkFormData), - PayoutLinkStatus(GenericLinkStatusData), - PaymentMethodCollectStatus(GenericLinkStatusData), -} - -impl Display for Box { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match **self { - GenericLinks::ExpiredLink(_) => "ExpiredLink", - GenericLinks::PaymentMethodCollect(_) => "PaymentMethodCollect", - GenericLinks::PayoutLink(_) => "PayoutLink", - GenericLinks::PayoutLinkStatus(_) => "PayoutLinkStatus", - GenericLinks::PaymentMethodCollectStatus(_) => "PaymentMethodCollectStatus", - } - ) - } -} - -#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct GenericLinkFormData { - pub js_data: String, - pub css_data: String, - pub sdk_url: String, - pub html_meta_tags: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct GenericExpiredLinkData { - pub title: String, - pub message: String, - pub theme: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct GenericLinkStatusData { - pub js_data: String, - pub css_data: String, -} - -#[derive(Debug, Eq, PartialEq)] -pub enum PaymentLinkAction { - PaymentLinkFormData(PaymentLinkFormData), - PaymentLinkStatus(PaymentLinkStatusData), -} - -#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct PaymentLinkFormData { - pub js_script: String, - pub css_script: String, - pub sdk_url: String, - pub html_meta_tags: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] -pub struct PaymentLinkStatusData { - pub js_script: String, - pub css_script: String, -} - -#[derive(Debug, Eq, PartialEq)] -pub struct RedirectionFormData { - pub redirect_form: RedirectForm, - pub payment_method_data: Option, - pub amount: String, - pub currency: String, -} - #[derive(Debug, Eq, PartialEq)] pub enum PaymentAction { PSync, diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index 8b6bdf50e1..8af9d0b58c 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -1,13 +1,14 @@ use common_utils::{crypto, errors::CustomResult, request::Request}; use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2}; -use hyperswitch_interfaces::connector_integration_v2::ConnectorIntegrationV2; +use hyperswitch_interfaces::{ + authentication::ExternalAuthenticationPayload, connector_integration_v2::ConnectorIntegrationV2, +}; use super::{BoxedConnectorIntegrationV2, ConnectorValidation}; use crate::{ core::payments, errors, events::connector_api_logs::ConnectorEvent, - routes::app::StorageInterface, services::{ api as services_api, BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration, ConnectorRedirectResponse, PaymentAction, @@ -16,8 +17,8 @@ use crate::{ types::{ self, api::{ - self, disputes, Connector, ConnectorV2, CurrencyUnit, ExternalAuthenticationPayload, - IncomingWebhookEvent, IncomingWebhookRequestDetails, ObjectReferenceId, + self, disputes, Connector, ConnectorV2, CurrencyUnit, IncomingWebhookEvent, + IncomingWebhookRequestDetails, ObjectReferenceId, }, domain, }, @@ -99,25 +100,6 @@ impl api::IncomingWebhook for ConnectorEnum { } } - async fn get_webhook_body_decoding_merchant_secret( - &self, - db: &dyn StorageInterface, - merchant_id: &str, - ) -> CustomResult, errors::ConnectorError> { - match self { - Self::Old(connector) => { - connector - .get_webhook_body_decoding_merchant_secret(db, merchant_id) - .await - } - Self::New(connector) => { - connector - .get_webhook_body_decoding_merchant_secret(db, merchant_id) - .await - } - } - } - fn get_webhook_body_decoding_message( &self, request: &IncomingWebhookRequestDetails<'_>, @@ -130,19 +112,30 @@ impl api::IncomingWebhook for ConnectorEnum { async fn decode_webhook_body( &self, - db: &dyn StorageInterface, request: &IncomingWebhookRequestDetails<'_>, merchant_id: &str, + connector_webhook_details: Option, + connector_name: &str, ) -> CustomResult, errors::ConnectorError> { match self { Self::Old(connector) => { connector - .decode_webhook_body(db, request, merchant_id) + .decode_webhook_body( + request, + merchant_id, + connector_webhook_details, + connector_name, + ) .await } Self::New(connector) => { connector - .decode_webhook_body(db, request, merchant_id) + .decode_webhook_body( + request, + merchant_id, + connector_webhook_details, + connector_name, + ) .await } } @@ -160,26 +153,26 @@ impl api::IncomingWebhook for ConnectorEnum { async fn get_webhook_source_verification_merchant_secret( &self, - merchant_account: &domain::MerchantAccount, + merchant_id: &str, connector_name: &str, - merchant_connector_account: domain::MerchantConnectorAccount, + connector_webhook_details: Option, ) -> CustomResult { match self { Self::Old(connector) => { connector .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_name, - merchant_connector_account, + connector_webhook_details, ) .await } Self::New(connector) => { connector .get_webhook_source_verification_merchant_secret( - merchant_account, + merchant_id, connector_name, - merchant_connector_account, + connector_webhook_details, ) .await } @@ -219,45 +212,12 @@ impl api::IncomingWebhook for ConnectorEnum { } } - async fn verify_webhook_source_verification_call( - &self, - state: &crate::routes::SessionState, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, - connector_name: &str, - request_details: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - match self { - Self::Old(connector) => { - connector - .verify_webhook_source_verification_call( - state, - merchant_account, - merchant_connector_account, - connector_name, - request_details, - ) - .await - } - Self::New(connector) => { - connector - .verify_webhook_source_verification_call( - state, - merchant_account, - merchant_connector_account, - connector_name, - request_details, - ) - .await - } - } - } - async fn verify_webhook_source( &self, request: &IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, + merchant_id: &str, + connector_webhook_details: Option, + connector_account_details: crypto::Encryptable>, connector_name: &str, ) -> CustomResult { match self { @@ -265,8 +225,9 @@ impl api::IncomingWebhook for ConnectorEnum { connector .verify_webhook_source( request, - merchant_account, - merchant_connector_account, + merchant_id, + connector_webhook_details, + connector_account_details, connector_name, ) .await @@ -275,8 +236,9 @@ impl api::IncomingWebhook for ConnectorEnum { connector .verify_webhook_source( request, - merchant_account, - merchant_connector_account, + merchant_id, + connector_webhook_details, + connector_account_details, connector_name, ) .await diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index 3a39e06fde..9b2a69bfe0 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -74,13 +74,6 @@ pub struct PostAuthenticationResponse { pub eci: Option, } -#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] -pub struct ExternalAuthenticationPayload { - pub trans_status: common_enums::TransactionStatus, - pub authentication_value: Option, - pub eci: Option, -} - pub trait ConnectorAuthentication: services::ConnectorIntegration< Authentication, diff --git a/crates/router/src/types/api/disputes.rs b/crates/router/src/types/api/disputes.rs index 72a899c3b9..247b8bf45e 100644 --- a/crates/router/src/types/api/disputes.rs +++ b/crates/router/src/types/api/disputes.rs @@ -1,5 +1,5 @@ +pub use hyperswitch_interfaces::disputes::DisputePayload; use masking::{Deserialize, Serialize}; -use time::PrimitiveDateTime; use crate::{services, types}; @@ -12,20 +12,6 @@ pub use hyperswitch_domain_models::router_flow_types::dispute::{Accept, Defend, pub use super::disputes_v2::{AcceptDisputeV2, DefendDisputeV2, DisputeV2, SubmitEvidenceV2}; -#[derive(Default, Debug)] -pub struct DisputePayload { - pub amount: String, - pub currency: String, - pub dispute_stage: api_models::enums::DisputeStage, - pub connector_status: String, - pub connector_dispute_id: String, - pub connector_reason: Option, - pub connector_reason_code: Option, - pub challenge_required_by: Option, - pub created_at: Option, - pub updated_at: Option, -} - #[derive(Default, Debug, Deserialize, Serialize)] pub struct DisputeEvidence { pub cancellation_policy: Option, diff --git a/crates/router/src/types/api/webhooks.rs b/crates/router/src/types/api/webhooks.rs index cc37cb8d7b..490ff29640 100644 --- a/crates/router/src/types/api/webhooks.rs +++ b/crates/router/src/types/api/webhooks.rs @@ -1,282 +1,5 @@ -use api_models::admin::MerchantConnectorWebhookDetails; pub use api_models::webhooks::{ AuthenticationIdType, IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig, ObjectReferenceId, OutgoingWebhook, OutgoingWebhookContent, WebhookFlow, }; -use common_utils::ext_traits::ValueExt; -use error_stack::ResultExt; -use masking::ExposeInterface; - -use super::ConnectorCommon; -use crate::{ - core::{ - errors::{self, CustomResult}, - payments, - webhooks::utils::construct_webhook_router_data, - }, - db::StorageInterface, - services::{self}, - types::{self, domain}, - utils::crypto, -}; - -pub struct IncomingWebhookRequestDetails<'a> { - pub method: actix_web::http::Method, - pub uri: actix_web::http::Uri, - pub headers: &'a actix_web::http::header::HeaderMap, - pub body: &'a [u8], - pub query_params: String, -} - -#[async_trait::async_trait] -pub trait IncomingWebhook: ConnectorCommon + Sync { - fn get_webhook_body_decoding_algorithm( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> { - Ok(Box::new(crypto::NoAlgorithm)) - } - - async fn get_webhook_body_decoding_merchant_secret( - &self, - _db: &dyn StorageInterface, - _merchant_id: &str, - ) -> CustomResult, errors::ConnectorError> { - Ok(Vec::new()) - } - - fn get_webhook_body_decoding_message( - &self, - request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> { - Ok(request.body.to_vec()) - } - - async fn decode_webhook_body( - &self, - db: &dyn StorageInterface, - request: &IncomingWebhookRequestDetails<'_>, - merchant_id: &str, - ) -> CustomResult, errors::ConnectorError> { - let algorithm = self.get_webhook_body_decoding_algorithm(request)?; - - let message = self - .get_webhook_body_decoding_message(request) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - let secret = self - .get_webhook_body_decoding_merchant_secret(db, merchant_id) - .await - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - - algorithm - .decode_message(&secret, message.into()) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) - } - - fn get_webhook_source_verification_algorithm( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> { - Ok(Box::new(crypto::NoAlgorithm)) - } - - async fn get_webhook_source_verification_merchant_secret( - &self, - merchant_account: &domain::MerchantAccount, - connector_name: &str, - merchant_connector_account: domain::MerchantConnectorAccount, - ) -> CustomResult { - let merchant_id = merchant_account.merchant_id.as_str(); - let debug_suffix = format!( - "For merchant_id: {}, and connector_name: {}", - merchant_id, connector_name - ); - let default_secret = "default_secret".to_string(); - let merchant_secret = match merchant_connector_account.connector_webhook_details { - Some(merchant_connector_webhook_details) => { - let connector_webhook_details = merchant_connector_webhook_details - .parse_value::( - "MerchantConnectorWebhookDetails", - ) - .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed) - .attach_printable_lazy(|| { - format!( - "Deserializing MerchantConnectorWebhookDetails failed {}", - debug_suffix - ) - })?; - api_models::webhooks::ConnectorWebhookSecrets { - secret: connector_webhook_details - .merchant_secret - .expose() - .into_bytes(), - additional_secret: connector_webhook_details.additional_secret, - } - } - - None => api_models::webhooks::ConnectorWebhookSecrets { - secret: default_secret.into_bytes(), - additional_secret: None, - }, - }; - - //need to fetch merchant secret from config table with caching in future for enhanced performance - - //If merchant has not set the secret for webhook source verification, "default_secret" is returned. - //So it will fail during verification step and goes to psync flow. - Ok(merchant_secret) - } - - fn get_webhook_source_verification_signature( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, - ) -> CustomResult, errors::ConnectorError> { - Ok(Vec::new()) - } - - fn get_webhook_source_verification_message( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - _merchant_id: &str, - _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, - ) -> CustomResult, errors::ConnectorError> { - Ok(Vec::new()) - } - - async fn verify_webhook_source_verification_call( - &self, - state: &crate::routes::SessionState, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, - connector_name: &str, - request_details: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - let connector_data = types::api::ConnectorData::get_connector_by_name( - &state.conf.connectors, - connector_name, - types::api::GetToken::Connector, - None, - ) - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) - .attach_printable("invalid connector name received in payment attempt")?; - let connector_integration: services::BoxedWebhookSourceVerificationConnectorIntegrationInterface< - types::api::VerifyWebhookSource, - types::VerifyWebhookSourceRequestData, - types::VerifyWebhookSourceResponseData, - > = connector_data.connector.get_connector_integration(); - let connector_webhook_secrets = self - .get_webhook_source_verification_merchant_secret( - merchant_account, - connector_name, - merchant_connector_account.clone(), - ) - .await - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - let router_data = construct_webhook_router_data( - connector_name, - merchant_connector_account, - merchant_account, - &connector_webhook_secrets, - request_details, - ) - .await - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) - .attach_printable("Failed while constructing webhook router data")?; - - let response = services::execute_connector_processing_step( - state, - connector_integration, - &router_data, - payments::CallConnectorAction::Trigger, - None, - ) - .await?; - - let verification_result = response - .response - .map(|response| response.verify_webhook_status); - match verification_result { - Ok(types::VerifyWebhookStatus::SourceVerified) => Ok(true), - _ => Ok(false), - } - } - - async fn verify_webhook_source( - &self, - request: &IncomingWebhookRequestDetails<'_>, - merchant_account: &domain::MerchantAccount, - merchant_connector_account: domain::MerchantConnectorAccount, - connector_name: &str, - ) -> CustomResult { - let algorithm = self - .get_webhook_source_verification_algorithm(request) - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - let connector_webhook_secrets = self - .get_webhook_source_verification_merchant_secret( - merchant_account, - connector_name, - merchant_connector_account, - ) - .await - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - let signature = self - .get_webhook_source_verification_signature(request, &connector_webhook_secrets) - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - let message = self - .get_webhook_source_verification_message( - request, - &merchant_account.merchant_id, - &connector_webhook_secrets, - ) - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; - - algorithm - .verify_signature(&connector_webhook_secrets.secret, &signature, &message) - .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) - } - - fn get_webhook_object_reference_id( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult; - - fn get_webhook_event_type( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult; - - fn get_webhook_resource_object( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError>; - - fn get_webhook_api_response( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> - { - Ok(services::api::ApplicationResponse::StatusOk) - } - - fn get_dispute_details( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into()) - } - - fn get_external_authentication_details( - &self, - _request: &IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented( - "get_external_authentication_details method".to_string(), - ) - .into()) - } -} +pub use hyperswitch_interfaces::webhooks::{IncomingWebhook, IncomingWebhookRequestDetails};