mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +08:00 
			
		
		
		
	refactor(core): add error handling wrapper to wehbook (#6636)
This commit is contained in:
		| @ -282,6 +282,9 @@ impl Connector { | |||||||
|     pub fn is_pre_processing_required_before_authorize(&self) -> bool { |     pub fn is_pre_processing_required_before_authorize(&self) -> bool { | ||||||
|         matches!(self, Self::Airwallex) |         matches!(self, Self::Airwallex) | ||||||
|     } |     } | ||||||
|  |     pub fn should_acknowledge_webhook_for_resource_not_found_errors(&self) -> bool { | ||||||
|  |         matches!(self, Self::Adyenplatform) | ||||||
|  |     } | ||||||
|     #[cfg(feature = "dummy_connector")] |     #[cfg(feature = "dummy_connector")] | ||||||
|     pub fn validate_dummy_connector_enabled( |     pub fn validate_dummy_connector_enabled( | ||||||
|         &self, |         &self, | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ use hyperswitch_interfaces::{ | |||||||
|     errors, |     errors, | ||||||
|     events::connector_api_logs::ConnectorEvent, |     events::connector_api_logs::ConnectorEvent, | ||||||
|     types::{PaymentsAuthorizeType, Response}, |     types::{PaymentsAuthorizeType, Response}, | ||||||
|     webhooks, |     webhooks::{self, IncomingWebhookFlowError}, | ||||||
| }; | }; | ||||||
| use masking::{Mask, PeekInterface, Secret}; | use masking::{Mask, PeekInterface, Secret}; | ||||||
| use transformers as cashtocode; | use transformers as cashtocode; | ||||||
| @ -420,6 +420,7 @@ impl webhooks::IncomingWebhook for Cashtocode { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         request: &webhooks::IncomingWebhookRequestDetails<'_>, |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { |     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { | ||||||
|         let status = "EXECUTED".to_string(); |         let status = "EXECUTED".to_string(); | ||||||
|         let obj: transformers::CashtocodePaymentsSyncResponse = request |         let obj: transformers::CashtocodePaymentsSyncResponse = request | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ use hyperswitch_interfaces::{ | |||||||
|         PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, |         PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, | ||||||
|         RefundExecuteType, RefundSyncType, Response, |         RefundExecuteType, RefundSyncType, Response, | ||||||
|     }, |     }, | ||||||
|     webhooks, |     webhooks::{self, IncomingWebhookFlowError}, | ||||||
| }; | }; | ||||||
| use masking::{ExposeInterface, Mask, PeekInterface}; | use masking::{ExposeInterface, Mask, PeekInterface}; | ||||||
| use ring::hmac; | use ring::hmac; | ||||||
| @ -814,6 +814,7 @@ impl webhooks::IncomingWebhook for Worldline { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         request: &webhooks::IncomingWebhookRequestDetails<'_>, |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult< |     ) -> CustomResult< | ||||||
|         hyperswitch_domain_models::api::ApplicationResponse<serde_json::Value>, |         hyperswitch_domain_models::api::ApplicationResponse<serde_json::Value>, | ||||||
|         errors::ConnectorError, |         errors::ConnectorError, | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ use hyperswitch_interfaces::{ | |||||||
|     errors, |     errors, | ||||||
|     events::connector_api_logs::ConnectorEvent, |     events::connector_api_logs::ConnectorEvent, | ||||||
|     types::{PaymentsAuthorizeType, PaymentsSyncType, RefundExecuteType, RefundSyncType, Response}, |     types::{PaymentsAuthorizeType, PaymentsSyncType, RefundExecuteType, RefundSyncType, Response}, | ||||||
|     webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, |     webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, | ||||||
| }; | }; | ||||||
| use masking::{Mask, PeekInterface, Secret}; | use masking::{Mask, PeekInterface, Secret}; | ||||||
| use transformers::{self as zen, ZenPaymentStatus, ZenWebhookTxnType}; | use transformers::{self as zen, ZenPaymentStatus, ZenWebhookTxnType}; | ||||||
| @ -671,6 +671,7 @@ impl IncomingWebhook for Zen { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         _request: &IncomingWebhookRequestDetails<'_>, |         _request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { |     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { | ||||||
|         Ok(ApplicationResponse::Json(serde_json::json!({ |         Ok(ApplicationResponse::Json(serde_json::json!({ | ||||||
|             "status": "ok" |             "status": "ok" | ||||||
|  | |||||||
| @ -36,7 +36,7 @@ use hyperswitch_interfaces::{ | |||||||
|     errors, |     errors, | ||||||
|     events::connector_api_logs::ConnectorEvent, |     events::connector_api_logs::ConnectorEvent, | ||||||
|     types::{self, Response}, |     types::{self, Response}, | ||||||
|     webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, |     webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, | ||||||
| }; | }; | ||||||
| use masking::{ExposeInterface, Secret}; | use masking::{ExposeInterface, Secret}; | ||||||
| use transformers::{self as zsl, get_status}; | use transformers::{self as zsl, get_status}; | ||||||
| @ -442,6 +442,7 @@ impl IncomingWebhook for Zsl { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         _request: &IncomingWebhookRequestDetails<'_>, |         _request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { |     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { | ||||||
|         Ok(ApplicationResponse::TextPlain("CALLBACK-OK".to_string())) |         Ok(ApplicationResponse::TextPlain("CALLBACK-OK".to_string())) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,7 +2,9 @@ | |||||||
|  |  | ||||||
| use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; | use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; | ||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| use hyperswitch_domain_models::api::ApplicationResponse; | use hyperswitch_domain_models::{ | ||||||
|  |     api::ApplicationResponse, errors::api_error_response::ApiErrorResponse, | ||||||
|  | }; | ||||||
| use masking::{ExposeInterface, Secret}; | use masking::{ExposeInterface, Secret}; | ||||||
|  |  | ||||||
| use crate::{api::ConnectorCommon, errors}; | use crate::{api::ConnectorCommon, errors}; | ||||||
| @ -22,6 +24,30 @@ pub struct IncomingWebhookRequestDetails<'a> { | |||||||
|     pub query_params: String, |     pub query_params: String, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// IncomingWebhookFlowError enum defining the error type for incoming webhook | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum IncomingWebhookFlowError { | ||||||
|  |     /// Resource not found for the webhook | ||||||
|  |     ResourceNotFound, | ||||||
|  |     /// Internal error for the webhook | ||||||
|  |     InternalError, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<&ApiErrorResponse> for IncomingWebhookFlowError { | ||||||
|  |     fn from(api_error_response: &ApiErrorResponse) -> Self { | ||||||
|  |         match api_error_response { | ||||||
|  |             ApiErrorResponse::WebhookResourceNotFound | ||||||
|  |             | ApiErrorResponse::DisputeNotFound { .. } | ||||||
|  |             | ApiErrorResponse::PayoutNotFound | ||||||
|  |             | ApiErrorResponse::MandateNotFound | ||||||
|  |             | ApiErrorResponse::PaymentNotFound | ||||||
|  |             | ApiErrorResponse::RefundNotFound | ||||||
|  |             | ApiErrorResponse::AuthenticationNotFound { .. } => Self::ResourceNotFound, | ||||||
|  |             _ => Self::InternalError, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Trait defining incoming webhook | /// Trait defining incoming webhook | ||||||
| #[async_trait::async_trait] | #[async_trait::async_trait] | ||||||
| pub trait IncomingWebhook: ConnectorCommon + Sync { | pub trait IncomingWebhook: ConnectorCommon + Sync { | ||||||
| @ -203,6 +229,7 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         _request: &IncomingWebhookRequestDetails<'_>, |         _request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { |     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { | ||||||
|         Ok(ApplicationResponse::StatusOk) |         Ok(ApplicationResponse::StatusOk) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ use common_utils::{ | |||||||
| }; | }; | ||||||
| use diesel_models::{enums as storage_enums, enums}; | use diesel_models::{enums as storage_enums, enums}; | ||||||
| use error_stack::{report, ResultExt}; | use error_stack::{report, ResultExt}; | ||||||
|  | use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; | ||||||
| use masking::{ExposeInterface, Secret}; | use masking::{ExposeInterface, Secret}; | ||||||
| use ring::hmac; | use ring::hmac; | ||||||
| use router_env::{instrument, tracing}; | use router_env::{instrument, tracing}; | ||||||
| @ -1880,6 +1881,7 @@ impl api::IncomingWebhook for Adyen { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         _request: &api::IncomingWebhookRequestDetails<'_>, |         _request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> |     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> | ||||||
|     { |     { | ||||||
|         Ok(services::api::ApplicationResponse::TextPlain( |         Ok(services::api::ApplicationResponse::TextPlain( | ||||||
|  | |||||||
| @ -13,6 +13,8 @@ use error_stack::report; | |||||||
| use error_stack::ResultExt; | use error_stack::ResultExt; | ||||||
| #[cfg(feature = "payouts")] | #[cfg(feature = "payouts")] | ||||||
| use http::HeaderName; | use http::HeaderName; | ||||||
|  | use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; | ||||||
|  | use masking::Maskable; | ||||||
| #[cfg(feature = "payouts")] | #[cfg(feature = "payouts")] | ||||||
| use masking::Secret; | use masking::Secret; | ||||||
| #[cfg(feature = "payouts")] | #[cfg(feature = "payouts")] | ||||||
| @ -27,11 +29,7 @@ use crate::{ | |||||||
|     configs::settings, |     configs::settings, | ||||||
|     core::errors::{self, CustomResult}, |     core::errors::{self, CustomResult}, | ||||||
|     headers, |     headers, | ||||||
|     services::{ |     services::{self, request::Mask, ConnectorValidation}, | ||||||
|         self, |  | ||||||
|         request::{self, Mask}, |  | ||||||
|         ConnectorValidation, |  | ||||||
|     }, |  | ||||||
|     types::{ |     types::{ | ||||||
|         self, |         self, | ||||||
|         api::{self, ConnectorCommon}, |         api::{self, ConnectorCommon}, | ||||||
| @ -67,7 +65,7 @@ impl ConnectorCommon for Adyenplatform { | |||||||
|     fn get_auth_header( |     fn get_auth_header( | ||||||
|         &self, |         &self, | ||||||
|         auth_type: &types::ConnectorAuthType, |         auth_type: &types::ConnectorAuthType, | ||||||
|     ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { |     ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> { | ||||||
|         let auth = adyenplatform::AdyenplatformAuthType::try_from(auth_type) |         let auth = adyenplatform::AdyenplatformAuthType::try_from(auth_type) | ||||||
|             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; |             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; | ||||||
|         Ok(vec![( |         Ok(vec![( | ||||||
| @ -209,7 +207,7 @@ impl services::ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::P | |||||||
|         &self, |         &self, | ||||||
|         req: &types::PayoutsRouterData<api::PoFulfill>, |         req: &types::PayoutsRouterData<api::PoFulfill>, | ||||||
|         _connectors: &settings::Connectors, |         _connectors: &settings::Connectors, | ||||||
|     ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { |     ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> { | ||||||
|         let mut header = vec![( |         let mut header = vec![( | ||||||
|             headers::CONTENT_TYPE.to_string(), |             headers::CONTENT_TYPE.to_string(), | ||||||
|             types::PayoutFulfillType::get_content_type(self) |             types::PayoutFulfillType::get_content_type(self) | ||||||
| @ -401,6 +399,25 @@ impl api::IncomingWebhook for Adyenplatform { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn get_webhook_api_response( | ||||||
|  |         &self, | ||||||
|  |         _request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         error_kind: Option<IncomingWebhookFlowError>, | ||||||
|  |     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> | ||||||
|  |     { | ||||||
|  |         if error_kind.is_some() { | ||||||
|  |             Ok(services::api::ApplicationResponse::JsonWithHeaders(( | ||||||
|  |                 serde_json::Value::Null, | ||||||
|  |                 vec![( | ||||||
|  |                     "x-http-code".to_string(), | ||||||
|  |                     Maskable::Masked(Secret::new("404".to_string())), | ||||||
|  |                 )], | ||||||
|  |             ))) | ||||||
|  |         } else { | ||||||
|  |             Ok(services::api::ApplicationResponse::StatusOk) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn get_webhook_event_type( |     fn get_webhook_event_type( | ||||||
|         &self, |         &self, | ||||||
|         #[cfg(feature = "payouts")] request: &api::IncomingWebhookRequestDetails<'_>, |         #[cfg(feature = "payouts")] request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ use common_utils::{ | |||||||
| }; | }; | ||||||
| use diesel_models::enums; | use diesel_models::enums; | ||||||
| use error_stack::{report, Report, ResultExt}; | use error_stack::{report, Report, ResultExt}; | ||||||
|  | use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; | ||||||
| use masking::{ExposeInterface, PeekInterface, Secret}; | use masking::{ExposeInterface, PeekInterface, Secret}; | ||||||
| use ring::hmac; | use ring::hmac; | ||||||
| use sha1::{Digest, Sha1}; | use sha1::{Digest, Sha1}; | ||||||
| @ -980,6 +981,7 @@ impl api::IncomingWebhook for Braintree { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         _request: &api::IncomingWebhookRequestDetails<'_>, |         _request: &api::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> |     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> | ||||||
|     { |     { | ||||||
|         Ok(services::api::ApplicationResponse::TextPlain( |         Ok(services::api::ApplicationResponse::TextPlain( | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ use hyperswitch_domain_models::{ | |||||||
|     router_request_types::VerifyWebhookSourceRequestData, |     router_request_types::VerifyWebhookSourceRequestData, | ||||||
|     router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, |     router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, | ||||||
| }; | }; | ||||||
| use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails; | use hyperswitch_interfaces::webhooks::{IncomingWebhookFlowError, IncomingWebhookRequestDetails}; | ||||||
| use masking::{ExposeInterface, PeekInterface}; | use masking::{ExposeInterface, PeekInterface}; | ||||||
| use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId}; | use router_env::{instrument, metrics::add_attributes, tracing, tracing_actix_web::RequestId}; | ||||||
|  |  | ||||||
| @ -209,7 +209,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|             ); |             ); | ||||||
|  |  | ||||||
|             let response = connector |             let response = connector | ||||||
|                 .get_webhook_api_response(&request_details) |                 .get_webhook_api_response(&request_details, None) | ||||||
|                 .switch() |                 .switch() | ||||||
|                 .attach_printable("Failed while early return in case of event type parsing")?; |                 .attach_printable("Failed while early return in case of event type parsing")?; | ||||||
|  |  | ||||||
| @ -260,14 +260,25 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|         let merchant_connector_account = match merchant_connector_account { |         let merchant_connector_account = match merchant_connector_account { | ||||||
|             Some(merchant_connector_account) => merchant_connector_account, |             Some(merchant_connector_account) => merchant_connector_account, | ||||||
|             None => { |             None => { | ||||||
|                 Box::pin(helper_utils::get_mca_from_object_reference_id( |                 match Box::pin(helper_utils::get_mca_from_object_reference_id( | ||||||
|                     &state, |                     &state, | ||||||
|                     object_ref_id.clone(), |                     object_ref_id.clone(), | ||||||
|                     &merchant_account, |                     &merchant_account, | ||||||
|                     &connector_name, |                     &connector_name, | ||||||
|                     &key_store, |                     &key_store, | ||||||
|                 )) |                 )) | ||||||
|                 .await? |                 .await | ||||||
|  |                 { | ||||||
|  |                     Ok(mca) => mca, | ||||||
|  |                     Err(error) => { | ||||||
|  |                         return handle_incoming_webhook_error( | ||||||
|  |                             error, | ||||||
|  |                             &connector, | ||||||
|  |                             connector_name.as_str(), | ||||||
|  |                             &request_details, | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
| @ -358,7 +369,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 id: profile_id.get_string_repr().to_owned(), |                 id: profile_id.get_string_repr().to_owned(), | ||||||
|             })?; |             })?; | ||||||
|  |  | ||||||
|         match flow_type { |         let result_response = match flow_type { | ||||||
|             api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow( |             api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
|                 req_state, |                 req_state, | ||||||
| @ -372,7 +383,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 event_type, |                 event_type, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for payments failed")?, |             .attach_printable("Incoming webhook flow for payments failed"), | ||||||
|  |  | ||||||
|             api::WebhookFlow::Refund => Box::pin(refunds_incoming_webhook_flow( |             api::WebhookFlow::Refund => Box::pin(refunds_incoming_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
| @ -385,7 +396,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 event_type, |                 event_type, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for refunds failed")?, |             .attach_printable("Incoming webhook flow for refunds failed"), | ||||||
|  |  | ||||||
|             api::WebhookFlow::Dispute => Box::pin(disputes_incoming_webhook_flow( |             api::WebhookFlow::Dispute => Box::pin(disputes_incoming_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
| @ -399,7 +410,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 event_type, |                 event_type, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for disputes failed")?, |             .attach_printable("Incoming webhook flow for disputes failed"), | ||||||
|  |  | ||||||
|             api::WebhookFlow::BankTransfer => Box::pin(bank_transfer_webhook_flow( |             api::WebhookFlow::BankTransfer => Box::pin(bank_transfer_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
| @ -411,9 +422,9 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 source_verified, |                 source_verified, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming bank-transfer webhook flow failed")?, |             .attach_printable("Incoming bank-transfer webhook flow failed"), | ||||||
|  |  | ||||||
|             api::WebhookFlow::ReturnResponse => WebhookResponseTracker::NoEffect, |             api::WebhookFlow::ReturnResponse => Ok(WebhookResponseTracker::NoEffect), | ||||||
|  |  | ||||||
|             api::WebhookFlow::Mandate => Box::pin(mandates_incoming_webhook_flow( |             api::WebhookFlow::Mandate => Box::pin(mandates_incoming_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
| @ -425,7 +436,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 event_type, |                 event_type, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for mandates failed")?, |             .attach_printable("Incoming webhook flow for mandates failed"), | ||||||
|  |  | ||||||
|             api::WebhookFlow::ExternalAuthentication => { |             api::WebhookFlow::ExternalAuthentication => { | ||||||
|                 Box::pin(external_authentication_incoming_webhook_flow( |                 Box::pin(external_authentication_incoming_webhook_flow( | ||||||
| @ -442,7 +453,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                     merchant_connector_account, |                     merchant_connector_account, | ||||||
|                 )) |                 )) | ||||||
|                 .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( |             api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( | ||||||
|                 state.clone(), |                 state.clone(), | ||||||
| @ -455,7 +466,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 business_profile, |                 business_profile, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for fraud check failed")?, |             .attach_printable("Incoming webhook flow for fraud check failed"), | ||||||
|  |  | ||||||
|             #[cfg(feature = "payouts")] |             #[cfg(feature = "payouts")] | ||||||
|             api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( |             api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( | ||||||
| @ -468,10 +479,22 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|                 source_verified, |                 source_verified, | ||||||
|             )) |             )) | ||||||
|             .await |             .await | ||||||
|             .attach_printable("Incoming webhook flow for payouts failed")?, |             .attach_printable("Incoming webhook flow for payouts 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"), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         match result_response { | ||||||
|  |             Ok(response) => response, | ||||||
|  |             Err(error) => { | ||||||
|  |                 return handle_incoming_webhook_error( | ||||||
|  |                     error, | ||||||
|  |                     &connector, | ||||||
|  |                     connector_name.as_str(), | ||||||
|  |                     &request_details, | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         metrics::WEBHOOK_INCOMING_FILTERED_COUNT.add( |         metrics::WEBHOOK_INCOMING_FILTERED_COUNT.add( | ||||||
| @ -486,7 +509,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let response = connector |     let response = connector | ||||||
|         .get_webhook_api_response(&request_details) |         .get_webhook_api_response(&request_details, None) | ||||||
|         .switch() |         .switch() | ||||||
|         .attach_printable("Could not get incoming webhook api response from connector")?; |         .attach_printable("Could not get incoming webhook api response from connector")?; | ||||||
|  |  | ||||||
| @ -497,6 +520,44 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|     Ok((response, webhook_effect, serialized_request)) |     Ok((response, webhook_effect, serialized_request)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn handle_incoming_webhook_error( | ||||||
|  |     error: error_stack::Report<errors::ApiErrorResponse>, | ||||||
|  |     connector: &ConnectorEnum, | ||||||
|  |     connector_name: &str, | ||||||
|  |     request_details: &IncomingWebhookRequestDetails<'_>, | ||||||
|  | ) -> errors::RouterResult<( | ||||||
|  |     services::ApplicationResponse<serde_json::Value>, | ||||||
|  |     WebhookResponseTracker, | ||||||
|  |     serde_json::Value, | ||||||
|  | )> { | ||||||
|  |     logger::error!(?error, "Incoming webhook flow failed"); | ||||||
|  |  | ||||||
|  |     // fetch the connector enum from the connector name | ||||||
|  |     let connector_enum = api_models::connector_enums::Connector::from_str(connector_name) | ||||||
|  |         .change_context(errors::ApiErrorResponse::InvalidDataValue { | ||||||
|  |             field_name: "connector", | ||||||
|  |         }) | ||||||
|  |         .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?; | ||||||
|  |  | ||||||
|  |     // get the error response from the connector | ||||||
|  |     if connector_enum.should_acknowledge_webhook_for_resource_not_found_errors() { | ||||||
|  |         let response = connector | ||||||
|  |             .get_webhook_api_response( | ||||||
|  |                 request_details, | ||||||
|  |                 Some(IncomingWebhookFlowError::from(error.current_context())), | ||||||
|  |             ) | ||||||
|  |             .switch() | ||||||
|  |             .attach_printable("Failed to get incoming webhook api response from connector")?; | ||||||
|  |         Ok(( | ||||||
|  |             response, | ||||||
|  |             WebhookResponseTracker::NoEffect, | ||||||
|  |             serde_json::Value::Null, | ||||||
|  |         )) | ||||||
|  |     } else { | ||||||
|  |         Err(error) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[allow(clippy::too_many_arguments)] | #[allow(clippy::too_many_arguments)] | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| async fn payments_incoming_webhook_flow( | async fn payments_incoming_webhook_flow( | ||||||
|  | |||||||
| @ -192,7 +192,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|             ); |             ); | ||||||
|  |  | ||||||
|             let response = connector |             let response = connector | ||||||
|                 .get_webhook_api_response(&request_details) |                 .get_webhook_api_response(&request_details, None) | ||||||
|                 .switch() |                 .switch() | ||||||
|                 .attach_printable("Failed while early return in case of event type parsing")?; |                 .attach_printable("Failed while early return in case of event type parsing")?; | ||||||
|  |  | ||||||
| @ -367,7 +367,7 @@ async fn incoming_webhooks_core<W: types::OutgoingWebhookType>( | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let response = connector |     let response = connector | ||||||
|         .get_webhook_api_response(&request_details) |         .get_webhook_api_response(&request_details, None) | ||||||
|         .switch() |         .switch() | ||||||
|         .attach_printable("Could not get incoming webhook api response from connector")?; |         .attach_printable("Could not get incoming webhook api response from connector")?; | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| use common_utils::{crypto, errors::CustomResult, request::Request}; | use common_utils::{crypto, errors::CustomResult, request::Request}; | ||||||
| use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2}; | use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2}; | ||||||
| use hyperswitch_interfaces::{ | use hyperswitch_interfaces::{ | ||||||
|     authentication::ExternalAuthenticationPayload, connector_integration_v2::ConnectorIntegrationV2, |     authentication::ExternalAuthenticationPayload, | ||||||
|  |     connector_integration_v2::ConnectorIntegrationV2, webhooks::IncomingWebhookFlowError, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| use super::{BoxedConnectorIntegrationV2, ConnectorValidation}; | use super::{BoxedConnectorIntegrationV2, ConnectorValidation}; | ||||||
| @ -279,11 +280,12 @@ impl api::IncomingWebhook for ConnectorEnum { | |||||||
|     fn get_webhook_api_response( |     fn get_webhook_api_response( | ||||||
|         &self, |         &self, | ||||||
|         request: &IncomingWebhookRequestDetails<'_>, |         request: &IncomingWebhookRequestDetails<'_>, | ||||||
|  |         error_kind: Option<IncomingWebhookFlowError>, | ||||||
|     ) -> CustomResult<services_api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> |     ) -> CustomResult<services_api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> | ||||||
|     { |     { | ||||||
|         match self { |         match self { | ||||||
|             Self::Old(connector) => connector.get_webhook_api_response(request), |             Self::Old(connector) => connector.get_webhook_api_response(request, error_kind), | ||||||
|             Self::New(connector) => connector.get_webhook_api_response(request), |             Self::New(connector) => connector.get_webhook_api_response(request, error_kind), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Sakil Mostak
					Sakil Mostak