From 989b2c34e13053d2f842c69fc9ea8775fb61b517 Mon Sep 17 00:00:00 2001 From: Aishwariyaa Anand <124241367+Aishwariyaa-Anand@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:45:23 +0530 Subject: [PATCH] feat(webhook): add filter by event class and type (#7275) Co-authored-by: Aishwariyaa Anand Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/openapi_spec.json | 262 +++++++++--------- crates/api_models/src/webhook_events.rs | 9 + crates/common_enums/src/enums.rs | 53 +++- crates/diesel_models/src/query/events.rs | 19 ++ crates/openapi/src/openapi.rs | 1 + crates/openapi/src/routes/webhook_events.rs | 114 +++----- .../src/core/webhooks/webhook_events.rs | 62 ++++- crates/router/src/db/events.rs | 17 ++ crates/router/src/db/kafka_store.rs | 8 +- crates/router/src/routes/app.rs | 4 +- crates/router/src/routes/webhook_events.rs | 8 +- crates/router/src/types/transformers.rs | 8 +- 12 files changed, 346 insertions(+), 219 deletions(-) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 6e9d3b12a7..b414d6f426 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -5307,7 +5307,7 @@ } }, "/events/{merchant_id}": { - "get": { + "post": { "tags": [ "Event" ], @@ -5323,82 +5323,39 @@ "schema": { "type": "string" } - }, - { - "name": "created_after", - "in": "query", - "description": "Only include Events created after the specified time. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "nullable": true - } - }, - { - "name": "created_before", - "in": "query", - "description": "Only include Events created before the specified time. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "nullable": true - } - }, - { - "name": "limit", - "in": "query", - "description": "The maximum number of Events to include in the response. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true - } - }, - { - "name": "offset", - "in": "query", - "description": "The number of Events to skip when retrieving the list of Events.\n Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true - } - }, - { - "name": "object_id", - "in": "query", - "description": "Only include Events associated with the specified object (Payment Intent ID, Refund ID, etc.). Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "profile_id", - "in": "query", - "description": "Only include Events associated with the Profile identified by the specified Profile ID.", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - }, - { - "name": "is_delivered", - "in": "query", - "description": "Only include Events which are ultimately delivered to the merchant.", - "required": false, - "schema": { - "type": "boolean", - "nullable": true - } } ], + "requestBody": { + "description": "The constraints that can be applied when listing Events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventListConstraints" + }, + "examples": { + "example": { + "value": { + "created_after": "2023-01-01T00:00:00", + "created_before": "2023-01-31T23:59:59", + "event_classes": [ + "payments", + "refunds" + ], + "event_types": [ + "payment_succeeded" + ], + "is_delivered": true, + "limit": 5, + "object_id": "{{object_id}}", + "offset": 0, + "profile_id": "{{profile_id}}" + } + } + } + } + }, + "required": true + }, "responses": { "200": { "description": "List of Events retrieved successfully", @@ -5419,79 +5376,51 @@ } }, "/events/profile/list": { - "get": { + "post": { "tags": [ "Event" ], "summary": "Events - List", "description": "List all Events associated with a Profile.", "operationId": "List all Events associated with a Profile", - "parameters": [ - { - "name": "created_after", - "in": "query", - "description": "Only include Events created after the specified time. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "nullable": true + "requestBody": { + "description": "The constraints that can be applied when listing Events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EventListConstraints" + }, + "examples": { + "example": { + "value": { + "created_after": "2023-01-01T00:00:00", + "created_before": "2023-01-31T23:59:59", + "event_classes": [ + "payments", + "refunds" + ], + "event_types": [ + "payment_succeeded" + ], + "is_delivered": true, + "limit": 5, + "object_id": "{{object_id}}", + "offset": 0, + "profile_id": "{{profile_id}}" + } + } + } } }, - { - "name": "created_before", - "in": "query", - "description": "Only include Events created before the specified time. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "nullable": true - } - }, - { - "name": "limit", - "in": "query", - "description": "The maximum number of Events to include in the response. Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true - } - }, - { - "name": "offset", - "in": "query", - "description": "The number of Events to skip when retrieving the list of Events.\n Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "integer", - "format": "int64", - "nullable": true - } - }, - { - "name": "object_id", - "in": "query", - "description": "Only include Events associated with the specified object (Payment Intent ID, Refund ID, etc.). Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified.", - "required": false, - "schema": { - "type": "string", - "nullable": true - } - } - ], + "required": true + }, "responses": { "200": { "description": "List of Events retrieved successfully", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EventListItemResponse" - } + "$ref": "#/components/schemas/TotalEventsResponse" } } } @@ -12025,6 +11954,71 @@ "payouts" ] }, + "EventListConstraints": { + "type": "object", + "description": "The constraints to apply when filtering events.", + "properties": { + "created_after": { + "type": "string", + "format": "date-time", + "description": "Filter events created after the specified time.", + "nullable": true + }, + "created_before": { + "type": "string", + "format": "date-time", + "description": "Filter events created before the specified time.", + "nullable": true + }, + "limit": { + "type": "integer", + "format": "int32", + "description": "Include at most the specified number of events.", + "nullable": true, + "minimum": 0 + }, + "offset": { + "type": "integer", + "format": "int32", + "description": "Include events after the specified offset.", + "nullable": true, + "minimum": 0 + }, + "object_id": { + "type": "string", + "description": "Filter all events associated with the specified object identifier (Payment Intent ID,\nRefund ID, etc.)", + "nullable": true + }, + "profile_id": { + "type": "string", + "description": "Filter all events associated with the specified business profile ID.", + "nullable": true + }, + "event_classes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventClass" + }, + "description": "Filter events by their class.", + "uniqueItems": true, + "nullable": true + }, + "event_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventType" + }, + "description": "Filter events by their type.", + "uniqueItems": true, + "nullable": true + }, + "is_delivered": { + "type": "boolean", + "description": "Filter all events by `is_overall_delivery_successful` field of the event.", + "nullable": true + } + } + }, "EventListItemResponse": { "type": "object", "description": "The response body for each item when listing events.", diff --git a/crates/api_models/src/webhook_events.rs b/crates/api_models/src/webhook_events.rs index 5cc5c173ea..4c216dd66d 100644 --- a/crates/api_models/src/webhook_events.rs +++ b/crates/api_models/src/webhook_events.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use common_enums::{EventClass, EventType, WebhookDeliveryAttempt}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -29,6 +31,11 @@ pub struct EventListConstraints { #[schema(value_type = Option)] pub profile_id: Option, + /// Filter events by their class. + pub event_classes: Option>, + + /// Filter events by their type. + pub event_types: Option>, /// Filter all events by `is_overall_delivery_successful` field of the event. pub is_delivered: Option, } @@ -40,6 +47,8 @@ pub enum EventListConstraintsInternal { created_before: Option, limit: Option, offset: Option, + event_classes: Option>, + event_types: Option>, is_delivered: Option, }, ObjectIdFilter { diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 983eaea0cd..7780e710f3 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1,7 +1,10 @@ mod accounts; mod payments; mod ui; -use std::num::{ParseFloatError, TryFromIntError}; +use std::{ + collections::HashSet, + num::{ParseFloatError, TryFromIntError}, +}; pub use accounts::MerchantProductType; pub use payments::ProductType; @@ -1358,6 +1361,7 @@ impl Currency { Clone, Copy, Debug, + Hash, Eq, PartialEq, serde::Deserialize, @@ -1378,10 +1382,49 @@ pub enum EventClass { Payouts, } +impl EventClass { + #[inline] + pub fn event_types(self) -> HashSet { + match self { + Self::Payments => HashSet::from([ + EventType::PaymentSucceeded, + EventType::PaymentFailed, + EventType::PaymentProcessing, + EventType::PaymentCancelled, + EventType::PaymentAuthorized, + EventType::PaymentCaptured, + EventType::ActionRequired, + ]), + Self::Refunds => HashSet::from([EventType::RefundSucceeded, EventType::RefundFailed]), + Self::Disputes => HashSet::from([ + EventType::DisputeOpened, + EventType::DisputeExpired, + EventType::DisputeAccepted, + EventType::DisputeCancelled, + EventType::DisputeChallenged, + EventType::DisputeWon, + EventType::DisputeLost, + ]), + Self::Mandates => HashSet::from([EventType::MandateActive, EventType::MandateRevoked]), + #[cfg(feature = "payouts")] + Self::Payouts => HashSet::from([ + EventType::PayoutSuccess, + EventType::PayoutFailed, + EventType::PayoutInitiated, + EventType::PayoutProcessing, + EventType::PayoutCancelled, + EventType::PayoutExpired, + EventType::PayoutReversed, + ]), + } + } +} + #[derive( Clone, Copy, Debug, + Hash, Eq, PartialEq, serde::Deserialize, @@ -1393,6 +1436,7 @@ pub enum EventClass { #[router_derive::diesel_enum(storage_type = "db_enum")] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] +// Reminder: Whenever an EventType variant is added or removed, make sure to update the `event_types` method in `EventClass` pub enum EventType { /// Authorize + Capture success PaymentSucceeded, @@ -1414,12 +1458,19 @@ pub enum EventType { DisputeLost, MandateActive, MandateRevoked, + #[cfg(feature = "payouts")] PayoutSuccess, + #[cfg(feature = "payouts")] PayoutFailed, + #[cfg(feature = "payouts")] PayoutInitiated, + #[cfg(feature = "payouts")] PayoutProcessing, + #[cfg(feature = "payouts")] PayoutCancelled, + #[cfg(feature = "payouts")] PayoutExpired, + #[cfg(feature = "payouts")] PayoutReversed, } diff --git a/crates/diesel_models/src/query/events.rs b/crates/diesel_models/src/query/events.rs index f5d87cb993..a06fe6ff61 100644 --- a/crates/diesel_models/src/query/events.rs +++ b/crates/diesel_models/src/query/events.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use diesel::{ associations::HasTable, BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, }; @@ -49,6 +51,7 @@ impl Event { .await } + #[allow(clippy::too_many_arguments)] pub async fn list_initial_attempts_by_merchant_id_constraints( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, @@ -56,6 +59,7 @@ impl Event { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, ) -> StorageResult> { use async_bb8_diesel::AsyncRunQueryDsl; @@ -82,6 +86,7 @@ impl Event { (dsl::created_at, created_after, created_before), limit, offset, + event_types, is_delivered, ); @@ -129,6 +134,7 @@ impl Event { .await } + #[allow(clippy::too_many_arguments)] pub async fn list_initial_attempts_by_profile_id_constraints( conn: &PgPooledConn, profile_id: &common_utils::id_type::ProfileId, @@ -136,6 +142,7 @@ impl Event { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, ) -> StorageResult> { use async_bb8_diesel::AsyncRunQueryDsl; @@ -162,6 +169,7 @@ impl Event { (dsl::created_at, created_after, created_before), limit, offset, + event_types, is_delivered, ); @@ -221,6 +229,7 @@ impl Event { ), limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, ) -> T where @@ -238,6 +247,10 @@ impl Event { diesel::dsl::Eq, Output = T, >, + T: diesel::query_dsl::methods::FilterDsl< + diesel::dsl::EqAny>, + Output = T, + >, T: diesel::query_dsl::methods::FilterDsl< diesel::dsl::Eq, Output = T, @@ -259,6 +272,10 @@ impl Event { query = query.offset(offset); } + if !event_types.is_empty() { + query = query.filter(dsl::event_type.eq_any(event_types)); + } + if let Some(is_delivered) = is_delivered { query = query.filter(dsl::is_overall_delivery_successful.eq(is_delivered)); } @@ -272,6 +289,7 @@ impl Event { profile_id: Option, created_after: time::PrimitiveDateTime, created_before: time::PrimitiveDateTime, + event_types: HashSet, is_delivered: Option, ) -> StorageResult { use async_bb8_diesel::AsyncRunQueryDsl; @@ -298,6 +316,7 @@ impl Event { (dsl::created_at, created_after, created_before), None, None, + event_types, is_delivered, ); diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 40cedb1e40..dc3850b573 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -681,6 +681,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::blocklist::ListBlocklistQuery, api_models::enums::BlocklistDataKind, api_models::enums::ErrorCategory, + api_models::webhook_events::EventListConstraints, api_models::webhook_events::EventListItemResponse, api_models::webhook_events::EventRetrieveResponse, api_models::webhook_events::OutgoingWebhookRequestContent, diff --git a/crates/openapi/src/routes/webhook_events.rs b/crates/openapi/src/routes/webhook_events.rs index c1bc2419f4..3aef711d9e 100644 --- a/crates/openapi/src/routes/webhook_events.rs +++ b/crates/openapi/src/routes/webhook_events.rs @@ -2,7 +2,7 @@ /// /// List all Events associated with a Merchant Account or Profile. #[utoipa::path( - get, + post, path = "/events/{merchant_id}", params( ( @@ -10,46 +10,25 @@ Path, description = "The unique identifier for the Merchant Account." ), - ( - "created_after" = Option, - Query, - description = "Only include Events created after the specified time. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "created_before" = Option, - Query, - description = "Only include Events created before the specified time. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "limit" = Option, - Query, - description = "The maximum number of Events to include in the response. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "offset" = Option, - Query, - description = "The number of Events to skip when retrieving the list of Events. - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "object_id" = Option, - Query, - description = "Only include Events associated with the specified object (Payment Intent ID, Refund ID, etc.). \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "profile_id" = Option, - Query, - description = "Only include Events associated with the Profile identified by the specified Profile ID." - ), - ( - "is_delivered" = Option, - Query, - description = "Only include Events which are ultimately delivered to the merchant." - ), + ), + request_body( + content = EventListConstraints, + description = "The constraints that can be applied when listing Events.", + examples ( + ("example" = ( + value = json!({ + "created_after": "2023-01-01T00:00:00", + "created_before": "2023-01-31T23:59:59", + "limit": 5, + "offset": 0, + "object_id": "{{object_id}}", + "profile_id": "{{profile_id}}", + "event_classes": ["payments", "refunds"], + "event_types": ["payment_succeeded"], + "is_delivered": true + }) + )), + ) ), responses( (status = 200, description = "List of Events retrieved successfully", body = TotalEventsResponse), @@ -64,42 +43,29 @@ pub fn list_initial_webhook_delivery_attempts() {} /// /// List all Events associated with a Profile. #[utoipa::path( - get, + post, path = "/events/profile/list", - params( - ( - "created_after" = Option, - Query, - description = "Only include Events created after the specified time. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "created_before" = Option, - Query, - description = "Only include Events created before the specified time. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "limit" = Option, - Query, - description = "The maximum number of Events to include in the response. \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "offset" = Option, - Query, - description = "The number of Events to skip when retrieving the list of Events. - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), - ( - "object_id" = Option, - Query, - description = "Only include Events associated with the specified object (Payment Intent ID, Refund ID, etc.). \ - Either only `object_id` must be specified, or one or more of `created_after`, `created_before`, `limit` and `offset` must be specified." - ), + request_body( + content = EventListConstraints, + description = "The constraints that can be applied when listing Events.", + examples ( + ("example" = ( + value = json!({ + "created_after": "2023-01-01T00:00:00", + "created_before": "2023-01-31T23:59:59", + "limit": 5, + "offset": 0, + "object_id": "{{object_id}}", + "profile_id": "{{profile_id}}", + "event_classes": ["payments", "refunds"], + "event_types": ["payment_succeeded"], + "is_delivered": true + }) + )), + ) ), responses( - (status = 200, description = "List of Events retrieved successfully", body = Vec), + (status = 200, description = "List of Events retrieved successfully", body = TotalEventsResponse), ), tag = "Event", operation_id = "List all Events associated with a Profile", diff --git a/crates/router/src/core/webhooks/webhook_events.rs b/crates/router/src/core/webhooks/webhook_events.rs index 42e3f2d6b2..ccf3c8b4de 100644 --- a/crates/router/src/core/webhooks/webhook_events.rs +++ b/crates/router/src/core/webhooks/webhook_events.rs @@ -1,4 +1,6 @@ -use common_utils::{self, fp_utils}; +use std::collections::HashSet; + +use common_utils::{self, errors::CustomResult, fp_utils}; use error_stack::ResultExt; use masking::PeekInterface; use router_env::{instrument, tracing}; @@ -40,6 +42,8 @@ pub async fn list_initial_delivery_attempts( let events_list_begin_time = (now.date() - time::Duration::days(INITIAL_DELIVERY_ATTEMPTS_LIST_MAX_DAYS)).midnight(); + let mut updated_event_types: HashSet = HashSet::new(); + let events = match constraints { api_models::webhook_events::EventListConstraintsInternal::ObjectIdFilter { object_id } => { match account { @@ -64,6 +68,8 @@ pub async fn list_initial_delivery_attempts( created_before, limit, offset, + event_classes, + event_types, is_delivered } => { let limit = match limit { @@ -80,6 +86,12 @@ pub async fn list_initial_delivery_attempts( _ => None, }; + let event_classes = event_classes.unwrap_or(HashSet::new()); + updated_event_types = event_types.unwrap_or(HashSet::new()); + if !event_classes.is_empty() { + updated_event_types = finalize_event_types(event_classes, updated_event_types).await?; + } + fp_utils::when(!created_after.zip(created_before).map(|(created_after,created_before)| created_after<=created_before).unwrap_or(true), || { Err(errors::ApiErrorResponse::InvalidRequestData { message: "The `created_after` timestamp must be an earlier timestamp compared to the `created_before` timestamp".to_string() }) })?; @@ -115,6 +127,7 @@ pub async fn list_initial_delivery_attempts( created_before, limit, offset, + updated_event_types.clone(), is_delivered, &key_store, ) @@ -126,6 +139,7 @@ pub async fn list_initial_delivery_attempts( created_before, limit, offset, + updated_event_types.clone(), is_delivered, &key_store, ) @@ -154,6 +168,7 @@ pub async fn list_initial_delivery_attempts( profile_id, created_after, created_before, + updated_event_types, is_delivered, ) .await @@ -384,3 +399,48 @@ async fn get_account_and_key_store( } } } + +async fn finalize_event_types( + event_classes: HashSet, + mut event_types: HashSet, +) -> CustomResult, errors::ApiErrorResponse> { + // Examples: + // 1. event_classes = ["payments", "refunds"], event_types = ["payment_succeeded"] + // 2. event_classes = ["refunds"], event_types = ["payment_succeeded"] + + // Create possible_event_types based on event_classes + // Example 1: possible_event_types = ["payment_*", "refund_*"] + // Example 2: possible_event_types = ["refund_*"] + let possible_event_types = event_classes + .clone() + .into_iter() + .flat_map(common_enums::EventClass::event_types) + .collect::>(); + + if event_types.is_empty() { + return Ok(possible_event_types); + } + + // Extend event_types if disjoint with event_classes + // Example 1: event_types = ["payment_succeeded", "refund_*"], is_disjoint is used to extend "refund_*" and ignore "payment_*". + // Example 2: event_types = ["payment_succeeded", "refund_*"], is_disjoint is only used to extend "refund_*". + event_classes.into_iter().for_each(|class| { + let valid_event_types = class.event_types(); + if event_types.is_disjoint(&valid_event_types) { + event_types.extend(valid_event_types); + } + }); + + // Validate event_types is a subset of possible_event_types + // Example 1: event_types is a subset of possible_event_types (valid) + // Example 2: event_types is not a subset of possible_event_types (error due to "payment_succeeded") + if !event_types.is_subset(&possible_event_types) { + return Err(error_stack::report!( + errors::ApiErrorResponse::InvalidRequestData { + message: "`event_types` must be a subset of `event_classes`".to_string(), + } + )); + } + + Ok(event_types.clone()) +} diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index 939df1d179..ede28787a8 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use common_utils::{ext_traits::AsyncExt, types::keymanager::KeyManagerState}; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; @@ -53,6 +55,7 @@ where created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError>; @@ -82,6 +85,7 @@ where created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError>; @@ -101,6 +105,7 @@ where profile_id: Option, created_after: time::PrimitiveDateTime, created_before: time::PrimitiveDateTime, + event_types: HashSet, is_delivered: Option, ) -> CustomResult; } @@ -196,6 +201,7 @@ impl EventInterface for Store { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -207,6 +213,7 @@ impl EventInterface for Store { created_before, limit, offset, + event_types, is_delivered, ) .await @@ -309,6 +316,7 @@ impl EventInterface for Store { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -320,6 +328,7 @@ impl EventInterface for Store { created_before, limit, offset, + event_types, is_delivered, ) .await @@ -373,6 +382,7 @@ impl EventInterface for Store { profile_id: Option, created_after: time::PrimitiveDateTime, created_before: time::PrimitiveDateTime, + event_types: HashSet, is_delivered: Option, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; @@ -382,6 +392,7 @@ impl EventInterface for Store { profile_id, created_after, created_before, + event_types, is_delivered, ) .await @@ -492,6 +503,7 @@ impl EventInterface for MockDb { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -501,6 +513,7 @@ impl EventInterface for MockDb { && event.initial_attempt_id.as_ref() == Some(&event.event_id) && (event.created_at >= created_after) && (event.created_at <= created_before) + && (event_types.is_empty() || event_types.contains(&event.event_type)) && (event.is_overall_delivery_successful == is_delivered); check @@ -626,6 +639,7 @@ impl EventInterface for MockDb { created_before: time::PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -635,6 +649,7 @@ impl EventInterface for MockDb { && event.initial_attempt_id.as_ref() == Some(&event.event_id) && (event.created_at >= created_after) && (event.created_at <= created_before) + && (event_types.is_empty() || event_types.contains(&event.event_type)) && (event.is_overall_delivery_successful == is_delivered); check @@ -733,6 +748,7 @@ impl EventInterface for MockDb { profile_id: Option, created_after: time::PrimitiveDateTime, created_before: time::PrimitiveDateTime, + event_types: HashSet, is_delivered: Option, ) -> CustomResult { let locked_events = self.events.lock().await; @@ -743,6 +759,7 @@ impl EventInterface for MockDb { && (event.business_profile_id == profile_id) && (event.created_at >= created_after) && (event.created_at <= created_before) + && (event_types.is_empty() || event_types.contains(&event.event_type)) && (event.is_overall_delivery_successful == is_delivered); check diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index a5171c167b..1f7a0b92d4 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use ::payment_methods::state::PaymentMethodsStorageInterface; use common_enums::enums::MerchantStorageScheme; @@ -772,6 +772,7 @@ impl EventInterface for KafkaStore { created_before: PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -783,6 +784,7 @@ impl EventInterface for KafkaStore { created_before, limit, offset, + event_types, is_delivered, merchant_key_store, ) @@ -831,6 +833,7 @@ impl EventInterface for KafkaStore { created_before: PrimitiveDateTime, limit: Option, offset: Option, + event_types: HashSet, is_delivered: Option, merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult, errors::StorageError> { @@ -842,6 +845,7 @@ impl EventInterface for KafkaStore { created_before, limit, offset, + event_types, is_delivered, merchant_key_store, ) @@ -873,6 +877,7 @@ impl EventInterface for KafkaStore { profile_id: Option, created_after: PrimitiveDateTime, created_before: PrimitiveDateTime, + event_types: HashSet, is_delivered: Option, ) -> CustomResult { self.diesel_store @@ -881,6 +886,7 @@ impl EventInterface for KafkaStore { profile_id, created_after, created_before, + event_types, is_delivered, ) .await diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 1879d19de0..f6386a9e2c 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -2471,12 +2471,12 @@ impl WebhookEvents { web::scope("/events") .app_data(web::Data::new(config)) .service(web::scope("/profile/list").service(web::resource("").route( - web::get().to(webhook_events::list_initial_webhook_delivery_attempts_with_jwtauth), + web::post().to(webhook_events::list_initial_webhook_delivery_attempts_with_jwtauth), ))) .service( web::scope("/{merchant_id}") .service(web::resource("").route( - web::get().to(webhook_events::list_initial_webhook_delivery_attempts), + web::post().to(webhook_events::list_initial_webhook_delivery_attempts), )) .service( web::scope("/{event_id}") diff --git a/crates/router/src/routes/webhook_events.rs b/crates/router/src/routes/webhook_events.rs index 405b3f5f85..27bec80589 100644 --- a/crates/router/src/routes/webhook_events.rs +++ b/crates/router/src/routes/webhook_events.rs @@ -20,11 +20,11 @@ pub async fn list_initial_webhook_delivery_attempts( state: web::Data, req: HttpRequest, path: web::Path, - query: web::Query, + json_payload: web::Json, ) -> impl Responder { let flow = Flow::WebhookEventInitialDeliveryAttemptList; let merchant_id = path.into_inner(); - let constraints = query.into_inner(); + let constraints = json_payload.into_inner(); let request_internal = EventListRequestInternal { merchant_id: merchant_id.clone(), @@ -60,10 +60,10 @@ pub async fn list_initial_webhook_delivery_attempts( pub async fn list_initial_webhook_delivery_attempts_with_jwtauth( state: web::Data, req: HttpRequest, - query: web::Query, + json_payload: web::Json, ) -> impl Responder { let flow = Flow::WebhookEventInitialDeliveryAttemptList; - let constraints = query.into_inner(); + let constraints = json_payload.into_inner(); let request_internal = EventListRequestInternal { merchant_id: common_utils::id_type::MerchantId::default(), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 0d890599ea..54abe8f7ce 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1912,12 +1912,14 @@ impl ForeignTryFrom && (item.created_after.is_some() || item.created_before.is_some() || item.limit.is_some() - || item.offset.is_some()) + || item.offset.is_some() + || item.event_classes.is_some() + || item.event_types.is_some()) { return Err(report!(errors::ApiErrorResponse::PreconditionFailed { message: "Either only `object_id` must be specified, or one or more of \ - `created_after`, `created_before`, `limit` and `offset` must be specified" + `created_after`, `created_before`, `limit`, `offset`, `event_classes` and `event_types` must be specified" .to_string() })); } @@ -1929,6 +1931,8 @@ impl ForeignTryFrom created_before: item.created_before, limit: item.limit.map(i64::from), offset: item.offset.map(i64::from), + event_classes: item.event_classes, + event_types: item.event_types, is_delivered: item.is_delivered, }), }