From 1a29cd7cf8b3a376298b37ec297972a37350323b Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Wed, 15 May 2024 12:33:54 +0530 Subject: [PATCH 01/14] add webhook support for frm --- crates/api_models/src/enums.rs | 4 + crates/api_models/src/webhooks.rs | 6 + crates/router/src/connector/signifyd.rs | 93 +++++++++++- .../connector/signifyd/transformers/api.rs | 25 +++ crates/router/src/core/webhooks.rs | 142 ++++++++++++++++++ 5 files changed, 262 insertions(+), 8 deletions(-) diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 35d76e9775e6..88be02f6d920 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -478,3 +478,7 @@ pub fn convert_pm_auth_connector(connector_name: &str) -> Option Option { AuthenticationConnectors::from_str(connector_name).ok() } + +pub fn convert_frm_connector(connector_name: &str) -> Option { + FrmConnectors::from_str(connector_name).ok() +} diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index df9b1249c754..60a10b6cdd08 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -39,6 +39,8 @@ pub enum IncomingWebhookEvent { MandateRevoked, EndpointVerification, ExternalAuthenticationARes, + FrmApproved, + FrmRejected, } pub enum WebhookFlow { @@ -50,6 +52,7 @@ pub enum WebhookFlow { BankTransfer, Mandate, ExternalAuthentication, + FraudRiskManagement, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -119,6 +122,9 @@ impl From for WebhookFlow { IncomingWebhookEvent::SourceChargeable | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication, + IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => { + Self::FraudRiskManagement + } } } } diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 1c045b8da011..05419bb7edf8 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -3,17 +3,20 @@ use std::fmt::Debug; use base64::Engine; #[cfg(feature = "frm")] -use common_utils::request::RequestContent; -use error_stack::{report, ResultExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +use error_stack::ResultExt; use masking::PeekInterface; +use ring::hmac; use transformers as signifyd; +use super::utils as connector_utils; use crate::{ configs::settings, consts, core::errors::{self, CustomResult}, headers, services::{self, request, ConnectorIntegration, ConnectorValidation}, + types::domain, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -648,24 +651,98 @@ impl #[async_trait::async_trait] impl api::IncomingWebhook for Signifyd { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = + connector_utils::get_header_key_value("x-signifyd-sec-hmac-sha256", request.headers)?; + Ok(header_value.as_bytes().to_vec()) + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + async fn verify_webhook_source( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + merchant_account: &domain::MerchantAccount, + merchant_connector_account: domain::MerchantConnectorAccount, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_account, + connector_label, + 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)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &connector_webhook_secrets.secret); + let signed_message = hmac::sign(&signing_key, &message); + let payload_sign = consts::BASE64_ENGINE.encode(signed_message.as_ref()); + Ok(payload_sign.as_bytes().eq(&signature)) + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::api::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(resource.order_id), + )) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::api::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api::IncomingWebhookEvent::from(resource.review_disposition)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::api::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(resource)) } } diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index b56c7f7011ef..e07db98962ef 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -1,3 +1,4 @@ +use crate::types::api; use bigdecimal::ToPrimitive; use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::{self, ResultExt}; @@ -703,3 +704,27 @@ impl TryFrom<&frm_types::FrmRecordReturnRouterData> for SignifydPaymentsRecordRe }) } } + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] + +pub struct SignifydWebhookBody { + pub order_id: String, + pub review_disposition: ReviewDisposition, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ReviewDisposition { + Fraudulent, + Good, +} + +impl From for api::IncomingWebhookEvent { + fn from(value: ReviewDisposition) -> Self { + match value { + ReviewDisposition::Fraudulent => Self::FrmRejected, + ReviewDisposition::Good => Self::FrmApproved, + } + } +} diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 3c04fdb7e498..dd7db3f51373 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -680,6 +680,125 @@ pub async fn mandates_incoming_webhook_flow( } } +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub(crate) async fn frm_incoming_webhook_flow( + state: AppState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + source_verified: bool, + event_type: webhooks::IncomingWebhookEvent, + _request_details: &api::IncomingWebhookRequestDetails<'_>, + _connector: &(dyn api::Connector + Sync), + object_ref_id: api::ObjectReferenceId, + _business_profile: diesel_models::business_profile::BusinessProfile, + _merchant_connector_account: domain::MerchantConnectorAccount, +) -> CustomResult { + if source_verified { + let payment_attempt = + get_payment_attempt_from_object_reference_id(&state, object_ref_id, &merchant_account) + .await?; + match event_type { + webhooks::IncomingWebhookEvent::FrmApproved => { + println!("frm approved"); + let payments_response = Box::pin(payments::payments_core::< + api::Approve, + api::PaymentsResponse, + _, + _, + _, + Ctx, + >( + state.clone(), + req_state, + merchant_account.clone(), + key_store.clone(), + payments::PaymentApprove, + api::PaymentsCaptureRequest { + payment_id: payment_attempt.payment_id, + ..Default::default() + }, + services::api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + )) + .await?; + match payments_response { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { + let payment_id = payments_response + .payment_id + .clone() + .get_required_value("payment_id") + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("payment id not received from payments core")?; + let status = payments_response.status; + let _event_type: Option = + payments_response.status.foreign_into(); + let response = WebhookResponseTracker::Payment { payment_id, status }; + Ok(response) + } + _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( + "Did not get payment id as object reference id in webhook payments flow", + )?, + } + } + webhooks::IncomingWebhookEvent::FrmRejected => { + println!("frm approved"); + let payments_response = Box::pin(payments::payments_core::< + api::Reject, + api::PaymentsResponse, + _, + _, + _, + Ctx, + >( + state.clone(), + req_state, + merchant_account.clone(), + key_store.clone(), + payments::PaymentCancel, + api::PaymentsCancelRequest { + payment_id: payment_attempt.payment_id.clone(), + cancellation_reason: Some("Rejected by merchant".to_string()), + ..Default::default() + }, + services::api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + )) + .await?; + match payments_response { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { + let payment_id = payments_response + .payment_id + .clone() + .get_required_value("payment_id") + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("payment id not received from payments core")?; + let status = payments_response.status; + let _event_type: Option = + payments_response.status.foreign_into(); + let response = WebhookResponseTracker::Payment { payment_id, status }; + Ok(response) + } + _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( + "Did not get payment id as object reference id in webhook payments flow", + )?, + } + } + _ => Err(report!(errors::ApiErrorResponse::EventNotFound)), + } + } else { + logger::error!("Webhook source verification failed for frm webhooks flow"); + Err(report!( + errors::ApiErrorResponse::WebhookAuthenticationFailed + )) + } +} + #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub async fn disputes_incoming_webhook_flow( @@ -1832,6 +1951,21 @@ pub async fn webhooks_core Box::pin(frm_incoming_webhook_flow::( + state.clone(), + req_state, + merchant_account, + key_store, + source_verified, + event_type, + &request_details, + connector, + object_ref_id, + business_profile, + merchant_connector_account, + )) + .await + .attach_printable("Incoming webhook flow for external authentication failed")?, _ => Err(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unsupported Flow Type received in incoming webhooks")?, @@ -1905,6 +2039,7 @@ fn get_connector_by_connector_name( ) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> { let authentication_connector = api_models::enums::convert_authentication_connector(connector_name); + let frm_connector = api_models::enums::convert_frm_connector(connector_name); let (connector, connector_name) = if authentication_connector.is_some() { let authentication_connector_data = api::AuthenticationConnectorData::get_connector_by_name(connector_name)?; @@ -1912,6 +2047,13 @@ fn get_connector_by_connector_name( authentication_connector_data.connector, authentication_connector_data.connector_name.to_string(), ) + } else if frm_connector.is_some() { + let frm_connector_data = + api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; + ( + frm_connector_data.connector, + frm_connector_data.connector_name.to_string(), + ) } else { let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, From 25e9bb81a374661d225bef47ba9b3c6a4d90a5ff Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 12:01:08 +0000 Subject: [PATCH 02/14] chore: run formatter --- crates/router/src/connector/signifyd.rs | 2 +- crates/router/src/connector/signifyd/transformers/api.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 05419bb7edf8..4b41d59acd77 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -16,10 +16,10 @@ use crate::{ core::errors::{self, CustomResult}, headers, services::{self, request, ConnectorIntegration, ConnectorValidation}, - types::domain, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + domain, }, }; #[cfg(feature = "frm")] diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index e07db98962ef..82cf68ba9604 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -1,4 +1,3 @@ -use crate::types::api; use bigdecimal::ToPrimitive; use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::{self, ResultExt}; @@ -14,7 +13,7 @@ use crate::{ }, core::{errors, fraud_check::types as core_types}, types::{ - self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, ResponseId, ResponseRouterData, }, }; From 1ec827dd2bdc421c4d34ccb35aa49b9c68df6ec0 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Thu, 16 May 2024 17:35:56 +0530 Subject: [PATCH 03/14] rename frm event --- crates/api_models/src/webhooks.rs | 4 ++-- crates/router/src/core/webhooks.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index 60a10b6cdd08..b5428b6fca50 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -52,7 +52,7 @@ pub enum WebhookFlow { BankTransfer, Mandate, ExternalAuthentication, - FraudRiskManagement, + FraudCheck, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -123,7 +123,7 @@ impl From for WebhookFlow { | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication, IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => { - Self::FraudRiskManagement + Self::FraudCheck } } } diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index dd7db3f51373..9cd6d3281ae6 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -1951,7 +1951,7 @@ pub async fn webhooks_core Box::pin(frm_incoming_webhook_flow::( + api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow::( state.clone(), req_state, merchant_account, From d9a34c46f7909ef118df5a54b10549fee1be75a2 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Mon, 20 May 2024 13:59:44 +0530 Subject: [PATCH 04/14] minor refactor --- crates/router/src/core/webhooks.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index dcaddfbb680d..27cb96135f74 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -679,7 +679,7 @@ pub async fn mandates_incoming_webhook_flow( #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] -pub(crate) async fn frm_incoming_webhook_flow( +pub(crate) async fn frm_incoming_webhook_flow( state: AppState, req_state: ReqState, merchant_account: domain::MerchantAccount, @@ -705,7 +705,6 @@ pub(crate) async fn frm_incoming_webhook_flow( _, _, _, - Ctx, >( state.clone(), req_state, @@ -742,14 +741,13 @@ pub(crate) async fn frm_incoming_webhook_flow( } } webhooks::IncomingWebhookEvent::FrmRejected => { - println!("frm approved"); + println!("frm rejected"); let payments_response = Box::pin(payments::payments_core::< api::Reject, api::PaymentsResponse, _, _, _, - Ctx, >( state.clone(), req_state, @@ -1947,7 +1945,7 @@ pub async fn webhooks_core( .await .attach_printable("Incoming webhook flow for external authentication failed")? } - api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow::( + api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( state.clone(), req_state, merchant_account, From add129f3d7ec894e77d87671c573e05ee0ef3896 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Mon, 20 May 2024 15:34:44 +0530 Subject: [PATCH 05/14] clippy fix --- crates/api_models/src/enums.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 48f46daa1691..1f08deca896c 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -591,6 +591,7 @@ pub fn convert_authentication_connector(connector_name: &str) -> Option Option { FrmConnectors::from_str(connector_name).ok() } From 5a755bd1a6274026b07cdecd6dc20c6e7af5b9a8 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Mon, 20 May 2024 20:57:59 +0530 Subject: [PATCH 06/14] clippy fix --- crates/router/src/connector/signifyd.rs | 6 +++--- crates/router/src/core/webhooks.rs | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 4b41d59acd77..d9826f95a69e 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -715,7 +715,7 @@ impl api::IncomingWebhook for Signifyd { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - let resource: signifyd::api::SignifydWebhookBody = request + let resource: signifyd::SignifydWebhookBody = request .body .parse_struct("SignifydWebhookBody") .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; @@ -728,7 +728,7 @@ impl api::IncomingWebhook for Signifyd { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - let resource: signifyd::api::SignifydWebhookBody = request + let resource: signifyd::SignifydWebhookBody = request .body .parse_struct("SignifydWebhookBody") .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; @@ -739,7 +739,7 @@ impl api::IncomingWebhook for Signifyd { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - let resource: signifyd::api::SignifydWebhookBody = request + let resource: signifyd::SignifydWebhookBody = request .body .parse_struct("SignifydWebhookBody") .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 27cb96135f74..1d2193817775 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -700,7 +700,7 @@ pub(crate) async fn frm_incoming_webhook_flow( webhooks::IncomingWebhookEvent::FrmApproved => { println!("frm approved"); let payments_response = Box::pin(payments::payments_core::< - api::Approve, + api::Capture, api::PaymentsResponse, _, _, @@ -713,6 +713,7 @@ pub(crate) async fn frm_incoming_webhook_flow( payments::PaymentApprove, api::PaymentsCaptureRequest { payment_id: payment_attempt.payment_id, + amount_to_capture: Some(payment_attempt.amount), ..Default::default() }, services::api::AuthFlow::Merchant, @@ -743,7 +744,7 @@ pub(crate) async fn frm_incoming_webhook_flow( webhooks::IncomingWebhookEvent::FrmRejected => { println!("frm rejected"); let payments_response = Box::pin(payments::payments_core::< - api::Reject, + api::Void, api::PaymentsResponse, _, _, @@ -753,7 +754,7 @@ pub(crate) async fn frm_incoming_webhook_flow( req_state, merchant_account.clone(), key_store.clone(), - payments::PaymentCancel, + payments::PaymentReject, api::PaymentsCancelRequest { payment_id: payment_attempt.payment_id.clone(), cancellation_reason: Some("Rejected by merchant".to_string()), From 39e0a947556fb7355d8642a04b993686e51ed024 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 12:36:03 +0530 Subject: [PATCH 07/14] add riskified webhooks --- crates/api_models/src/webhooks.rs | 3 + crates/router/src/connector/riskified.rs | 102 +++++++++++++++--- .../connector/riskified/transformers/api.rs | 29 ++++- crates/router/src/connector/signifyd.rs | 4 +- .../connector/signifyd/transformers/api.rs | 4 +- crates/router/src/core/webhooks.rs | 2 - 6 files changed, 123 insertions(+), 21 deletions(-) diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index b5428b6fca50..68ca967e8379 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -39,7 +39,9 @@ pub enum IncomingWebhookEvent { MandateRevoked, EndpointVerification, ExternalAuthenticationARes, + #[cfg(feature = "frm")] FrmApproved, + #[cfg(feature = "frm")] FrmRejected, } @@ -52,6 +54,7 @@ pub enum WebhookFlow { BankTransfer, Mandate, ExternalAuthentication, + #[cfg(feature = "frm")] FraudCheck, } diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index c7cb321f7198..705eca36772a 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -2,22 +2,25 @@ pub mod transformers; use std::fmt::Debug; #[cfg(feature = "frm")] -use common_utils::request::RequestContent; -use error_stack::{report, ResultExt}; +use base64::Engine; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface}; use ring::hmac; use transformers as riskified; #[cfg(feature = "frm")] -use super::utils::FrmTransactionRouterDataRequest; +use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; use crate::{ configs::settings, + consts, core::errors::{self, CustomResult}, headers, services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + domain, }, }; #[cfg(feature = "frm")] @@ -268,11 +271,7 @@ impl self.base_url(connectors), "/checkout_denied" )), - Some(true) => Ok(format!("{}{}", self.base_url(connectors), "/decision")), - None => Err(errors::ConnectorError::FlowNotSupported { - flow: "Transaction".to_owned(), - connector: req.connector.to_string(), - })?, + _ => Ok(format!("{}{}", self.base_url(connectors), "/decision")), } } @@ -545,26 +544,101 @@ impl frm_api::FraudCheckFulfillment for Riskified {} #[cfg(feature = "frm")] impl frm_api::FraudCheckRecordReturn for Riskified {} +#[cfg(feature = "frm")] #[async_trait::async_trait] impl api::IncomingWebhook for Riskified { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = + connector_utils::get_header_key_value("x-riskified-hmac-sha256", request.headers)?; + Ok(header_value.as_bytes().to_vec()) + } + + async fn verify_webhook_source( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + merchant_account: &domain::MerchantAccount, + merchant_connector_account: domain::MerchantConnectorAccount, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_account, + connector_label, + 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)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &connector_webhook_secrets.secret); + let signed_message = hmac::sign(&signing_key, &message); + let payload_sign = consts::BASE64_ENGINE.encode(signed_message.as_ref()); + Ok(payload_sign.as_bytes().eq(&signature)) + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(resource.id), + )) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api::IncomingWebhookEvent::from(resource.status)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(resource)) } } diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index f20b6103cc8b..f0d47274d138 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -11,7 +11,7 @@ use crate::{ }, core::{errors, fraud_check::types as core_types}, types::{ - self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, ResponseId, ResponseRouterData, }, }; @@ -142,8 +142,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq field_name: "frm_metadata", })? .parse_value("Riskified Metadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - + .change_context(errors::ConnectorError::InvalidDataFormat { + field_name: "frm_metadata", + })?; let billing_address = payment_data.get_billing()?; let shipping_address = payment_data.get_shipping_address_with_phone_number()?; let address = payment_data.get_billing_address()?; @@ -606,3 +607,25 @@ fn get_fulfillment_status( core_types::FulfillmentStatus::PARTIAL | core_types::FulfillmentStatus::REPLACEMENT => None, } } + +#[derive(Debug, Clone, Deserialize, Serialize)] + +pub struct RiskifiedWebhookBody { + pub id: String, + pub status: RiskifiedWebhookStatus, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum RiskifiedWebhookStatus { + Approved, + Declined, +} + +impl From for api::IncomingWebhookEvent { + fn from(value: RiskifiedWebhookStatus) -> Self { + match value { + RiskifiedWebhookStatus::Declined => Self::FrmRejected, + RiskifiedWebhookStatus::Approved => Self::FrmApproved, + } + } +} diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index d9826f95a69e..597f34ebaf7e 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -1,14 +1,15 @@ pub mod transformers; use std::fmt::Debug; -use base64::Engine; #[cfg(feature = "frm")] +use base64::Engine; use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; use error_stack::ResultExt; use masking::PeekInterface; use ring::hmac; use transformers as signifyd; +#[cfg(feature = "frm")] use super::utils as connector_utils; use crate::{ configs::settings, @@ -649,6 +650,7 @@ impl } } +#[cfg(feature = "frm")] #[async_trait::async_trait] impl api::IncomingWebhook for Signifyd { fn get_webhook_source_verification_algorithm( diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 71f4b4adc19e..8c487f904f8a 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -399,7 +399,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ field_name: "frm_metadata", })? .parse_value("Signifyd Frm Metadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::InvalidDataFormat { + field_name: "frm_metadata", + })?; let ship_address = item.get_shipping_address()?; let street_addr = ship_address.get_line1()?; let city_addr = ship_address.get_city()?; diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 1d2193817775..f9076429cb73 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -698,7 +698,6 @@ pub(crate) async fn frm_incoming_webhook_flow( .await?; match event_type { webhooks::IncomingWebhookEvent::FrmApproved => { - println!("frm approved"); let payments_response = Box::pin(payments::payments_core::< api::Capture, api::PaymentsResponse, @@ -742,7 +741,6 @@ pub(crate) async fn frm_incoming_webhook_flow( } } webhooks::IncomingWebhookEvent::FrmRejected => { - println!("frm rejected"); let payments_response = Box::pin(payments::payments_core::< api::Void, api::PaymentsResponse, From adf450d24b9e2ef23d12971edbccc1eec924dfdb Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 13:03:36 +0530 Subject: [PATCH 08/14] clippy fix --- crates/api_models/src/webhooks.rs | 3 --- crates/router/src/connector/riskified.rs | 8 ++------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index 68ca967e8379..b5428b6fca50 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -39,9 +39,7 @@ pub enum IncomingWebhookEvent { MandateRevoked, EndpointVerification, ExternalAuthenticationARes, - #[cfg(feature = "frm")] FrmApproved, - #[cfg(feature = "frm")] FrmRejected, } @@ -54,7 +52,6 @@ pub enum WebhookFlow { BankTransfer, Mandate, ExternalAuthentication, - #[cfg(feature = "frm")] FraudCheck, } diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 705eca36772a..76620a16a2b1 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -125,7 +125,7 @@ impl ConnectorCommon for Riskified { Ok(ErrorResponse { status_code: res.status_code, attempt_status: None, - code: crate::consts::NO_ERROR_CODE.to_string(), + code: consts::NO_ERROR_CODE.to_string(), message: response.error.message.clone(), reason: None, connector_transaction_id: None, @@ -285,14 +285,10 @@ impl let req_obj = riskified::TransactionFailedRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) } - Some(true) => { + _ => { let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) } - None => Err(errors::ConnectorError::FlowNotSupported { - flow: "Transaction".to_owned(), - connector: req.connector.to_owned(), - })?, } } From 309a16f5813c3cd6dfd66d0b14acc4bdf88c75e8 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 15:59:38 +0530 Subject: [PATCH 09/14] feature flag fix for clippy --- crates/router/src/connector/riskified.rs | 35 ++++++++++++++-------- crates/router/src/connector/signifyd.rs | 13 ++++++-- crates/router/src/core/webhooks.rs | 19 +++++++----- crates/router/src/types/api/fraud_check.rs | 6 ++-- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 76620a16a2b1..9ec7bdbf2e09 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -1,39 +1,47 @@ pub mod transformers; use std::fmt::Debug; -#[cfg(feature = "frm")] -use base64::Engine; -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; -use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface}; -use ring::hmac; -use transformers as riskified; - #[cfg(feature = "frm")] use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; + use crate::{ configs::settings, - consts, core::errors::{self, CustomResult}, - headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, }, }; #[cfg(feature = "frm")] use crate::{ + consts, events::connector_api_logs::ConnectorEvent, - types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + headers, + services::request, + types::{ + api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, + }, utils::BytesExt, }; +#[cfg(feature = "frm")] +use base64::Engine; +#[cfg(feature = "frm")] +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +#[cfg(feature = "frm")] +use error_stack::ResultExt; +#[cfg(feature = "frm")] +use masking::{ExposeInterface, PeekInterface}; +#[cfg(feature = "frm")] +use ring::hmac; +#[cfg(feature = "frm")] +use transformers as riskified; #[derive(Debug, Clone)] pub struct Riskified; impl Riskified { + #[cfg(feature = "frm")] pub fn generate_authorization_signature( &self, auth: &riskified::RiskifiedAuthType, @@ -56,6 +64,7 @@ impl ConnectorCommonExt for Ri where Self: ConnectorIntegration, { + #[cfg(feature = "frm")] fn build_headers( &self, req: &types::RouterData, diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 597f34ebaf7e..6444a39fa5ab 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -3,30 +3,36 @@ use std::fmt::Debug; #[cfg(feature = "frm")] use base64::Engine; +#[cfg(feature = "frm")] use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +#[cfg(feature = "frm")] use error_stack::ResultExt; +#[cfg(feature = "frm")] use masking::PeekInterface; +#[cfg(feature = "frm")] use ring::hmac; +#[cfg(feature = "frm")] use transformers as signifyd; #[cfg(feature = "frm")] use super::utils as connector_utils; use crate::{ configs::settings, - consts, core::errors::{self, CustomResult}, headers, services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, }, }; #[cfg(feature = "frm")] use crate::{ + consts, events::connector_api_logs::ConnectorEvent, - types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + types::{ + api::fraud_check as frm_api, domain, fraud_check as frm_types, s, ErrorResponse, Response, + }, utils::BytesExt, }; @@ -65,6 +71,7 @@ impl ConnectorCommon for Signifyd { connectors.signifyd.base_url.as_ref() } + #[cfg(feature = "frm")] fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index f9076429cb73..d59409b1bf65 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -2032,7 +2032,9 @@ fn get_connector_by_connector_name( ) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> { let authentication_connector = api_models::enums::convert_authentication_connector(connector_name); + #[cfg(feature = "frm")] let frm_connector = api_models::enums::convert_frm_connector(connector_name); + let (connector, connector_name) = if authentication_connector.is_some() { let authentication_connector_data = api::AuthenticationConnectorData::get_connector_by_name(connector_name)?; @@ -2040,14 +2042,17 @@ fn get_connector_by_connector_name( authentication_connector_data.connector, authentication_connector_data.connector_name.to_string(), ) - } else if frm_connector.is_some() { - let frm_connector_data = - api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; - ( - frm_connector_data.connector, - frm_connector_data.connector_name.to_string(), - ) } else { + #[cfg(feature = "frm")] + if frm_connector.is_some() { + let frm_connector_data = + api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; + ( + frm_connector_data.connector, + frm_connector_data.connector_name.to_string(), + ) + } + let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, connector_name, diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 45c79930e80c..6c1ff523ea48 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -1,10 +1,12 @@ -use std::str::FromStr; - +#[cfg(feature = "frm")] use api_models::enums; use common_utils::errors::CustomResult; use error_stack::ResultExt; +use std::str::FromStr; +#[cfg(feature = "frm")] use super::{BoxedConnector, ConnectorData, SessionConnectorData}; +#[cfg(feature = "frm")] use crate::{ connector, core::errors, From ef9191c4c92cb9280e018a0932748ae75cf19d6f Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 10:30:25 +0000 Subject: [PATCH 10/14] chore: run formatter --- crates/router/src/connector/riskified.rs | 26 +++++++++++----------- crates/router/src/core/webhooks.rs | 2 +- crates/router/src/types/api/fraud_check.rs | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index 9ec7bdbf2e09..28678bbd9392 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -2,8 +2,20 @@ pub mod transformers; use std::fmt::Debug; #[cfg(feature = "frm")] -use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; +use base64::Engine; +#[cfg(feature = "frm")] +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +#[cfg(feature = "frm")] +use error_stack::ResultExt; +#[cfg(feature = "frm")] +use masking::{ExposeInterface, PeekInterface}; +#[cfg(feature = "frm")] +use ring::hmac; +#[cfg(feature = "frm")] +use transformers as riskified; +#[cfg(feature = "frm")] +use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -24,18 +36,6 @@ use crate::{ }, utils::BytesExt, }; -#[cfg(feature = "frm")] -use base64::Engine; -#[cfg(feature = "frm")] -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; -#[cfg(feature = "frm")] -use error_stack::ResultExt; -#[cfg(feature = "frm")] -use masking::{ExposeInterface, PeekInterface}; -#[cfg(feature = "frm")] -use ring::hmac; -#[cfg(feature = "frm")] -use transformers as riskified; #[derive(Debug, Clone)] pub struct Riskified; diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index d59409b1bf65..62776c5d7eb0 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -2052,7 +2052,7 @@ fn get_connector_by_connector_name( frm_connector_data.connector_name.to_string(), ) } - + let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, connector_name, diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 6c1ff523ea48..e53adf5585de 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -1,8 +1,9 @@ +use std::str::FromStr; + #[cfg(feature = "frm")] use api_models::enums; use common_utils::errors::CustomResult; use error_stack::ResultExt; -use std::str::FromStr; #[cfg(feature = "frm")] use super::{BoxedConnector, ConnectorData, SessionConnectorData}; From c615925dd45ef2c29abe592ac3e93b2310f98fd9 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 16:42:53 +0530 Subject: [PATCH 11/14] clippy fix --- crates/router/src/connector/signifyd.rs | 2 +- crates/router/src/core/webhooks.rs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 6444a39fa5ab..9e591a336f16 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -31,7 +31,7 @@ use crate::{ consts, events::connector_api_logs::ConnectorEvent, types::{ - api::fraud_check as frm_api, domain, fraud_check as frm_types, s, ErrorResponse, Response, + api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, }, utils::BytesExt, }; diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index d59409b1bf65..eeca7edba1d5 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -2033,7 +2033,17 @@ fn get_connector_by_connector_name( let authentication_connector = api_models::enums::convert_authentication_connector(connector_name); #[cfg(feature = "frm")] - let frm_connector = api_models::enums::convert_frm_connector(connector_name); + { + let frm_connector = api_models::enums::convert_frm_connector(connector_name); + if frm_connector.is_some() { + let frm_connector_data = + api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; + return Ok(( + *frm_connector_data.connector, + frm_connector_data.connector_name.to_string(), + )); + } + } let (connector, connector_name) = if authentication_connector.is_some() { let authentication_connector_data = @@ -2043,16 +2053,6 @@ fn get_connector_by_connector_name( authentication_connector_data.connector_name.to_string(), ) } else { - #[cfg(feature = "frm")] - if frm_connector.is_some() { - let frm_connector_data = - api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; - ( - frm_connector_data.connector, - frm_connector_data.connector_name.to_string(), - ) - } - let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, connector_name, From d134d873289650b6357b8ff54fc3d4da41648728 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 18:54:19 +0530 Subject: [PATCH 12/14] resolve comments --- crates/router/src/core/webhooks.rs | 14 ++++---------- crates/router/src/types/api/fraud_check.rs | 3 --- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index eeca7edba1d5..468caaedd0e5 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -686,11 +686,7 @@ pub(crate) async fn frm_incoming_webhook_flow( key_store: domain::MerchantKeyStore, source_verified: bool, event_type: webhooks::IncomingWebhookEvent, - _request_details: &api::IncomingWebhookRequestDetails<'_>, - _connector: &(dyn api::Connector + Sync), object_ref_id: api::ObjectReferenceId, - _business_profile: diesel_models::business_profile::BusinessProfile, - _merchant_connector_account: domain::MerchantConnectorAccount, ) -> CustomResult { if source_verified { let payment_attempt = @@ -712,7 +708,7 @@ pub(crate) async fn frm_incoming_webhook_flow( payments::PaymentApprove, api::PaymentsCaptureRequest { payment_id: payment_attempt.payment_id, - amount_to_capture: Some(payment_attempt.amount), + amount_to_capture: payment_attempt.amount_to_capture, ..Default::default() }, services::api::AuthFlow::Merchant, @@ -755,7 +751,9 @@ pub(crate) async fn frm_incoming_webhook_flow( payments::PaymentReject, api::PaymentsCancelRequest { payment_id: payment_attempt.payment_id.clone(), - cancellation_reason: Some("Rejected by merchant".to_string()), + cancellation_reason: Some( + "Rejected by merchant based on FRM decision".to_string(), + ), ..Default::default() }, services::api::AuthFlow::Merchant, @@ -1951,11 +1949,7 @@ pub async fn webhooks_core( key_store, source_verified, event_type, - &request_details, - connector, object_ref_id, - business_profile, - merchant_connector_account, )) .await .attach_printable("Incoming webhook flow for external authentication failed")?, diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index e53adf5585de..45c79930e80c 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -1,13 +1,10 @@ use std::str::FromStr; -#[cfg(feature = "frm")] use api_models::enums; use common_utils::errors::CustomResult; use error_stack::ResultExt; -#[cfg(feature = "frm")] use super::{BoxedConnector, ConnectorData, SessionConnectorData}; -#[cfg(feature = "frm")] use crate::{ connector, core::errors, From aa59652d3ee3cac720e3535eba33b5caafc0ba7b Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 21 May 2024 20:01:27 +0530 Subject: [PATCH 13/14] add outgoing webhooks --- crates/router/src/core/webhooks.rs | 83 +++++++++++++++--------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 468caaedd0e5..ee70d29ca688 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -687,14 +687,15 @@ pub(crate) async fn frm_incoming_webhook_flow( source_verified: bool, event_type: webhooks::IncomingWebhookEvent, object_ref_id: api::ObjectReferenceId, + business_profile: diesel_models::business_profile::BusinessProfile, ) -> CustomResult { if source_verified { let payment_attempt = get_payment_attempt_from_object_reference_id(&state, object_ref_id, &merchant_account) .await?; - match event_type { + let payment_response = match event_type { webhooks::IncomingWebhookEvent::FrmApproved => { - let payments_response = Box::pin(payments::payments_core::< + Box::pin(payments::payments_core::< api::Capture, api::PaymentsResponse, _, @@ -716,28 +717,10 @@ pub(crate) async fn frm_incoming_webhook_flow( None, HeaderPayload::default(), )) - .await?; - match payments_response { - services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { - let payment_id = payments_response - .payment_id - .clone() - .get_required_value("payment_id") - .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) - .attach_printable("payment id not received from payments core")?; - let status = payments_response.status; - let _event_type: Option = - payments_response.status.foreign_into(); - let response = WebhookResponseTracker::Payment { payment_id, status }; - Ok(response) - } - _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( - "Did not get payment id as object reference id in webhook payments flow", - )?, - } + .await? } webhooks::IncomingWebhookEvent::FrmRejected => { - let payments_response = Box::pin(payments::payments_core::< + Box::pin(payments::payments_core::< api::Void, api::PaymentsResponse, _, @@ -761,27 +744,42 @@ pub(crate) async fn frm_incoming_webhook_flow( None, HeaderPayload::default(), )) - .await?; - match payments_response { - services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { - let payment_id = payments_response - .payment_id - .clone() - .get_required_value("payment_id") - .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) - .attach_printable("payment id not received from payments core")?; - let status = payments_response.status; - let _event_type: Option = - payments_response.status.foreign_into(); - let response = WebhookResponseTracker::Payment { payment_id, status }; - Ok(response) - } - _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( - "Did not get payment id as object reference id in webhook payments flow", - )?, - } + .await? + } + _ => Err(errors::ApiErrorResponse::EventNotFound)?, + }; + match payment_response { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { + let payment_id = payments_response + .payment_id + .clone() + .get_required_value("payment_id") + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("payment id not received from payments core")?; + let status = payments_response.status; + let event_type: Option = payments_response.status.foreign_into(); + if let Some(outgoing_event_type) = event_type { + let primary_object_created_at = payments_response.created; + create_event_and_trigger_outgoing_webhook( + state, + merchant_account, + business_profile, + &key_store, + outgoing_event_type, + enums::EventClass::Payments, + payment_id.clone(), + enums::EventObjectType::PaymentDetails, + api::OutgoingWebhookContent::PaymentDetails(payments_response), + primary_object_created_at, + ) + .await?; + }; + let response = WebhookResponseTracker::Payment { payment_id, status }; + Ok(response) } - _ => Err(report!(errors::ApiErrorResponse::EventNotFound)), + _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( + "Did not get payment id as object reference id in webhook payments flow", + )?, } } else { logger::error!("Webhook source verification failed for frm webhooks flow"); @@ -1950,6 +1948,7 @@ pub async fn webhooks_core( source_verified, event_type, object_ref_id, + business_profile, )) .await .attach_printable("Incoming webhook flow for external authentication failed")?, From fcbada3c22226a79c70dd26a2d5a44614817efc8 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Wed, 22 May 2024 02:22:23 +0530 Subject: [PATCH 14/14] resolve comments --- crates/router/src/core/webhooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index ee70d29ca688..7e29036c40fe 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -1951,7 +1951,7 @@ pub async fn webhooks_core( business_profile, )) .await - .attach_printable("Incoming webhook flow for external authentication failed")?, + .attach_printable("Incoming webhook flow for fraud check failed")?, _ => Err(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unsupported Flow Type received in incoming webhooks")?,