mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-04 05:59:48 +08:00
feat(webhooks): add support for custom outgoing webhook http headers (#5275)
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>
This commit is contained in:
@ -612,7 +612,7 @@ async fn payments_incoming_webhook_flow(
|
||||
// If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
let primary_object_created_at = payments_response.created;
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -623,7 +623,7 @@ async fn payments_incoming_webhook_flow(
|
||||
enums::EventObjectType::PaymentDetails,
|
||||
api::OutgoingWebhookContent::PaymentDetails(payments_response),
|
||||
primary_object_created_at,
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
};
|
||||
|
||||
@ -735,7 +735,7 @@ async fn payouts_incoming_webhook_flow(
|
||||
.attach_printable("Failed to fetch the payout create response")?,
|
||||
};
|
||||
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -746,7 +746,7 @@ async fn payouts_incoming_webhook_flow(
|
||||
enums::EventObjectType::PayoutDetails,
|
||||
api::OutgoingWebhookContent::PayoutDetails(payout_create_response),
|
||||
Some(updated_payout_attempt.created_at),
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@ -840,7 +840,7 @@ async fn refunds_incoming_webhook_flow(
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
let refund_response: api_models::refunds::RefundResponse =
|
||||
updated_refund.clone().foreign_into();
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -851,7 +851,7 @@ async fn refunds_incoming_webhook_flow(
|
||||
enums::EventObjectType::RefundDetails,
|
||||
api::OutgoingWebhookContent::RefundDetails(refund_response),
|
||||
Some(updated_refund.created_at),
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@ -1116,7 +1116,7 @@ async fn external_authentication_incoming_webhook_flow(
|
||||
// If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
let primary_object_created_at = payments_response.created;
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -1127,7 +1127,7 @@ async fn external_authentication_incoming_webhook_flow(
|
||||
enums::EventObjectType::PaymentDetails,
|
||||
api::OutgoingWebhookContent::PaymentDetails(payments_response),
|
||||
primary_object_created_at,
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
};
|
||||
let response = WebhookResponseTracker::Payment { payment_id, status };
|
||||
@ -1214,7 +1214,7 @@ async fn mandates_incoming_webhook_flow(
|
||||
);
|
||||
let event_type: Option<enums::EventType> = updated_mandate.mandate_status.foreign_into();
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -1225,7 +1225,7 @@ async fn mandates_incoming_webhook_flow(
|
||||
enums::EventObjectType::MandateDetails,
|
||||
api::OutgoingWebhookContent::MandateDetails(mandates_response),
|
||||
Some(updated_mandate.created_at),
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
Ok(WebhookResponseTracker::Mandate {
|
||||
@ -1323,7 +1323,7 @@ async fn frm_incoming_webhook_flow(
|
||||
let event_type: Option<enums::EventType> = payments_response.status.foreign_into();
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
let primary_object_created_at = payments_response.created;
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -1334,7 +1334,7 @@ async fn frm_incoming_webhook_flow(
|
||||
enums::EventObjectType::PaymentDetails,
|
||||
api::OutgoingWebhookContent::PaymentDetails(payments_response),
|
||||
primary_object_created_at,
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
};
|
||||
let response = WebhookResponseTracker::Payment { payment_id, status };
|
||||
@ -1397,7 +1397,7 @@ async fn disputes_incoming_webhook_flow(
|
||||
let disputes_response = Box::new(dispute_object.clone().foreign_into());
|
||||
let event_type: enums::EventType = dispute_object.dispute_status.foreign_into();
|
||||
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -1408,7 +1408,7 @@ async fn disputes_incoming_webhook_flow(
|
||||
enums::EventObjectType::DisputeDetails,
|
||||
api::OutgoingWebhookContent::DisputeDetails(disputes_response),
|
||||
Some(dispute_object.created_at),
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
metrics::INCOMING_DISPUTE_WEBHOOK_MERCHANT_NOTIFIED_METRIC.add(&metrics::CONTEXT, 1, &[]);
|
||||
Ok(WebhookResponseTracker::Dispute {
|
||||
@ -1489,7 +1489,7 @@ async fn bank_transfer_webhook_flow(
|
||||
// If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
|
||||
if let Some(outgoing_event_type) = event_type {
|
||||
let primary_object_created_at = payments_response.created;
|
||||
super::create_event_and_trigger_outgoing_webhook(
|
||||
Box::pin(super::create_event_and_trigger_outgoing_webhook(
|
||||
state,
|
||||
merchant_account,
|
||||
business_profile,
|
||||
@ -1500,7 +1500,7 @@ async fn bank_transfer_webhook_flow(
|
||||
enums::EventObjectType::PaymentDetails,
|
||||
api::OutgoingWebhookContent::PaymentDetails(payments_response),
|
||||
primary_object_created_at,
|
||||
)
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api_models::{
|
||||
webhook_events::{OutgoingWebhookRequestContent, OutgoingWebhookResponseContent},
|
||||
webhooks,
|
||||
@ -5,6 +7,7 @@ use api_models::{
|
||||
use common_utils::{ext_traits::Encode, request::RequestContent};
|
||||
use diesel_models::process_tracker::business_status;
|
||||
use error_stack::{report, ResultExt};
|
||||
use hyperswitch_domain_models::type_encryption::decrypt;
|
||||
use masking::{ExposeInterface, Mask, PeekInterface, Secret};
|
||||
use router_env::{
|
||||
instrument,
|
||||
@ -86,8 +89,10 @@ pub(crate) async fn create_event_and_trigger_outgoing_webhook(
|
||||
let request_content = get_outgoing_webhook_request(
|
||||
&merchant_account,
|
||||
outgoing_webhook,
|
||||
business_profile.payment_response_hash_key.as_deref(),
|
||||
&business_profile,
|
||||
merchant_key_store,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
|
||||
.attach_printable("Failed to construct outgoing webhook request content")?;
|
||||
|
||||
@ -546,15 +551,17 @@ fn get_webhook_url_from_business_profile(
|
||||
.map(ExposeInterface::expose)
|
||||
}
|
||||
|
||||
pub(crate) fn get_outgoing_webhook_request(
|
||||
pub(crate) async fn get_outgoing_webhook_request(
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
outgoing_webhook: api::OutgoingWebhook,
|
||||
payment_response_hash_key: Option<&str>,
|
||||
business_profile: &diesel_models::business_profile::BusinessProfile,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> CustomResult<OutgoingWebhookRequestContent, errors::WebhooksFlowError> {
|
||||
#[inline]
|
||||
fn get_outgoing_webhook_request_inner<WebhookType: types::OutgoingWebhookType>(
|
||||
async fn get_outgoing_webhook_request_inner<WebhookType: types::OutgoingWebhookType>(
|
||||
outgoing_webhook: api::OutgoingWebhook,
|
||||
payment_response_hash_key: Option<&str>,
|
||||
business_profile: &diesel_models::business_profile::BusinessProfile,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> CustomResult<OutgoingWebhookRequestContent, errors::WebhooksFlowError> {
|
||||
let mut headers = vec![(
|
||||
reqwest::header::CONTENT_TYPE.to_string(),
|
||||
@ -562,7 +569,31 @@ pub(crate) fn get_outgoing_webhook_request(
|
||||
)];
|
||||
|
||||
let transformed_outgoing_webhook = WebhookType::from(outgoing_webhook);
|
||||
|
||||
let payment_response_hash_key = business_profile.payment_response_hash_key.clone();
|
||||
let custom_headers = decrypt::<serde_json::Value, masking::WithType>(
|
||||
business_profile
|
||||
.outgoing_webhook_custom_http_headers
|
||||
.clone(),
|
||||
key_store.key.get_inner().peek(),
|
||||
)
|
||||
.await
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
|
||||
.attach_printable("Failed to decrypt outgoing webhook custom HTTP headers")?
|
||||
.map(|decrypted_value| {
|
||||
decrypted_value
|
||||
.into_inner()
|
||||
.expose()
|
||||
.parse_value::<HashMap<String, String>>("HashMap<String,String>")
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
|
||||
.attach_printable("Failed to deserialize outgoing webhook custom HTTP headers")
|
||||
})
|
||||
.transpose()?;
|
||||
if let Some(ref map) = custom_headers {
|
||||
headers.extend(
|
||||
map.iter()
|
||||
.map(|(key, value)| (key.clone(), value.clone().into_masked())),
|
||||
);
|
||||
};
|
||||
let outgoing_webhooks_signature = transformed_outgoing_webhook
|
||||
.get_outgoing_webhooks_signature(payment_response_hash_key)?;
|
||||
|
||||
@ -581,15 +612,22 @@ pub(crate) fn get_outgoing_webhook_request(
|
||||
|
||||
match merchant_account.get_compatible_connector() {
|
||||
#[cfg(feature = "stripe")]
|
||||
Some(api_models::enums::Connector::Stripe) => get_outgoing_webhook_request_inner::<
|
||||
stripe_webhooks::StripeOutgoingWebhook,
|
||||
>(
|
||||
outgoing_webhook, payment_response_hash_key
|
||||
),
|
||||
_ => get_outgoing_webhook_request_inner::<webhooks::OutgoingWebhook>(
|
||||
outgoing_webhook,
|
||||
payment_response_hash_key,
|
||||
),
|
||||
Some(api_models::enums::Connector::Stripe) => {
|
||||
get_outgoing_webhook_request_inner::<stripe_webhooks::StripeOutgoingWebhook>(
|
||||
outgoing_webhook,
|
||||
business_profile,
|
||||
key_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => {
|
||||
get_outgoing_webhook_request_inner::<webhooks::OutgoingWebhook>(
|
||||
outgoing_webhook,
|
||||
business_profile,
|
||||
key_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user