feat(webhook): add frm webhook support (#4662)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
chikke srujan
2024-05-22 13:45:01 +05:30
committed by GitHub
parent 2ad7fc0cd6
commit ae601e8e1b
7 changed files with 400 additions and 38 deletions

View File

@ -599,3 +599,8 @@ pub fn convert_pm_auth_connector(connector_name: &str) -> Option<PmAuthConnector
pub fn convert_authentication_connector(connector_name: &str) -> Option<AuthenticationConnectors> { pub fn convert_authentication_connector(connector_name: &str) -> Option<AuthenticationConnectors> {
AuthenticationConnectors::from_str(connector_name).ok() AuthenticationConnectors::from_str(connector_name).ok()
} }
#[cfg(feature = "frm")]
pub fn convert_frm_connector(connector_name: &str) -> Option<FrmConnectors> {
FrmConnectors::from_str(connector_name).ok()
}

View File

@ -39,6 +39,8 @@ pub enum IncomingWebhookEvent {
MandateRevoked, MandateRevoked,
EndpointVerification, EndpointVerification,
ExternalAuthenticationARes, ExternalAuthenticationARes,
FrmApproved,
FrmRejected,
} }
pub enum WebhookFlow { pub enum WebhookFlow {
@ -50,6 +52,7 @@ pub enum WebhookFlow {
BankTransfer, BankTransfer,
Mandate, Mandate,
ExternalAuthentication, ExternalAuthentication,
FraudCheck,
} }
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -119,6 +122,9 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
IncomingWebhookEvent::SourceChargeable IncomingWebhookEvent::SourceChargeable
| IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer,
IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication, IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication,
IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => {
Self::FraudCheck
}
} }
} }
} }

View File

