mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-30 09:38:33 +08:00 
			
		
		
		
	fix(router): outgoing webhook api call (#1193)
This commit is contained in:
		 Sai Harsha Vardhan
					Sai Harsha Vardhan
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							1f52a66452
						
					
				
				
					commit
					31a52d8058
				
			| @ -91,5 +91,8 @@ pub enum OutgoingWebhookContent { | |||||||
|     DisputeDetails(Box<disputes::DisputeResponse>), |     DisputeDetails(Box<disputes::DisputeResponse>), | ||||||
| } | } | ||||||
|  |  | ||||||
| pub trait OutgoingWebhookType: Serialize + From<OutgoingWebhook> + Sync + Send {} | pub trait OutgoingWebhookType: | ||||||
|  |     Serialize + From<OutgoingWebhook> + Sync + Send + std::fmt::Debug | ||||||
|  | { | ||||||
|  | } | ||||||
| impl OutgoingWebhookType for OutgoingWebhook {} | impl OutgoingWebhookType for OutgoingWebhook {} | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ impl From<StripeBillingDetails> for payments::Address { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone)] | #[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)] | ||||||
| pub struct StripeCard { | pub struct StripeCard { | ||||||
|     pub number: cards::CardNumber, |     pub number: cards::CardNumber, | ||||||
|     pub exp_month: pii::Secret<String>, |     pub exp_month: pii::Secret<String>, | ||||||
| @ -221,7 +221,7 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Default, Eq, PartialEq, Serialize, Deserialize)] | #[derive(Clone, Default, Eq, PartialEq, Serialize, Deserialize, Debug)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
| pub enum StripePaymentStatus { | pub enum StripePaymentStatus { | ||||||
|     Succeeded, |     Succeeded, | ||||||
| @ -288,7 +288,7 @@ pub struct StripeCaptureRequest { | |||||||
|     pub amount_to_capture: Option<i64>, |     pub amount_to_capture: Option<i64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Serialize)] | #[derive(Default, Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct StripePaymentIntentResponse { | pub struct StripePaymentIntentResponse { | ||||||
|     pub id: Option<String>, |     pub id: Option<String>, | ||||||
|     pub object: &'static str, |     pub object: &'static str, | ||||||
| @ -328,7 +328,7 @@ pub struct StripePaymentIntentResponse { | |||||||
|     pub last_payment_error: Option<LastPaymentError>, |     pub last_payment_error: Option<LastPaymentError>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Serialize)] | #[derive(Default, Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct LastPaymentError { | pub struct LastPaymentError { | ||||||
|     charge: Option<String>, |     charge: Option<String>, | ||||||
|     code: Option<String>, |     code: Option<String>, | ||||||
| @ -402,7 +402,7 @@ impl From<payments::PaymentsResponse> for StripePaymentIntentResponse { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Serialize)] | #[derive(Default, Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct StripePaymentMethod { | pub struct StripePaymentMethod { | ||||||
|     #[serde(rename = "id")] |     #[serde(rename = "id")] | ||||||
|     payment_method_id: String, |     payment_method_id: String, | ||||||
| @ -414,7 +414,7 @@ pub struct StripePaymentMethod { | |||||||
|     livemode: bool, |     livemode: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Serialize)] | #[derive(Default, Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct Charges { | pub struct Charges { | ||||||
|     object: &'static str, |     object: &'static str, | ||||||
|     data: Vec<String>, |     data: Vec<String>, | ||||||
| @ -604,13 +604,13 @@ impl ForeignFrom<Option<Request3DS>> for api_models::enums::AuthenticationType { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Default, Eq, PartialEq, Serialize)] | #[derive(Default, Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct RedirectUrl { | pub struct RedirectUrl { | ||||||
|     pub return_url: Option<String>, |     pub return_url: Option<String>, | ||||||
|     pub url: Option<String>, |     pub url: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Eq, PartialEq, Serialize)] | #[derive(Eq, PartialEq, Serialize, Debug)] | ||||||
| pub struct StripeNextAction { | pub struct StripeNextAction { | ||||||
|     #[serde(rename = "type")] |     #[serde(rename = "type")] | ||||||
|     stype: payments::NextActionType, |     stype: payments::NextActionType, | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ pub struct StripeUpdateRefundRequest { | |||||||
|     pub metadata: Option<pii::SecretSerdeValue>, |     pub metadata: Option<pii::SecretSerdeValue>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Serialize, PartialEq, Eq)] | #[derive(Clone, Serialize, PartialEq, Eq, Debug)] | ||||||
| pub struct StripeRefundResponse { | pub struct StripeRefundResponse { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub amount: i64, |     pub amount: i64, | ||||||
| @ -31,7 +31,7 @@ pub struct StripeRefundResponse { | |||||||
|     pub metadata: pii::SecretSerdeValue, |     pub metadata: pii::SecretSerdeValue, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Serialize, Deserialize, Eq, PartialEq)] | #[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
| pub enum StripeRefundStatus { | pub enum StripeRefundStatus { | ||||||
|     Succeeded, |     Succeeded, | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ use super::{ | |||||||
|     payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse, |     payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize, Debug)] | ||||||
| pub struct StripeOutgoingWebhook { | pub struct StripeOutgoingWebhook { | ||||||
|     id: Option<String>, |     id: Option<String>, | ||||||
|     #[serde(rename = "type")] |     #[serde(rename = "type")] | ||||||
| @ -18,7 +18,7 @@ pub struct StripeOutgoingWebhook { | |||||||
|  |  | ||||||
| impl api::OutgoingWebhookType for StripeOutgoingWebhook {} | impl api::OutgoingWebhookType for StripeOutgoingWebhook {} | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize, Debug)] | ||||||
| #[serde(tag = "type", content = "object", rename_all = "snake_case")] | #[serde(tag = "type", content = "object", rename_all = "snake_case")] | ||||||
| pub enum StripeWebhookObject { | pub enum StripeWebhookObject { | ||||||
|     PaymentIntent(StripePaymentIntentResponse), |     PaymentIntent(StripePaymentIntentResponse), | ||||||
| @ -26,7 +26,7 @@ pub enum StripeWebhookObject { | |||||||
|     Dispute(StripeDisputeResponse), |     Dispute(StripeDisputeResponse), | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize, Debug)] | ||||||
| pub struct StripeDisputeResponse { | pub struct StripeDisputeResponse { | ||||||
|     pub id: String, |     pub id: String, | ||||||
|     pub amount: String, |     pub amount: String, | ||||||
| @ -36,7 +36,7 @@ pub struct StripeDisputeResponse { | |||||||
|     pub status: StripeDisputeStatus, |     pub status: StripeDisputeStatus, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize, Debug)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
| pub enum StripeDisputeStatus { | pub enum StripeDisputeStatus { | ||||||
|     WarningNeedsResponse, |     WarningNeedsResponse, | ||||||
|  | |||||||
| @ -460,6 +460,8 @@ pub enum WebhooksFlowError { | |||||||
|     NotImplemented, |     NotImplemented, | ||||||
|     #[error("Dispute webhook status validation failed")] |     #[error("Dispute webhook status validation failed")] | ||||||
|     DisputeWebhookValidationFailed, |     DisputeWebhookValidationFailed, | ||||||
|  |     #[error("Outgoing webhook body encoding failed")] | ||||||
|  |     OutgoingWebhookEncodingFailed, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "detailed_errors")] | #[cfg(feature = "detailed_errors")] | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ use crate::{ | |||||||
|         errors::{self, CustomResult, RouterResponse}, |         errors::{self, CustomResult, RouterResponse}, | ||||||
|         payments, refunds, |         payments, refunds, | ||||||
|     }, |     }, | ||||||
|     db::StorageInterface, |  | ||||||
|     logger, |     logger, | ||||||
|     routes::AppState, |     routes::AppState, | ||||||
|     services, |     services, | ||||||
| @ -24,7 +23,7 @@ use crate::{ | |||||||
|     utils::{generate_id, Encode, OptionExt, ValueExt}, |     utils::{generate_id, Encode, OptionExt, ValueExt}, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const OUTGOING_WEBHOOK_TIMEOUT_MS: u64 = 5000; | const OUTGOING_WEBHOOK_TIMEOUT_SECS: u64 = 5; | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>( | async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>( | ||||||
| @ -403,8 +402,7 @@ async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookType>( | |||||||
|  |  | ||||||
|         arbiter.spawn(async move { |         arbiter.spawn(async move { | ||||||
|             let result = |             let result = | ||||||
|                 trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, state.store) |                 trigger_webhook_to_merchant::<W>(merchant_account, outgoing_webhook, &state).await; | ||||||
|                     .await; |  | ||||||
|  |  | ||||||
|             if let Err(e) = result { |             if let Err(e) = result { | ||||||
|                 logger::error!(?e); |                 logger::error!(?e); | ||||||
| @ -418,7 +416,7 @@ async fn create_event_and_trigger_outgoing_webhook<W: api::OutgoingWebhookType>( | |||||||
| async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | ||||||
|     merchant_account: storage::MerchantAccount, |     merchant_account: storage::MerchantAccount, | ||||||
|     webhook: api::OutgoingWebhook, |     webhook: api::OutgoingWebhook, | ||||||
|     db: Box<dyn StorageInterface>, |     state: &AppState, | ||||||
| ) -> CustomResult<(), errors::WebhooksFlowError> { | ) -> CustomResult<(), errors::WebhooksFlowError> { | ||||||
|     let webhook_details_json = merchant_account |     let webhook_details_json = merchant_account | ||||||
|         .webhook_details |         .webhook_details | ||||||
| @ -440,29 +438,38 @@ async fn trigger_webhook_to_merchant<W: api::OutgoingWebhookType>( | |||||||
|  |  | ||||||
|     let transformed_outgoing_webhook = W::from(webhook); |     let transformed_outgoing_webhook = W::from(webhook); | ||||||
|  |  | ||||||
|     let response = reqwest::Client::new() |     let transformed_outgoing_webhook_string = | ||||||
|         .post(&webhook_url) |         Encode::<serde_json::Value>::encode_to_string_of_json(&transformed_outgoing_webhook) | ||||||
|         .header(reqwest::header::CONTENT_TYPE, "application/json") |             .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed) | ||||||
|         .json(&transformed_outgoing_webhook) |             .attach_printable("There was an issue when encoding the outgoing webhook body")?; | ||||||
|         .timeout(core::time::Duration::from_millis( |  | ||||||
|             OUTGOING_WEBHOOK_TIMEOUT_MS, |     let request = services::RequestBuilder::new() | ||||||
|         )) |         .method(services::Method::Post) | ||||||
|         .send() |         .url(&webhook_url) | ||||||
|         .await; |         .attach_default_headers() | ||||||
|  |         .headers(vec![( | ||||||
|  |             reqwest::header::CONTENT_TYPE.to_string(), | ||||||
|  |             "application/json".into(), | ||||||
|  |         )]) | ||||||
|  |         .body(Some(transformed_outgoing_webhook_string)) | ||||||
|  |         .build(); | ||||||
|  |  | ||||||
|  |     let response = | ||||||
|  |         services::api::send_request(state, request, Some(OUTGOING_WEBHOOK_TIMEOUT_SECS)).await; | ||||||
|  |  | ||||||
|     match response { |     match response { | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             // [#217]: Schedule webhook for retry. |             // [#217]: Schedule webhook for retry. | ||||||
|             Err(e) |             Err(e).change_context(errors::WebhooksFlowError::CallToMerchantFailed)?; | ||||||
|                 .into_report() |  | ||||||
|                 .change_context(errors::WebhooksFlowError::CallToMerchantFailed)?; |  | ||||||
|         } |         } | ||||||
|         Ok(res) => { |         Ok(res) => { | ||||||
|             if res.status().is_success() { |             if res.status().is_success() { | ||||||
|                 let update_event = storage::EventUpdate::UpdateWebhookNotified { |                 let update_event = storage::EventUpdate::UpdateWebhookNotified { | ||||||
|                     is_webhook_notified: Some(true), |                     is_webhook_notified: Some(true), | ||||||
|                 }; |                 }; | ||||||
|                 db.update_event(outgoing_webhook_event_id, update_event) |                 state | ||||||
|  |                     .store | ||||||
|  |                     .update_event(outgoing_webhook_event_id, update_event) | ||||||
|                     .await |                     .await | ||||||
|                     .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)?; |                     .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)?; | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -287,7 +287,7 @@ pub async fn call_connector_api( | |||||||
| ) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> { | ) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> { | ||||||
|     let current_time = Instant::now(); |     let current_time = Instant::now(); | ||||||
|  |  | ||||||
|     let response = send_request(state, request).await; |     let response = send_request(state, request, None).await; | ||||||
|  |  | ||||||
|     let elapsed_time = current_time.elapsed(); |     let elapsed_time = current_time.elapsed(); | ||||||
|     logger::info!(request_time=?elapsed_time); |     logger::info!(request_time=?elapsed_time); | ||||||
| @ -296,9 +296,10 @@ pub async fn call_connector_api( | |||||||
| } | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| async fn send_request( | pub async fn send_request( | ||||||
|     state: &AppState, |     state: &AppState, | ||||||
|     request: Request, |     request: Request, | ||||||
|  |     option_timeout_secs: Option<u64>, | ||||||
| ) -> CustomResult<reqwest::Response, errors::ApiClientError> { | ) -> CustomResult<reqwest::Response, errors::ApiClientError> { | ||||||
|     logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request); |     logger::debug!(method=?request.method, headers=?request.headers, payload=?request.payload, ?request); | ||||||
|     let url = &request.url; |     let url = &request.url; | ||||||
| @ -356,7 +357,9 @@ async fn send_request( | |||||||
|         Method::Delete => client.delete(url), |         Method::Delete => client.delete(url), | ||||||
|     } |     } | ||||||
|     .add_headers(headers) |     .add_headers(headers) | ||||||
|     .timeout(Duration::from_secs(crate::consts::REQUEST_TIME_OUT)) |     .timeout(Duration::from_secs( | ||||||
|  |         option_timeout_secs.unwrap_or(crate::consts::REQUEST_TIME_OUT), | ||||||
|  |     )) | ||||||
|     .send() |     .send() | ||||||
|     .await |     .await | ||||||
|     .map_err(|error| match error { |     .map_err(|error| match error { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user