mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +08:00
feat(webhooks): store request and response payloads in events table (#4029)
This commit is contained in:
@ -1,17 +1,23 @@
|
||||
use api_models::webhooks;
|
||||
use common_utils::{crypto::SignMessage, ext_traits::Encode};
|
||||
use error_stack::ResultExt;
|
||||
use masking::Secret;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{core::errors, headers, services::request::Maskable, types::storage::enums};
|
||||
|
||||
pub struct OutgoingWebhookPayloadWithSignature {
|
||||
pub payload: Secret<String>,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
|
||||
pub trait OutgoingWebhookType:
|
||||
Serialize + From<webhooks::OutgoingWebhook> + Sync + Send + std::fmt::Debug + 'static
|
||||
{
|
||||
fn get_outgoing_webhooks_signature(
|
||||
&self,
|
||||
payment_response_hash_key: Option<String>,
|
||||
) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError>;
|
||||
payment_response_hash_key: Option<impl AsRef<[u8]>>,
|
||||
) -> errors::CustomResult<OutgoingWebhookPayloadWithSignature, errors::WebhooksFlowError>;
|
||||
|
||||
fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String);
|
||||
}
|
||||
@ -19,26 +25,32 @@ pub trait OutgoingWebhookType:
|
||||
impl OutgoingWebhookType for webhooks::OutgoingWebhook {
|
||||
fn get_outgoing_webhooks_signature(
|
||||
&self,
|
||||
payment_response_hash_key: Option<String>,
|
||||
) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError> {
|
||||
payment_response_hash_key: Option<impl AsRef<[u8]>>,
|
||||
) -> errors::CustomResult<OutgoingWebhookPayloadWithSignature, errors::WebhooksFlowError> {
|
||||
let webhook_signature_payload = self
|
||||
.encode_to_string_of_json()
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
|
||||
.attach_printable("failed encoding outgoing webhook payload")?;
|
||||
|
||||
Ok(payment_response_hash_key
|
||||
let signature = payment_response_hash_key
|
||||
.map(|key| {
|
||||
common_utils::crypto::HmacSha512::sign_message(
|
||||
&common_utils::crypto::HmacSha512,
|
||||
key.as_bytes(),
|
||||
key.as_ref(),
|
||||
webhook_signature_payload.as_bytes(),
|
||||
)
|
||||
})
|
||||
.transpose()
|
||||
.change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed)
|
||||
.attach_printable("Failed to sign the message")?
|
||||
.map(hex::encode))
|
||||
.map(hex::encode);
|
||||
|
||||
Ok(OutgoingWebhookPayloadWithSignature {
|
||||
payload: webhook_signature_payload.into(),
|
||||
signature,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String) {
|
||||
header.push((headers::X_WEBHOOK_SIGNATURE.to_string(), signature.into()))
|
||||
}
|
||||
@ -58,4 +70,18 @@ pub(crate) struct OutgoingWebhookTrackingData {
|
||||
pub(crate) event_class: enums::EventClass,
|
||||
pub(crate) primary_object_id: String,
|
||||
pub(crate) primary_object_type: enums::EventObjectType,
|
||||
pub(crate) initial_attempt_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct OutgoingWebhookRequestContent {
|
||||
pub(crate) payload: Secret<String>,
|
||||
pub(crate) headers: Vec<(String, Secret<String>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub(crate) struct OutgoingWebhookResponseContent {
|
||||
pub(crate) payload: Secret<String>,
|
||||
pub(crate) headers: Vec<(String, Secret<String>)>,
|
||||
pub(crate) status_code: u16,
|
||||
}
|
||||
|
||||
@ -120,3 +120,27 @@ pub async fn construct_webhook_router_data<'a>(
|
||||
};
|
||||
Ok(router_data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_idempotent_event_id(
|
||||
primary_object_id: &str,
|
||||
event_type: crate::types::storage::enums::EventType,
|
||||
delivery_attempt: super::types::WebhookDeliveryAttempt,
|
||||
) -> String {
|
||||
use super::types::WebhookDeliveryAttempt;
|
||||
|
||||
const EVENT_ID_SUFFIX_LENGTH: usize = 8;
|
||||
|
||||
let common_prefix = format!("{primary_object_id}_{event_type}");
|
||||
match delivery_attempt {
|
||||
WebhookDeliveryAttempt::InitialAttempt => common_prefix,
|
||||
WebhookDeliveryAttempt::AutomaticRetry => {
|
||||
common_utils::generate_id(EVENT_ID_SUFFIX_LENGTH, &common_prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn generate_event_id() -> String {
|
||||
common_utils::generate_time_ordered_id("evt")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user