@ -2,19 +2,24 @@ pub mod transformers;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use common_utils::request::RequestContent; use base64::Engine;
use error_stack::{report, ResultExt}; #[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}; use masking::{ExposeInterface, PeekInterface};
#[cfg(feature = "frm")]
use ring::hmac; use ring::hmac;
#[cfg(feature = "frm")]
use transformers as riskified; use transformers as riskified;
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use super::utils::FrmTransactionRouterDataRequest; use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest};
use crate::{ use crate::{
configs::settings, configs::settings,
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
headers, services::{self, ConnectorIntegration, ConnectorValidation},
services::{self, request, ConnectorIntegration, ConnectorValidation},
types::{ types::{
self, self,
api::{self, ConnectorCommon, ConnectorCommonExt}, api::{self, ConnectorCommon, ConnectorCommonExt},
@ -22,8 +27,13 @@ use crate::{
}; };
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use crate::{ use crate::{
consts,
events::connector_api_logs::ConnectorEvent, 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, utils::BytesExt,
}; };
@ -31,6 +41,7 @@ use crate::{
pub struct Riskified; pub struct Riskified;
impl Riskified { impl Riskified {
#[cfg(feature = "frm")]
pub fn generate_authorization_signature( pub fn generate_authorization_signature(
&self, &self,
auth: &riskified::RiskifiedAuthType, auth: &riskified::RiskifiedAuthType,
@ -53,6 +64,7 @@ impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Ri
where where
Self: ConnectorIntegration<Flow, Request, Response>, Self: ConnectorIntegration<Flow, Request, Response>,
{ {
#[cfg(feature = "frm")]
fn build_headers( fn build_headers(
&self, &self,
req: &types::RouterData<Flow, Request, Response>, req: &types::RouterData<Flow, Request, Response>,
@ -122,7 +134,7 @@ impl ConnectorCommon for Riskified {
Ok(ErrorResponse { Ok(ErrorResponse {
status_code: res.status_code, status_code: res.status_code,
attempt_status: None, attempt_status: None,
code: crate::consts::NO_ERROR_CODE.to_string(), code: consts::NO_ERROR_CODE.to_string(),
message: response.error.message.clone(), message: response.error.message.clone(),
reason: None, reason: None,
connector_transaction_id: None, connector_transaction_id: None,
@ -268,11 +280,7 @@ impl
self.base_url(connectors), self.base_url(connectors),
"/checkout_denied" "/checkout_denied"
)), )),
Some(true) => Ok(format!("{}{}", self.base_url(connectors), "/decision")), _ => Ok(format!("{}{}", self.base_url(connectors), "/decision")),
None => Err(errors::ConnectorError::FlowNotSupported {
flow: "Transaction".to_owned(),
connector: req.connector.to_string(),
})?,
} }
} }
@ -286,14 +294,10 @@ impl
let req_obj = riskified::TransactionFailedRequest::try_from(req)?; let req_obj = riskified::TransactionFailedRequest::try_from(req)?;
Ok(RequestContent::Json(Box::new(req_obj))) Ok(RequestContent::Json(Box::new(req_obj)))
} }
Some(true) => { _ => {
let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; let req_obj = riskified::TransactionSuccessRequest::try_from(req)?;
Ok(RequestContent::Json(Box::new(req_obj))) Ok(RequestContent::Json(Box::new(req_obj)))
} }
None => Err(errors::ConnectorError::FlowNotSupported {
flow: "Transaction".to_owned(),
connector: req.connector.to_owned(),
})?,
} }
} }
@ -545,26 +549,101 @@ impl frm_api::FraudCheckFulfillment for Riskified {}
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
impl frm_api::FraudCheckRecordReturn for Riskified {} impl frm_api::FraudCheckRecordReturn for Riskified {}
#[cfg(feature = "frm")]
#[async_trait::async_trait] #[async_trait::async_trait]
impl api::IncomingWebhook for Riskified { impl api::IncomingWebhook for Riskified {
fn get_webhook_object_reference_id( fn get_webhook_source_verification_algorithm(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, _request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, 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<Vec<u8>, 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<bool, errors::ConnectorError> {
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<Vec<u8>, errors::ConnectorError> {
Ok(request.body.to_vec())
}
fn get_webhook_object_reference_id(
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
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( fn get_webhook_event_type(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> { ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
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( fn get_webhook_resource_object(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> { ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, 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))
} }
} }

View File

@ -11,7 +11,7 @@ use crate::{
}, },
core::{errors, fraud_check::types as core_types}, core::{errors, fraud_check::types as core_types},
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, ResponseId, ResponseRouterData,
}, },
}; };
@ -142,8 +142,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq
field_name: "frm_metadata", field_name: "frm_metadata",
})? })?
.parse_value("Riskified 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 billing_address = payment_data.get_billing()?;
let shipping_address = payment_data.get_shipping_address_with_phone_number()?; let shipping_address = payment_data.get_shipping_address_with_phone_number()?;
let address = payment_data.get_billing_address()?; let address = payment_data.get_billing_address()?;
@ -606,3 +607,25 @@ fn get_fulfillment_status(
core_types::FulfillmentStatus::PARTIAL | core_types::FulfillmentStatus::REPLACEMENT => None, 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<RiskifiedWebhookStatus> for api::IncomingWebhookEvent {
fn from(value: RiskifiedWebhookStatus) -> Self {
match value {
RiskifiedWebhookStatus::Declined => Self::FrmRejected,
RiskifiedWebhookStatus::Approved => Self::FrmApproved,
}
}
}

View File

@ -1,16 +1,23 @@
pub mod transformers; pub mod transformers;
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "frm")]
use base64::Engine; use base64::Engine;
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use common_utils::request::RequestContent; use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
use error_stack::{report, ResultExt}; #[cfg(feature = "frm")]
use error_stack::ResultExt;
#[cfg(feature = "frm")]
use masking::PeekInterface; use masking::PeekInterface;
#[cfg(feature = "frm")]
use ring::hmac;
#[cfg(feature = "frm")]
use transformers as signifyd; use transformers as signifyd;
#[cfg(feature = "frm")]
use super::utils as connector_utils;
use crate::{ use crate::{
configs::settings, configs::settings,
consts,
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
headers, headers,
services::{self, request, ConnectorIntegration, ConnectorValidation}, services::{self, request, ConnectorIntegration, ConnectorValidation},
@ -21,8 +28,11 @@ use crate::{
}; };
#[cfg(feature = "frm")] #[cfg(feature = "frm")]
use crate::{ use crate::{
consts,
events::connector_api_logs::ConnectorEvent, 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, ErrorResponse, Response,
},
utils::BytesExt, utils::BytesExt,
}; };
@ -61,6 +71,7 @@ impl ConnectorCommon for Signifyd {
connectors.signifyd.base_url.as_ref() connectors.signifyd.base_url.as_ref()
} }
#[cfg(feature = "frm")]
fn get_auth_header( fn get_auth_header(
&self, &self,
auth_type: &types::ConnectorAuthType, auth_type: &types::ConnectorAuthType,
@ -646,26 +657,101 @@ impl
} }
} }
#[cfg(feature = "frm")]
#[async_trait::async_trait] #[async_trait::async_trait]
impl api::IncomingWebhook for Signifyd { impl api::IncomingWebhook for Signifyd {
fn get_webhook_object_reference_id( fn get_webhook_source_verification_algorithm(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, _request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, 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<Vec<u8>, 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<Vec<u8>, 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<bool, errors::ConnectorError> {
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<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented)) let resource: signifyd::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( fn get_webhook_event_type(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> { ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented)) let resource: signifyd::SignifydWebhookBody = request
.body
.parse_struct("SignifydWebhookBody")
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
Ok(api::IncomingWebhookEvent::from(resource.review_disposition))
} }
fn get_webhook_resource_object( fn get_webhook_resource_object(
&self, &self,
_request: &api::IncomingWebhookRequestDetails<'_>, request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> { ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
Err(report!(errors::ConnectorError::WebhooksNotImplemented)) let resource: signifyd::SignifydWebhookBody = request
.body
.parse_struct("SignifydWebhookBody")
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
Ok(Box::new(resource))
} }
} }

