mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	 6eb72e923e
			
		
	
	6eb72e923e
	
	
	
		
			
			Co-authored-by: Chikke Srujan <chikke.srujan@Chikke-Srujan-N7WRTY72X7.local> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
		
			
				
	
	
		
			241 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			241 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! 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: &common_utils::id_type::MerchantId,
 | |
|         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: &common_utils::id_type::MerchantId,
 | |
|         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: &common_utils::id_type::MerchantId,
 | |
|         _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: &common_utils::id_type::MerchantId,
 | |
|         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())
 | |
|     }
 | |
| 
 | |
|     /// fn get_mandate_details
 | |
|     fn get_mandate_details(
 | |
|         &self,
 | |
|         _request: &IncomingWebhookRequestDetails<'_>,
 | |
|     ) -> CustomResult<
 | |
|         Option<hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails>,
 | |
|         errors::ConnectorError,
 | |
|     > {
 | |
|         Ok(None)
 | |
|     }
 | |
| }
 |