mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	refactor: Move trait IncomingWebhook to hyperswitch_interfaces (#5191)
This commit is contained in:
		
							
								
								
									
										229
									
								
								crates/hyperswitch_interfaces/src/webhooks.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								crates/hyperswitch_interfaces/src/webhooks.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,229 @@ | ||||
| //! Webhooks interface | ||||
|  | ||||
| use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; | ||||
| use error_stack::ResultExt; | ||||
| use hyperswitch_domain_models::api::ApplicationResponse; | ||||
| use masking::{ExposeInterface, Secret}; | ||||
|  | ||||
| use crate::{api::ConnectorCommon, errors}; | ||||
|  | ||||
| /// struct IncomingWebhookRequestDetails | ||||
| #[derive(Debug)] | ||||
| pub struct IncomingWebhookRequestDetails<'a> { | ||||
|     /// method | ||||
|     pub method: http::Method, | ||||
|     /// uri | ||||
|     pub uri: http::Uri, | ||||
|     /// headers | ||||
|     pub headers: &'a actix_web::http::header::HeaderMap, | ||||
|     /// body | ||||
|     pub body: &'a [u8], | ||||
|     /// query_params | ||||
|     pub query_params: String, | ||||
| } | ||||
|  | ||||
| /// Trait defining incoming webhook | ||||
| #[async_trait::async_trait] | ||||
| pub trait IncomingWebhook: ConnectorCommon + Sync { | ||||
|     /// fn get_webhook_body_decoding_algorithm | ||||
|     fn get_webhook_body_decoding_algorithm( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<Box<dyn crypto::DecodeMessage + Send>, errors::ConnectorError> { | ||||
|         Ok(Box::new(crypto::NoAlgorithm)) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_body_decoding_message | ||||
|     fn get_webhook_body_decoding_message( | ||||
|         &self, | ||||
|         request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         Ok(request.body.to_vec()) | ||||
|     } | ||||
|  | ||||
|     /// fn decode_webhook_body | ||||
|     async fn decode_webhook_body( | ||||
|         &self, | ||||
|         request: &IncomingWebhookRequestDetails<'_>, | ||||
|         merchant_id: &str, | ||||
|         connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>, | ||||
|         connector_name: &str, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         let algorithm = self.get_webhook_body_decoding_algorithm(request)?; | ||||
|  | ||||
|         let message = self | ||||
|             .get_webhook_body_decoding_message(request) | ||||
|             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||
|         let secret = self | ||||
|             .get_webhook_source_verification_merchant_secret( | ||||
|                 merchant_id, | ||||
|                 connector_name, | ||||
|                 connector_webhook_details, | ||||
|             ) | ||||
|             .await | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|  | ||||
|         algorithm | ||||
|             .decode_message(&secret.secret, message.into()) | ||||
|             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_source_verification_algorithm | ||||
|     fn get_webhook_source_verification_algorithm( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> { | ||||
|         Ok(Box::new(crypto::NoAlgorithm)) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_source_verification_merchant_secret | ||||
|     async fn get_webhook_source_verification_merchant_secret( | ||||
|         &self, | ||||
|         merchant_id: &str, | ||||
|         connector_name: &str, | ||||
|         connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>, | ||||
|     ) -> CustomResult<api_models::webhooks::ConnectorWebhookSecrets, errors::ConnectorError> { | ||||
|         let debug_suffix = format!( | ||||
|             "For merchant_id: {}, and connector_name: {}", | ||||
|             merchant_id, connector_name | ||||
|         ); | ||||
|         let default_secret = "default_secret".to_string(); | ||||
|         let merchant_secret = match connector_webhook_details { | ||||
|             Some(merchant_connector_webhook_details) => { | ||||
|                 let connector_webhook_details = merchant_connector_webhook_details | ||||
|                     .parse_value::<api_models::admin::MerchantConnectorWebhookDetails>( | ||||
|                         "MerchantConnectorWebhookDetails", | ||||
|                     ) | ||||
|                     .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed) | ||||
|                     .attach_printable_lazy(|| { | ||||
|                         format!( | ||||
|                             "Deserializing MerchantConnectorWebhookDetails failed {}", | ||||
|                             debug_suffix | ||||
|                         ) | ||||
|                     })?; | ||||
|                 api_models::webhooks::ConnectorWebhookSecrets { | ||||
|                     secret: connector_webhook_details | ||||
|                         .merchant_secret | ||||
|                         .expose() | ||||
|                         .into_bytes(), | ||||
|                     additional_secret: connector_webhook_details.additional_secret, | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             None => api_models::webhooks::ConnectorWebhookSecrets { | ||||
|                 secret: default_secret.into_bytes(), | ||||
|                 additional_secret: None, | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
|         //need to fetch merchant secret from config table with caching in future for enhanced performance | ||||
|  | ||||
|         //If merchant has not set the secret for webhook source verification, "default_secret" is returned. | ||||
|         //So it will fail during verification step and goes to psync flow. | ||||
|         Ok(merchant_secret) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_source_verification_signature | ||||
|     fn get_webhook_source_verification_signature( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|         _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         Ok(Vec::new()) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_source_verification_message | ||||
|     fn get_webhook_source_verification_message( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|         _merchant_id: &str, | ||||
|         _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         Ok(Vec::new()) | ||||
|     } | ||||
|  | ||||
|     /// fn verify_webhook_source | ||||
|     async fn verify_webhook_source( | ||||
|         &self, | ||||
|         request: &IncomingWebhookRequestDetails<'_>, | ||||
|         merchant_id: &str, | ||||
|         connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>, | ||||
|         _connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>, | ||||
|         connector_name: &str, | ||||
|     ) -> CustomResult<bool, errors::ConnectorError> { | ||||
|         let algorithm = self | ||||
|             .get_webhook_source_verification_algorithm(request) | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|  | ||||
|         let connector_webhook_secrets = self | ||||
|             .get_webhook_source_verification_merchant_secret( | ||||
|                 merchant_id, | ||||
|                 connector_name, | ||||
|                 connector_webhook_details, | ||||
|             ) | ||||
|             .await | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|  | ||||
|         let signature = self | ||||
|             .get_webhook_source_verification_signature(request, &connector_webhook_secrets) | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|  | ||||
|         let message = self | ||||
|             .get_webhook_source_verification_message( | ||||
|                 request, | ||||
|                 merchant_id, | ||||
|                 &connector_webhook_secrets, | ||||
|             ) | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|  | ||||
|         algorithm | ||||
|             .verify_signature(&connector_webhook_secrets.secret, &signature, &message) | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) | ||||
|     } | ||||
|  | ||||
|     /// fn get_webhook_object_reference_id | ||||
|     fn get_webhook_object_reference_id( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError>; | ||||
|  | ||||
|     /// fn get_webhook_event_type | ||||
|     fn get_webhook_event_type( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError>; | ||||
|  | ||||
|     /// fn get_webhook_resource_object | ||||
|     fn get_webhook_resource_object( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError>; | ||||
|  | ||||
|     /// fn get_webhook_api_response | ||||
|     fn get_webhook_api_response( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<ApplicationResponse<serde_json::Value>, errors::ConnectorError> { | ||||
|         Ok(ApplicationResponse::StatusOk) | ||||
|     } | ||||
|  | ||||
|     /// fn get_dispute_details | ||||
|     fn get_dispute_details( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<crate::disputes::DisputePayload, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_dispute_details method".to_string()).into()) | ||||
|     } | ||||
|  | ||||
|     /// fn get_external_authentication_details | ||||
|     fn get_external_authentication_details( | ||||
|         &self, | ||||
|         _request: &IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<crate::authentication::ExternalAuthenticationPayload, errors::ConnectorError> | ||||
|     { | ||||
|         Err(errors::ConnectorError::NotImplemented( | ||||
|             "get_external_authentication_details method".to_string(), | ||||
|         ) | ||||
|         .into()) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 DEEPANSHU BANSAL
					DEEPANSHU BANSAL