View File

@ -13,7 +13,7 @@ use crate::{
}, },
core::{errors, fraud_check::types as core_types}, core::{errors, fraud_check::types as core_types},
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, ResponseId, ResponseRouterData,
}, },
}; };
@ -399,7 +399,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ
field_name: "frm_metadata", field_name: "frm_metadata",
})? })?
.parse_value("Signifyd 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 ship_address = item.get_shipping_address()?;
let street_addr = ship_address.get_line1()?; let street_addr = ship_address.get_line1()?;
let city_addr = ship_address.get_city()?; let city_addr = ship_address.get_city()?;
@ -705,3 +707,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<ReviewDisposition> for api::IncomingWebhookEvent {
fn from(value: ReviewDisposition) -> Self {
match value {
ReviewDisposition::Fraudulent => Self::FrmRejected,
ReviewDisposition::Good => Self::FrmApproved,
}
}
}

View File

@ -677,6 +677,118 @@ 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,
object_ref_id: api::ObjectReferenceId,
business_profile: diesel_models::business_profile::BusinessProfile,
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
if source_verified {
let payment_attempt =
get_payment_attempt_from_object_reference_id(&state, object_ref_id, &merchant_account)
.await?;
let payment_response = match event_type {
webhooks::IncomingWebhookEvent::FrmApproved => {
Box::pin(payments::payments_core::<
api::Capture,
api::PaymentsResponse,
_,
_,
_,
>(
state.clone(),
req_state,
merchant_account.clone(),
key_store.clone(),
payments::PaymentApprove,
api::PaymentsCaptureRequest {
payment_id: payment_attempt.payment_id,
amount_to_capture: payment_attempt.amount_to_capture,
..Default::default()
},
services::api::AuthFlow::Merchant,
payments::CallConnectorAction::Trigger,
None,
HeaderPayload::default(),
))
.await?
}
webhooks::IncomingWebhookEvent::FrmRejected => {
Box::pin(payments::payments_core::<
api::Void,
api::PaymentsResponse,
_,
_,
_,
>(
state.clone(),
req_state,
merchant_account.clone(),
key_store.clone(),
payments::PaymentReject,
api::PaymentsCancelRequest {
payment_id: payment_attempt.payment_id.clone(),
cancellation_reason: Some(
"Rejected by merchant based on FRM decision".to_string(),
),
..Default::default()
},
services::api::AuthFlow::Merchant,
payments::CallConnectorAction::Trigger,
None,
HeaderPayload::default(),
))
.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<enums::EventType> = 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(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");
Err(report!(
errors::ApiErrorResponse::WebhookAuthenticationFailed
))
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn disputes_incoming_webhook_flow( pub async fn disputes_incoming_webhook_flow(
@ -1828,6 +1940,18 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType>(
.await .await
.attach_printable("Incoming webhook flow for external authentication failed")? .attach_printable("Incoming webhook flow for external authentication failed")?
} }
api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow(
state.clone(),
req_state,
merchant_account,
key_store,
source_verified,
event_type,
object_ref_id,
business_profile,
))
.await
.attach_printable("Incoming webhook flow for fraud check failed")?,
_ => Err(errors::ApiErrorResponse::InternalServerError) _ => Err(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unsupported Flow Type received in incoming webhooks")?, .attach_printable("Unsupported Flow Type received in incoming webhooks")?,
@ -1901,6 +2025,19 @@ fn get_connector_by_connector_name(
) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> { ) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> {
let authentication_connector = let authentication_connector =
api_models::enums::convert_authentication_connector(connector_name); api_models::enums::convert_authentication_connector(connector_name);
#[cfg(feature = "frm")]
{
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 (connector, connector_name) = if authentication_connector.is_some() {
let authentication_connector_data = let authentication_connector_data =
api::AuthenticationConnectorData::get_connector_by_name(connector_name)?; api::AuthenticationConnectorData::get_connector_by_name(connector_name)?;