mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
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:
@ -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> {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -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,
|
||||
FraudCheck,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@ -119,6 +122,9 @@ impl From<IncomingWebhookEvent> for WebhookFlow {
|
||||
IncomingWebhookEvent::SourceChargeable
|
||||
| IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer,
|
||||
IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication,
|
||||
IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => {
|
||||
Self::FraudCheck
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,19 +2,24 @@ pub mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
use common_utils::request::RequestContent;
|
||||
use error_stack::{report, ResultExt};
|
||||
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::FrmTransactionRouterDataRequest;
|
||||
use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::errors::{self, CustomResult},
|
||||
headers,
|
||||
services::{self, request, ConnectorIntegration, ConnectorValidation},
|
||||
services::{self, ConnectorIntegration, ConnectorValidation},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
@ -22,8 +27,13 @@ use crate::{
|
||||
};
|
||||
#[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,
|
||||
};
|
||||
|
||||
@ -31,6 +41,7 @@ use crate::{
|
||||
pub struct Riskified;
|
||||
|
||||
impl Riskified {
|
||||
#[cfg(feature = "frm")]
|
||||
pub fn generate_authorization_signature(
|
||||
&self,
|
||||
auth: &riskified::RiskifiedAuthType,
|
||||
@ -53,6 +64,7 @@ impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Ri
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
{
|
||||
#[cfg(feature = "frm")]
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
@ -122,7 +134,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,
|
||||
@ -268,11 +280,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")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,14 +294,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(),
|
||||
})?,
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,26 +549,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<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> {
|
||||
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<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(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> 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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<RiskifiedWebhookStatus> for api::IncomingWebhookEvent {
|
||||
fn from(value: RiskifiedWebhookStatus) -> Self {
|
||||
match value {
|
||||
RiskifiedWebhookStatus::Declined => Self::FrmRejected,
|
||||
RiskifiedWebhookStatus::Approved => Self::FrmApproved,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
pub mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
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};
|
||||
#[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},
|
||||
@ -21,8 +28,11 @@ use crate::{
|
||||
};
|
||||
#[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, ErrorResponse, Response,
|
||||
},
|
||||
utils::BytesExt,
|
||||
};
|
||||
|
||||
@ -61,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,
|
||||
@ -646,26 +657,101 @@ impl
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
#[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<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> {
|
||||
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(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> 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(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> 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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,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,
|
||||
},
|
||||
};
|
||||
@ -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()?;
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn disputes_incoming_webhook_flow(
|
||||
@ -1828,6 +1940,18 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType>(
|
||||
.await
|
||||
.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)
|
||||
.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> {
|
||||
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);
|
||||
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 =
|
||||
api::AuthenticationConnectorData::get_connector_by_name(connector_name)?;
|
||||
|
||||
Reference in New Issue
Block a user