mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(compatibility): add support for stripe compatible webhooks (#1728)
This commit is contained in:
		 Abhishek Marrivagu
					Abhishek Marrivagu
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							14c2d72509
						
					
				
				
					commit
					87ae99f7f2
				
			| @ -98,9 +98,3 @@ pub enum OutgoingWebhookContent { | |||||||
|     RefundDetails(refunds::RefundResponse), |     RefundDetails(refunds::RefundResponse), | ||||||
|     DisputeDetails(Box<disputes::DisputeResponse>), |     DisputeDetails(Box<disputes::DisputeResponse>), | ||||||
| } | } | ||||||
|  |  | ||||||
| pub trait OutgoingWebhookType: |  | ||||||
|     Serialize + From<OutgoingWebhook> + Sync + Send + std::fmt::Debug |  | ||||||
| { |  | ||||||
| } |  | ||||||
| impl OutgoingWebhookType for OutgoingWebhook {} |  | ||||||
|  | |||||||
| @ -2,21 +2,70 @@ use api_models::{ | |||||||
|     enums::DisputeStatus, |     enums::DisputeStatus, | ||||||
|     webhooks::{self as api}, |     webhooks::{self as api}, | ||||||
| }; | }; | ||||||
|  | use common_utils::{crypto::SignMessage, date_time, ext_traits}; | ||||||
|  | use error_stack::{IntoReport, ResultExt}; | ||||||
|  | use router_env::logger; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  |  | ||||||
| use super::{ | use super::{ | ||||||
|     payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse, |     payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse, | ||||||
| }; | }; | ||||||
|  | use crate::{ | ||||||
|  |     core::{errors, webhooks::types::OutgoingWebhookType}, | ||||||
|  |     headers, | ||||||
|  |     services::request::Maskable, | ||||||
|  | }; | ||||||
|  |  | ||||||
| #[derive(Serialize, Debug)] | #[derive(Serialize, Debug)] | ||||||
| pub struct StripeOutgoingWebhook { | pub struct StripeOutgoingWebhook { | ||||||
|     id: Option<String>, |     id: String, | ||||||
|     #[serde(rename = "type")] |     #[serde(rename = "type")] | ||||||
|     stype: &'static str, |     stype: &'static str, | ||||||
|  |     object: &'static str, | ||||||
|     data: StripeWebhookObject, |     data: StripeWebhookObject, | ||||||
|  |     created: u64, | ||||||
|  |     // api_version: "2019-11-05", // not used | ||||||
| } | } | ||||||
|  |  | ||||||
| impl api::OutgoingWebhookType for StripeOutgoingWebhook {} | impl OutgoingWebhookType for StripeOutgoingWebhook { | ||||||
|  |     fn get_outgoing_webhooks_signature( | ||||||
|  |         &self, | ||||||
|  |         payment_response_hash_key: Option<String>, | ||||||
|  |     ) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError> { | ||||||
|  |         let timestamp = self.created; | ||||||
|  |  | ||||||
|  |         let payment_response_hash_key = payment_response_hash_key | ||||||
|  |             .ok_or(errors::WebhooksFlowError::MerchantConfigNotFound) | ||||||
|  |             .into_report() | ||||||
|  |             .attach_printable("For stripe compatibility payment_response_hash_key is mandatory")?; | ||||||
|  |  | ||||||
|  |         let webhook_signature_payload = | ||||||
|  |             ext_traits::Encode::<serde_json::Value>::encode_to_string_of_json(self) | ||||||
|  |                 .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) | ||||||
|  |                 .attach_printable("failed encoding outgoing webhook payload")?; | ||||||
|  |  | ||||||
|  |         let new_signature_payload = format!("{timestamp}.{webhook_signature_payload}"); | ||||||
|  |         let v1 = hex::encode( | ||||||
|  |             common_utils::crypto::HmacSha256::sign_message( | ||||||
|  |                 &common_utils::crypto::HmacSha256, | ||||||
|  |                 payment_response_hash_key.as_bytes(), | ||||||
|  |                 new_signature_payload.as_bytes(), | ||||||
|  |             ) | ||||||
|  |             .change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed) | ||||||
|  |             .attach_printable("Failed to sign the message")?, | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         let t = timestamp; | ||||||
|  |         Ok(Some(format!("t={t},v1={v1}"))) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String) { | ||||||
|  |         header.push(( | ||||||
|  |             headers::STRIPE_COMPATIBLE_WEBHOOK_SIGNATURE.to_string(), | ||||||
|  |             signature.into(), | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Serialize, Debug)] | #[derive(Serialize, Debug)] | ||||||
| #[serde(tag = "type", content = "object", rename_all = "snake_case")] | #[serde(tag = "type", content = "object", rename_all = "snake_case")] | ||||||
| @ -76,13 +125,46 @@ impl From<DisputeStatus> for StripeDisputeStatus { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn get_stripe_event_type(event_type: api_models::enums::EventType) -> &'static str { | ||||||
|  |     match event_type { | ||||||
|  |         api_models::enums::EventType::PaymentSucceeded => "payment_intent.succeeded", | ||||||
|  |         api_models::enums::EventType::PaymentFailed => "payment_intent.payment_failed", | ||||||
|  |         api_models::enums::EventType::PaymentProcessing => "payment_intent.processing", | ||||||
|  |  | ||||||
|  |         // the below are not really stripe compatible because stripe doesn't provide this | ||||||
|  |         api_models::enums::EventType::ActionRequired => "action.required", | ||||||
|  |         api_models::enums::EventType::RefundSucceeded => "refund.succeeded", | ||||||
|  |         api_models::enums::EventType::RefundFailed => "refund.failed", | ||||||
|  |         api_models::enums::EventType::DisputeOpened => "dispute.failed", | ||||||
|  |         api_models::enums::EventType::DisputeExpired => "dispute.expired", | ||||||
|  |         api_models::enums::EventType::DisputeAccepted => "dispute.accepted", | ||||||
|  |         api_models::enums::EventType::DisputeCancelled => "dispute.cancelled", | ||||||
|  |         api_models::enums::EventType::DisputeChallenged => "dispute.challenged", | ||||||
|  |         api_models::enums::EventType::DisputeWon => "dispute.won", | ||||||
|  |         api_models::enums::EventType::DisputeLost => "dispute.lost", | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl From<api::OutgoingWebhook> for StripeOutgoingWebhook { | impl From<api::OutgoingWebhook> for StripeOutgoingWebhook { | ||||||
|     fn from(value: api::OutgoingWebhook) -> Self { |     fn from(value: api::OutgoingWebhook) -> Self { | ||||||
|         let data: StripeWebhookObject = value.content.into(); |  | ||||||
|         Self { |         Self { | ||||||
|             id: data.get_id(), |             id: value.event_id, | ||||||
|             stype: "webhook_endpoint", |             stype: get_stripe_event_type(value.event_type), | ||||||
|             data, |             data: StripeWebhookObject::from(value.content), | ||||||
|  |             object: "event", | ||||||
|  |             // put this conversion it into a function | ||||||
|  |             created: u64::try_from(value.timestamp.assume_utc().unix_timestamp()).unwrap_or_else( | ||||||
|  |                 |error| { | ||||||
|  |                     logger::error!( | ||||||
|  |                         %error, | ||||||
|  |                         "incorrect value for `webhook.timestamp` provided {}", value.timestamp | ||||||
|  |                     ); | ||||||
|  |                     // Current timestamp converted to Unix timestamp should have a positive value | ||||||
|  |                     // for many years to come | ||||||
|  |                     u64::try_from(date_time::now().assume_utc().unix_timestamp()) | ||||||
|  |                         .unwrap_or_default() | ||||||
|  |                 }, | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -100,13 +182,3 @@ impl From<api::OutgoingWebhookContent> for StripeWebhookObject { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl StripeWebhookObject { |  | ||||||
|     fn get_id(&self) -> Option<String> { |  | ||||||
|         match self { |  | ||||||
|             Self::PaymentIntent(p) => p.id.to_owned(), |  | ||||||
|             Self::Refund(r) => Some(r.id.to_owned()), |  | ||||||
|             Self::Dispute(d) => Some(d.id.to_owned()), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| pub mod transformers; | pub mod types; | ||||||
| pub mod utils; | pub mod utils; | ||||||
|  |  | ||||||
| use common_utils::{crypto::SignMessage, ext_traits}; |  | ||||||
| use error_stack::{report, IntoReport, ResultExt}; | use error_stack::{report, IntoReport, ResultExt}; | ||||||
| use masking::ExposeInterface; | use masking::ExposeInterface; | ||||||
| use router_env::{instrument, tracing}; | use router_env::{instrument, tracing}; | ||||||
| @ -14,11 +13,11 @@ use crate::{ | |||||||
|         errors::{self, CustomResult, RouterResponse}, |         errors::{self, CustomResult, RouterResponse}, | ||||||
|         payments, refunds, |         payments, refunds, | ||||||
|     }, |     }, | ||||||
|     headers, logger, |     logger, | ||||||
|     routes::AppState, |     routes::AppState, | ||||||
|     services, |     services, | ||||||
|     types::{ |     types::{ | ||||||
|         self, api, domain, |         self as router_types, api, domain, | ||||||
|         storage::{self, enums}, |         storage::{self, enums}, | ||||||
|         transformers::{ForeignInto, ForeignTryInto}, |         transformers::{ForeignInto, ForeignTryInto}, | ||||||
|     }, |     }, | ||||||
| @ -29,7 +28,7 @@ const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; | |||||||
| const MERCHANT_ID: &str = "merchant_id"; | const MERCHANT_ID: &str = "merchant_id"; | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>( | pub async fn payments_incoming_webhook_flow<W: types::OutgoingWebhookType>( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     key_store: domain::MerchantKeyStore, |     key_store: domain::MerchantKeyStore, | ||||||
| @ -108,7 +107,7 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>( | pub async fn refunds_incoming_webhook_flow<W: types::OutgoingWebhookType>( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     key_store: domain::MerchantKeyStore, |     key_store: domain::MerchantKeyStore, | ||||||
| @ -325,7 +324,7 @@ pub async fn get_or_update_dispute_object( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>( | pub async fn disputes_incoming_webhook_flow<W: types::OutgoingWebhookType>( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     webhook_details: api::IncomingWebhookDetails, |     webhook_details: api::IncomingWebhookDetails, | ||||||
| @ -388,7 +387,7 @@ pub async fn disputes_incoming_webhook_flow<W: api::OutgoingWebhookType>( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>( | async fn bank_transfer_webhook_flow<W: types::OutgoingWebhookType>( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     key_store: domain::MerchantKeyStore, |     key_store: domain::MerchantKeyStore, | ||||||
| @ -465,7 +464,7 @@ async fn bank_transfer_webhook_flow<W: api::OutgoingWebhookType>( | |||||||
|  |  | ||||||
| #[allow(clippy::too_many_arguments)] | #[allow(clippy::too_many_arguments)] | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookType>( | pub async fn create_event_and_trigger_outgoing_webhook<W: types::OutgoingWebhookType>( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     event_type: enums::EventType, |     event_type: enums::EventType, | ||||||
| @ -506,34 +505,9 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy | |||||||
|             timestamp: event.created_at, |             timestamp: event.created_at, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let webhook_signature_payload = |  | ||||||
|             ext_traits::Encode::<serde_json::Value>::encode_to_string_of_json(&outgoing_webhook) |  | ||||||
|                 .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) |  | ||||||
|                 .attach_printable("failed encoding outgoing webhook payload")?; |  | ||||||
|  |  | ||||||
|         let outgoing_webhooks_signature = merchant_account |  | ||||||
|             .payment_response_hash_key |  | ||||||
|             .clone() |  | ||||||
|             .map(|key| { |  | ||||||
|                 common_utils::crypto::HmacSha512::sign_message( |  | ||||||
|                     &common_utils::crypto::HmacSha512, |  | ||||||
|                     key.as_bytes(), |  | ||||||
|                     webhook_signature_payload.as_bytes(), |  | ||||||
|                 ) |  | ||||||
|             }) |  | ||||||
|             .transpose() |  | ||||||
|             .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) |  | ||||||
|             .attach_printable("Failed to sign the message")? |  | ||||||
|             .map(hex::encode); |  | ||||||
|  |  | ||||||
|         arbiter.spawn(async move { |         arbiter.spawn(async move { | ||||||
|             let result = trigger_webhook_to_merchant::<W>( |             let result = | ||||||
|                 merchant_account, |                 trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, &state).await; | ||||||
|                 outgoing_webhook, |  | ||||||
|                 outgoing_webhooks_signature, |  | ||||||
|                 &state, |  | ||||||
|             ) |  | ||||||
|             .await; |  | ||||||
|  |  | ||||||
|             if let Err(e) = result { |             if let Err(e) = result { | ||||||
|                 logger::error!(?e); |                 logger::error!(?e); | ||||||
| @ -544,10 +518,9 @@ pub async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookTy | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | pub async fn trigger_webhook_to_merchant<W: types::OutgoingWebhookType>( | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|     webhook: api::OutgoingWebhook, |     webhook: api::OutgoingWebhook, | ||||||
|     outgoing_webhooks_signature: Option<String>, |  | ||||||
|     state: &AppState, |     state: &AppState, | ||||||
| ) -> CustomResult<(), errors::WebhooksFlowError> { | ) -> CustomResult<(), errors::WebhooksFlowError> { | ||||||
|     let webhook_details_json = merchant_account |     let webhook_details_json = merchant_account | ||||||
| @ -570,7 +543,10 @@ pub async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | |||||||
|  |  | ||||||
|     let transformed_outgoing_webhook = W::from(webhook); |     let transformed_outgoing_webhook = W::from(webhook); | ||||||
|  |  | ||||||
|     let transformed_outgoing_webhook_string = types::RequestBody::log_and_get_request_body( |     let outgoing_webhooks_signature = transformed_outgoing_webhook | ||||||
|  |         .get_outgoing_webhooks_signature(merchant_account.payment_response_hash_key.clone())?; | ||||||
|  |  | ||||||
|  |     let transformed_outgoing_webhook_string = router_types::RequestBody::log_and_get_request_body( | ||||||
|         &transformed_outgoing_webhook, |         &transformed_outgoing_webhook, | ||||||
|         Encode::<serde_json::Value>::encode_to_string_of_json, |         Encode::<serde_json::Value>::encode_to_string_of_json, | ||||||
|     ) |     ) | ||||||
| @ -583,7 +559,7 @@ pub async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | |||||||
|     )]; |     )]; | ||||||
|  |  | ||||||
|     if let Some(signature) = outgoing_webhooks_signature { |     if let Some(signature) = outgoing_webhooks_signature { | ||||||
|         header.push((headers::X_WEBHOOK_SIGNATURE.to_string(), signature.into())) |         W::add_webhook_header(&mut header, signature) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let request = services::RequestBuilder::new() |     let request = services::RequestBuilder::new() | ||||||
| @ -649,7 +625,7 @@ pub async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn webhooks_core<W: api::OutgoingWebhookType>( | pub async fn webhooks_core<W: types::OutgoingWebhookType>( | ||||||
|     state: &AppState, |     state: &AppState, | ||||||
|     req: &actix_web::HttpRequest, |     req: &actix_web::HttpRequest, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|  | |||||||
| @ -1 +0,0 @@ | |||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								crates/router/src/core/webhooks/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								crates/router/src/core/webhooks/types.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | use api_models::webhooks; | ||||||
|  | use common_utils::{crypto::SignMessage, ext_traits}; | ||||||
|  | use error_stack::ResultExt; | ||||||
|  | use serde::Serialize; | ||||||
|  |  | ||||||
|  | use crate::{core::errors, headers, services::request::Maskable}; | ||||||
|  |  | ||||||
|  | pub trait OutgoingWebhookType: | ||||||
|  |     Serialize + From<webhooks::OutgoingWebhook> + Sync + Send + std::fmt::Debug | ||||||
|  | { | ||||||
|  |     fn get_outgoing_webhooks_signature( | ||||||
|  |         &self, | ||||||
|  |         payment_response_hash_key: Option<String>, | ||||||
|  |     ) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError>; | ||||||
|  |  | ||||||
|  |     fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl OutgoingWebhookType for webhooks::OutgoingWebhook { | ||||||
|  |     fn get_outgoing_webhooks_signature( | ||||||
|  |         &self, | ||||||
|  |         payment_response_hash_key: Option<String>, | ||||||
|  |     ) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError> { | ||||||
|  |         let webhook_signature_payload = | ||||||
|  |             ext_traits::Encode::<serde_json::Value>::encode_to_string_of_json(self) | ||||||
|  |                 .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) | ||||||
|  |                 .attach_printable("failed encoding outgoing webhook payload")?; | ||||||
|  |  | ||||||
|  |         Ok(payment_response_hash_key | ||||||
|  |             .map(|key| { | ||||||
|  |                 common_utils::crypto::HmacSha512::sign_message( | ||||||
|  |                     &common_utils::crypto::HmacSha512, | ||||||
|  |                     key.as_bytes(), | ||||||
|  |                     webhook_signature_payload.as_bytes(), | ||||||
|  |                 ) | ||||||
|  |             }) | ||||||
|  |             .transpose() | ||||||
|  |             .change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed) | ||||||
|  |             .attach_printable("Failed to sign the message")? | ||||||
|  |             .map(hex::encode)) | ||||||
|  |     } | ||||||
|  |     fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String) { | ||||||
|  |         header.push((headers::X_WEBHOOK_SIGNATURE.to_string(), signature.into())) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -65,6 +65,8 @@ pub mod headers { | |||||||
|     pub const X_ACCEPT_VERSION: &str = "X-Accept-Version"; |     pub const X_ACCEPT_VERSION: &str = "X-Accept-Version"; | ||||||
|     pub const X_DATE: &str = "X-Date"; |     pub const X_DATE: &str = "X-Date"; | ||||||
|     pub const X_WEBHOOK_SIGNATURE: &str = "X-Webhook-Signature-512"; |     pub const X_WEBHOOK_SIGNATURE: &str = "X-Webhook-Signature-512"; | ||||||
|  |  | ||||||
|  |     pub const STRIPE_COMPATIBLE_WEBHOOK_SIGNATURE: &str = "Stripe-Signature"; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub mod pii { | pub mod pii { | ||||||
|  | |||||||
| @ -3,13 +3,12 @@ use router_env::{instrument, tracing, Flow}; | |||||||
|  |  | ||||||
| use super::app::AppState; | use super::app::AppState; | ||||||
| use crate::{ | use crate::{ | ||||||
|     core::webhooks, |     core::webhooks::{self, types}, | ||||||
|     services::{api, authentication as auth}, |     services::{api, authentication as auth}, | ||||||
|     types::api as api_types, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #[instrument(skip_all, fields(flow = ?Flow::IncomingWebhookReceive))] | #[instrument(skip_all, fields(flow = ?Flow::IncomingWebhookReceive))] | ||||||
| pub async fn receive_incoming_webhook<W: api_types::OutgoingWebhookType>( | pub async fn receive_incoming_webhook<W: types::OutgoingWebhookType>( | ||||||
|     state: web::Data<AppState>, |     state: web::Data<AppState>, | ||||||
|     req: HttpRequest, |     req: HttpRequest, | ||||||
|     body: web::Bytes, |     body: web::Bytes, | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use api_models::admin::MerchantConnectorWebhookDetails; | use api_models::admin::MerchantConnectorWebhookDetails; | ||||||
| pub use api_models::webhooks::{ | pub use api_models::webhooks::{ | ||||||
|     IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig, ObjectReferenceId, |     IncomingWebhookDetails, IncomingWebhookEvent, MerchantWebhookConfig, ObjectReferenceId, | ||||||
|     OutgoingWebhook, OutgoingWebhookContent, OutgoingWebhookType, WebhookFlow, |     OutgoingWebhook, OutgoingWebhookContent, WebhookFlow, | ||||||
| }; | }; | ||||||
| use common_utils::ext_traits::ValueExt; | use common_utils::ext_traits::ValueExt; | ||||||
| use error_stack::{IntoReport, ResultExt}; | use error_stack::{IntoReport, ResultExt}; | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user