Bug: feat(disputes): support filters in disputes list



Support filters in disputes list
- Get list of filters to be applied on disputes list
- It can be multi-select for options like status and connector
- `get` api to be used to pass filters which to be applied on list API
- Implement parsing logic for get api, (to be used by other get filter APIs as well)

Filters:
- Date (Timerange)
- Status
- Payment id or Dispute id search 
- Amount
- Currency
- Connector
diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs
index a1340b5aa5a..4ddb50af0f2 100644
--- a/crates/api_models/src/disputes.rs
+++ b/crates/api_models/src/disputes.rs
@@ -1,11 +1,13 @@
 use std::collections::HashMap;
 
+use common_utils::types::TimeRange;
 use masking::{Deserialize, Serialize};
+use serde::de::Error;
 use time::PrimitiveDateTime;
 use utoipa::ToSchema;
 
 use super::enums::{DisputeStage, DisputeStatus};
-use crate::files;
+use crate::{admin::MerchantConnectorInfo, enums, files};
 
 #[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)]
 pub struct DisputeResponse {
@@ -108,41 +110,51 @@ pub struct DisputeEvidenceBlock {
     pub file_metadata_response: files::FileMetadataResponse,
 }
 
-#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
+#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
 #[serde(deny_unknown_fields)]
-pub struct DisputeListConstraints {
-    /// limit on the number of objects to return
-    pub limit: Option<i64>,
+pub struct DisputeListGetConstraints {
+    /// The identifier for dispute
+    pub dispute_id: Option<String>,
+    /// The payment_id against which dispute is raised
+    pub payment_id: Option<common_utils::id_type::PaymentId>,
+    /// Limit on the number of objects to return
+    pub limit: Option<u32>,
+    /// The starting point within a list of object
+    pub offset: Option<u32>,
     /// The identifier for business profile
     #[schema(value_type = Option<String>)]
     pub profile_id: Option<common_utils::id_type::ProfileId>,
-    /// status of the dispute
-    pub dispute_status: Option<DisputeStatus>,
-    /// stage of the dispute
-    pub dispute_stage: Option<DisputeStage>,
-    /// reason for the dispute
+    /// The comma separated list of status of the disputes
+    #[serde(default, deserialize_with = "parse_comma_separated")]
+    pub dispute_status: Option<Vec<DisputeStatus>>,
+    /// The comma separated list of stages of the disputes
+    #[serde(default, deserialize_with = "parse_comma_separated")]
+    pub dispute_stage: Option<Vec<DisputeStage>>,
+    /// Reason for the dispute
     pub reason: Option<String>,
-    /// connector linked to dispute
-    pub connector: Option<String>,
-    /// The time at which dispute is received
-    #[schema(example = "2022-09-10T10:11:12Z")]
-    pub received_time: Option<PrimitiveDateTime>,
-    /// Time less than the dispute received time
-    #[schema(example = "2022-09-10T10:11:12Z")]
-    #[serde(rename = "received_time.lt")]
-    pub received_time_lt: Option<PrimitiveDateTime>,
-    /// Time greater than the dispute received time
-    #[schema(example = "2022-09-10T10:11:12Z")]
-    #[serde(rename = "received_time.gt")]
-    pub received_time_gt: Option<PrimitiveDateTime>,
-    /// Time less than or equals to the dispute received time
-    #[schema(example = "2022-09-10T10:11:12Z")]
-    #[serde(rename = "received_time.lte")]
-    pub received_time_lte: Option<PrimitiveDateTime>,
-    /// Time greater than or equals to the dispute received time
-    #[schema(example = "2022-09-10T10:11:12Z")]
-    #[serde(rename = "received_time.gte")]
-    pub received_time_gte: Option<PrimitiveDateTime>,
+    /// The comma separated list of connectors linked to disputes
+    #[serde(default, deserialize_with = "parse_comma_separated")]
+    pub connector: Option<Vec<String>>,
+    /// The comma separated list of currencies of the disputes
+    #[serde(default, deserialize_with = "parse_comma_separated")]
+    pub currency: Option<Vec<common_enums::Currency>>,
+    /// The merchant connector id to filter the disputes list
+    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
+    /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
+    #[serde(flatten)]
+    pub time_range: Option<TimeRange>,
+}
+
+#[derive(Clone, Debug, serde::Serialize, ToSchema)]
+pub struct DisputeListFilters {
+    /// The map of available connector filters, where the key is the connector name and the value is a list of MerchantConnectorInfo instances
+    pub connector: HashMap<String, Vec<MerchantConnectorInfo>>,
+    /// The list of available currency filters
+    pub currency: Vec<enums::Currency>,
+    /// The list of available dispute status filters
+    pub dispute_status: Vec<DisputeStatus>,
+    /// The list of available dispute stage filters
+    pub dispute_stage: Vec<DisputeStage>,
 }
 
 #[derive(Default, Clone, Debug, Serialize, Deserialize, ToSchema)]
@@ -216,3 +228,19 @@ pub struct DisputesAggregateResponse {
     /// Different status of disputes with their count
     pub status_with_count: HashMap<DisputeStatus, i64>,
 }
+
+fn parse_comma_separated<'de, D, T>(v: D) -> Result<Option<Vec<T>>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+    T: std::str::FromStr,
+    <T as std::str::FromStr>::Err: std::fmt::Debug + std::fmt::Display + std::error::Error,
+{
+    let output = Option::<&str>::deserialize(v)?;
+    output
+        .map(|s| {
+            s.split(",")
+                .map(|x| x.parse::<T>().map_err(D::Error::custom))
+                .collect::<Result<_, _>>()
+        })
+        .transpose()
+}
diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs
index b10f8e32dba..fefc596fe73 100644
--- a/crates/api_models/src/events.rs
+++ b/crates/api_models/src/events.rs
@@ -73,7 +73,7 @@ impl_api_event_type!(
         RetrievePaymentLinkRequest,
         PaymentLinkListConstraints,
         MandateId,
-        DisputeListConstraints,
+        DisputeListGetConstraints,
         RetrieveApiKeyResponse,
         ProfileResponse,
         ProfileUpdate,
@@ -168,3 +168,9 @@ impl ApiEventMetric for PaymentMethodIntentCreate {
         Some(ApiEventsType::PaymentMethodCreate)
     }
 }
+
+impl ApiEventMetric for DisputeListFilters {
+    fn get_api_event_type(&self) -> Option<ApiEventsType> {
+        Some(ApiEventsType::ResourceListAPI)
+    }
+}
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 31cb43669b2..312a5bf092d 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -1817,6 +1817,7 @@ pub enum CardNetwork {
     serde::Deserialize,
     serde::Serialize,
     strum::Display,
+    strum::EnumIter,
     strum::EnumString,
     ToSchema,
 )]
diff --git a/crates/hyperswitch_domain_models/src/disputes.rs b/crates/hyperswitch_domain_models/src/disputes.rs
new file mode 100644
index 00000000000..87ae6204ac7
--- /dev/null
+++ b/crates/hyperswitch_domain_models/src/disputes.rs
@@ -0,0 +1,89 @@
+use crate::errors;
+
+pub struct DisputeListConstraints {
+    pub dispute_id: Option<String>,
+    pub payment_id: Option<common_utils::id_type::PaymentId>,
+    pub limit: Option<u32>,
+    pub offset: Option<u32>,
+    pub profile_id: Option<Vec<common_utils::id_type::ProfileId>>,
+    pub dispute_status: Option<Vec<common_enums::DisputeStatus>>,
+    pub dispute_stage: Option<Vec<common_enums::DisputeStage>>,
+    pub reason: Option<String>,
+    pub connector: Option<Vec<String>>,
+    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
+    pub currency: Option<Vec<common_enums::Currency>>,
+    pub time_range: Option<common_utils::types::TimeRange>,
+}
+
+impl
+    TryFrom<(
+        api_models::disputes::DisputeListGetConstraints,
+        Option<Vec<common_utils::id_type::ProfileId>>,
+    )> for DisputeListConstraints
+{
+    type Error = error_stack::Report<errors::api_error_response::ApiErrorResponse>;
+    fn try_from(
+        (value, auth_profile_id_list): (
+            api_models::disputes::DisputeListGetConstraints,
+            Option<Vec<common_utils::id_type::ProfileId>>,
+        ),
+    ) -> Result<Self, Self::Error> {
+        let api_models::disputes::DisputeListGetConstraints {
+            dispute_id,
+            payment_id,
+            limit,
+            offset,
+            profile_id,
+            dispute_status,
+            dispute_stage,
+            reason,
+            connector,
+            merchant_connector_id,
+            currency,
+            time_range,
+        } = value;
+        let profile_id_from_request_body = profile_id;
+        // Match both the profile ID from the request body and the list of authenticated profile IDs coming from auth layer
+        let profile_id_list = match (profile_id_from_request_body, auth_profile_id_list) {
+            (None, None) => None,
+            // Case when the request body profile ID is None, but authenticated profile IDs are available, return the auth list
+            (None, Some(auth_profile_id_list)) => Some(auth_profile_id_list),
+            // Case when the request body profile ID is provided, but the auth list is None, create a vector with the request body profile ID
+            (Some(profile_id_from_request_body), None) => Some(vec![profile_id_from_request_body]),
+            (Some(profile_id_from_request_body), Some(auth_profile_id_list)) => {
+                // Check if the profile ID from the request body is present in the authenticated profile ID list
+                let profile_id_from_request_body_is_available_in_auth_profile_id_list =
+                    auth_profile_id_list.contains(&profile_id_from_request_body);
+
+                if profile_id_from_request_body_is_available_in_auth_profile_id_list {
+                    Some(vec![profile_id_from_request_body])
+                } else {
+                    // If the profile ID is not valid, return an error indicating access is not available
+                    return Err(error_stack::Report::new(
+                        errors::api_error_response::ApiErrorResponse::PreconditionFailed {
+                            message: format!(
+                                "Access not available for the given profile_id {:?}",
+                                profile_id_from_request_body
+                            ),
+                        },
+                    ));
+                }
+            }
+        };
+
+        Ok(Self {
+            dispute_id,
+            payment_id,
+            limit,
+            offset,
+            profile_id: profile_id_list,
+            dispute_status,
+            dispute_stage,
+            reason,
+            connector,
+            merchant_connector_id,
+            currency,
+            time_range,
+        })
+    }
+}
diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs
index b0a660ad5d3..c99fbd89cd5 100644
--- a/crates/hyperswitch_domain_models/src/lib.rs
+++ b/crates/hyperswitch_domain_models/src/lib.rs
@@ -3,6 +3,7 @@ pub mod behaviour;
 pub mod business_profile;
 pub mod consts;
 pub mod customer;
+pub mod disputes;
 pub mod errors;
 pub mod mandates;
 pub mod merchant_account;
diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs
index 30b73eb4c6a..8f083c3103a 100644
--- a/crates/router/src/core/disputes.rs
+++ b/crates/router/src/core/disputes.rs
@@ -1,11 +1,13 @@
-use api_models::{disputes as dispute_models, files as files_api_models};
+use std::collections::HashMap;
+
+use api_models::{
+    admin::MerchantConnectorInfo, disputes as dispute_models, files as files_api_models,
+};
 use common_utils::ext_traits::{Encode, ValueExt};
 use error_stack::ResultExt;
 use router_env::{instrument, tracing};
-pub mod transformers;
-use std::collections::HashMap;
-
 use strum::IntoEnumIterator;
+pub mod transformers;
 
 use super::{
     errors::{self, ConnectorErrorExt, RouterResponse, StorageErrorExt},
@@ -48,12 +50,13 @@ pub async fn retrieve_dispute(
 pub async fn retrieve_disputes_list(
     state: SessionState,
     merchant_account: domain::MerchantAccount,
-    _profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
-    constraints: api_models::disputes::DisputeListConstraints,
+    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
+    constraints: api_models::disputes::DisputeListGetConstraints,
 ) -> RouterResponse<Vec<api_models::disputes::DisputeResponse>> {
+    let dispute_list_constraints = &(constraints.clone(), profile_id_list.clone()).try_into()?;
     let disputes = state
         .store
-        .find_disputes_by_merchant_id(merchant_account.get_id(), constraints)
+        .find_disputes_by_constraints(merchant_account.get_id(), dispute_list_constraints)
         .await
         .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
         .attach_printable("Unable to retrieve disputes")?;
@@ -76,6 +79,59 @@ pub async fn accept_dispute(
     todo!()
 }
 
+#[instrument(skip(state))]
+pub async fn get_filters_for_disputes(
+    state: SessionState,
+    merchant_account: domain::MerchantAccount,
+    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
+) -> RouterResponse<api_models::disputes::DisputeListFilters> {
+    let merchant_connector_accounts = if let services::ApplicationResponse::Json(data) =
+        super::admin::list_payment_connectors(
+            state,
+            merchant_account.get_id().to_owned(),
+            profile_id_list,
+        )
+        .await?
+    {
+        data
+    } else {
+        return Err(error_stack::report!(
+            errors::ApiErrorResponse::InternalServerError
+        ))
+        .attach_printable(
+            "Failed to retrieve merchant connector accounts while fetching dispute list filters.",
+        );
+    };
+
+    let connector_map = merchant_connector_accounts
+        .into_iter()
+        .filter_map(|merchant_connector_account| {
+            merchant_connector_account
+                .connector_label
+                .clone()
+                .map(|label| {
+                    let info = merchant_connector_account.to_merchant_connector_info(&label);
+                    (merchant_connector_account.connector_name, info)
+                })
+        })
+        .fold(
+            HashMap::new(),
+            |mut map: HashMap<String, Vec<MerchantConnectorInfo>>, (connector_name, info)| {
+                map.entry(connector_name).or_default().push(info);
+                map
+            },
+        );
+
+    Ok(services::ApplicationResponse::Json(
+        api_models::disputes::DisputeListFilters {
+            connector: connector_map,
+            currency: storage_enums::Currency::iter().collect(),
+            dispute_status: storage_enums::DisputeStatus::iter().collect(),
+            dispute_stage: storage_enums::DisputeStage::iter().collect(),
+        },
+    ))
+}
+
 #[cfg(feature = "v1")]
 #[instrument(skip(state))]
 pub async fn accept_dispute(
diff --git a/crates/router/src/db/dispute.rs b/crates/router/src/db/dispute.rs
index a44528fde43..d7f4af63015 100644
--- a/crates/router/src/db/dispute.rs
+++ b/crates/router/src/db/dispute.rs
@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 
 use error_stack::report;
+use hyperswitch_domain_models::disputes;
 use router_env::{instrument, tracing};
 
 use super::{MockDb, Store};
@@ -30,10 +31,10 @@ pub trait DisputeInterface {
         dispute_id: &str,
     ) -> CustomResult<storage::Dispute, errors::StorageError>;
 
-    async fn find_disputes_by_merchant_id(
+    async fn find_disputes_by_constraints(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        dispute_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<storage::Dispute>, errors::StorageError>;
 
     async fn find_disputes_by_merchant_id_payment_id(
@@ -101,10 +102,10 @@ impl DisputeInterface for Store {
     }
 
     #[instrument(skip_all)]
-    async fn find_disputes_by_merchant_id(
+    async fn find_disputes_by_constraints(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        dispute_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<storage::Dispute>, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
         storage::Dispute::filter_by_constraints(&conn, merchant_id, dispute_constraints)
@@ -238,75 +239,96 @@ impl DisputeInterface for MockDb {
             .into())
     }
 
-    async fn find_disputes_by_merchant_id(
+    async fn find_disputes_by_constraints(
         &self,
         merchant_id: &common_utils::id_type::MerchantId,
-        dispute_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<storage::Dispute>, errors::StorageError> {
         let locked_disputes = self.disputes.lock().await;
-
-        Ok(locked_disputes
+        let limit_usize = dispute_constraints
+            .limit
+            .unwrap_or(u32::MAX)
+            .try_into()
+            .unwrap_or(usize::MAX);
+        let offset_usize = dispute_constraints
+            .offset
+            .unwrap_or(0)
+            .try_into()
+            .unwrap_or(usize::MIN);
+        let filtered_disputes: Vec<storage::Dispute> = locked_disputes
             .iter()
-            .filter(|d| {
-                d.merchant_id == *merchant_id
+            .filter(|dispute| {
+                dispute.merchant_id == *merchant_id
                     && dispute_constraints
-                        .dispute_status
+                        .dispute_id
                         .as_ref()
-                        .map(|status| status == &d.dispute_status)
-                        .unwrap_or(true)
+                        .map_or(true, |id| &dispute.dispute_id == id)
                     && dispute_constraints
-                        .dispute_stage
+                        .payment_id
                         .as_ref()
-                        .map(|stage| stage == &d.dispute_stage)
-                        .unwrap_or(true)
+                        .map_or(true, |id| &dispute.payment_id == id)
                     && dispute_constraints
-                        .reason
+                        .profile_id
                         .as_ref()
-                        .and_then(|reason| {
-                            d.connector_reason
+                        .map_or(true, |profile_ids| {
+                            dispute
+                                .profile_id
                                 .as_ref()
-                                .map(|connector_reason| connector_reason == reason)
+                                .map_or(true, |id| profile_ids.contains(id))
                         })
-                        .unwrap_or(true)
                     && dispute_constraints
-                        .connector
+                        .dispute_status
                         .as_ref()
-                        .map(|connector| connector == &d.connector)
-                        .unwrap_or(true)
+                        .map_or(true, |statuses| statuses.contains(&dispute.dispute_status))
                     && dispute_constraints
-                        .received_time
+                        .dispute_stage
                         .as_ref()
-                        .map(|received_time| received_time == &d.created_at)
-                        .unwrap_or(true)
+                        .map_or(true, |stages| stages.contains(&dispute.dispute_stage))
+                    && dispute_constraints.reason.as_ref().map_or(true, |reason| {
+                        dispute
+                            .connector_reason
+                            .as_ref()
+                            .map_or(true, |d_reason| d_reason == reason)
+                    })
                     && dispute_constraints
-                        .received_time_lt
+                        .connector
                         .as_ref()
-                        .map(|received_time_lt| received_time_lt > &d.created_at)
-                        .unwrap_or(true)
+                        .map_or(true, |connectors| {
+                            connectors
+                                .iter()
+                                .any(|connector| dispute.connector.as_str() == *connector)
+                        })
                     && dispute_constraints
-                        .received_time_gt
+                        .merchant_connector_id
                         .as_ref()
-                        .map(|received_time_gt| received_time_gt < &d.created_at)
-                        .unwrap_or(true)
+                        .map_or(true, |id| {
+                            dispute.merchant_connector_id.as_ref() == Some(id)
+                        })
                     && dispute_constraints
-                        .received_time_lte
+                        .currency
                         .as_ref()
-                        .map(|received_time_lte| received_time_lte >= &d.created_at)
-                        .unwrap_or(true)
+                        .map_or(true, |currencies| {
+                            currencies
+                                .iter()
+                                .any(|currency| dispute.currency.as_str() == currency.to_string())
+                        })
                     && dispute_constraints
-                        .received_time_gte
+                        .time_range
                         .as_ref()
-                        .map(|received_time_gte| received_time_gte <= &d.created_at)
-                        .unwrap_or(true)
+                        .map_or(true, |range| {
+                            let dispute_time = dispute.created_at;
+                            dispute_time >= range.start_time
+                                && range
+                                    .end_time
+                                    .map_or(true, |end_time| dispute_time <= end_time)
+                        })
             })
-            .take(
-                dispute_constraints
-                    .limit
-                    .and_then(|limit| usize::try_from(limit).ok())
-                    .unwrap_or(usize::MAX),
-            )
+            .skip(offset_usize)
+            .take(limit_usize)
             .cloned()
-            .collect())
+            .collect();
+
+        Ok(filtered_disputes)
     }
 
     async fn find_disputes_by_merchant_id_payment_id(
@@ -437,11 +459,11 @@ mod tests {
     mod mockdb_dispute_interface {
         use std::borrow::Cow;
 
-        use api_models::disputes::DisputeListConstraints;
         use diesel_models::{
             dispute::DisputeNew,
             enums::{DisputeStage, DisputeStatus},
         };
+        use hyperswitch_domain_models::disputes::DisputeListConstraints;
         use masking::Secret;
         use redis_interface::RedisSettings;
         use serde_json::Value;
@@ -648,20 +670,21 @@ mod tests {
                 .unwrap();
 
             let found_disputes = mockdb
-                .find_disputes_by_merchant_id(
+                .find_disputes_by_constraints(
                     &merchant_id,
-                    DisputeListConstraints {
+                    &DisputeListConstraints {
+                        dispute_id: None,
+                        payment_id: None,
+                        profile_id: None,
+                        connector: None,
+                        merchant_connector_id: None,
+                        currency: None,
                         limit: None,
+                        offset: None,
                         dispute_status: None,
                         dispute_stage: None,
                         reason: None,
-                        connector: None,
-                        received_time: None,
-                        received_time_lt: None,
-                        received_time_gt: None,
-                        received_time_lte: None,
-                        received_time_gte: None,
-                        profile_id: None,
+                        time_range: None,
                     },
                 )
                 .await
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index ff7e8145c33..36b66244e18 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -14,6 +14,7 @@ use hyperswitch_domain_models::payouts::{
     payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface,
 };
 use hyperswitch_domain_models::{
+    disputes,
     payments::{payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface},
     refunds,
 };
@@ -590,13 +591,13 @@ impl DisputeInterface for KafkaStore {
             .await
     }
 
-    async fn find_disputes_by_merchant_id(
+    async fn find_disputes_by_constraints(
         &self,
         merchant_id: &id_type::MerchantId,
-        dispute_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<storage::Dispute>, errors::StorageError> {
         self.diesel_store
-            .find_disputes_by_merchant_id(merchant_id, dispute_constraints)
+            .find_disputes_by_constraints(merchant_id, dispute_constraints)
             .await
     }
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 01a48c56401..dafee54ae05 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1496,6 +1496,11 @@ impl Disputes {
                 web::resource("/profile/list")
                     .route(web::get().to(disputes::retrieve_disputes_list_profile)),
             )
+            .service(web::resource("/filter").route(web::get().to(disputes::get_disputes_filters)))
+            .service(
+                web::resource("/profile/filter")
+                    .route(web::get().to(disputes::get_disputes_filters_profile)),
+            )
             .service(
                 web::resource("/accept/{dispute_id}")
                     .route(web::post().to(disputes::accept_dispute)),
diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs
index 50a26ceae89..1a8c4410a53 100644
--- a/crates/router/src/routes/disputes.rs
+++ b/crates/router/src/routes/disputes.rs
@@ -87,10 +87,10 @@ pub async fn retrieve_dispute(
 pub async fn retrieve_disputes_list(
     state: web::Data<AppState>,
     req: HttpRequest,
-    payload: web::Query<dispute_models::DisputeListConstraints>,
+    query: web::Query<dispute_models::DisputeListGetConstraints>,
 ) -> HttpResponse {
     let flow = Flow::DisputesList;
-    let payload = payload.into_inner();
+    let payload = query.into_inner();
     Box::pin(api::server_wrap(
         flow,
         state,
@@ -140,7 +140,7 @@ pub async fn retrieve_disputes_list(
 pub async fn retrieve_disputes_list_profile(
     state: web::Data<AppState>,
     req: HttpRequest,
-    payload: web::Query<dispute_models::DisputeListConstraints>,
+    payload: web::Query<dispute_models::DisputeListGetConstraints>,
 ) -> HttpResponse {
     let flow = Flow::DisputesList;
     let payload = payload.into_inner();
@@ -170,6 +170,81 @@ pub async fn retrieve_disputes_list_profile(
     .await
 }
 
+/// Disputes - Disputes Filters
+#[utoipa::path(
+    get,
+    path = "/disputes/filter",
+    responses(
+        (status = 200, description = "List of filters", body = DisputeListFilters),
+    ),
+    tag = "Disputes",
+    operation_id = "List all filters for disputes",
+    security(("api_key" = []))
+)]
+#[instrument(skip_all, fields(flow = ?Flow::DisputesFilters))]
+pub async fn get_disputes_filters(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+    let flow = Flow::DisputesFilters;
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        (),
+        |state, auth, _, _| disputes::get_filters_for_disputes(state, auth.merchant_account, None),
+        auth::auth_type(
+            &auth::HeaderAuth(auth::ApiKeyAuth),
+            &auth::JWTAuth {
+                permission: Permission::DisputeRead,
+                minimum_entity_level: EntityType::Merchant,
+            },
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+/// Disputes - Disputes Filters Profile
+#[utoipa::path(
+    get,
+    path = "/disputes/profile/filter",
+    responses(
+        (status = 200, description = "List of filters", body = DisputeListFilters),
+    ),
+    tag = "Disputes",
+    operation_id = "List all filters for disputes",
+    security(("api_key" = []))
+)]
+#[instrument(skip_all, fields(flow = ?Flow::DisputesFilters))]
+pub async fn get_disputes_filters_profile(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+) -> HttpResponse {
+    let flow = Flow::DisputesFilters;
+    Box::pin(api::server_wrap(
+        flow,
+        state,
+        &req,
+        (),
+        |state, auth, _, _| {
+            disputes::get_filters_for_disputes(
+                state,
+                auth.merchant_account,
+                auth.profile_id.map(|profile_id| vec![profile_id]),
+            )
+        },
+        auth::auth_type(
+            &auth::HeaderAuth(auth::ApiKeyAuth),
+            &auth::JWTAuth {
+                permission: Permission::DisputeRead,
+                minimum_entity_level: EntityType::Profile,
+            },
+            req.headers(),
+        ),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 /// Disputes - Accept Dispute
 #[utoipa::path(
     get,
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index d6d3ec8a952..482bc40c49f 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -172,6 +172,7 @@ impl From<Flow> for ApiIdentifier {
 
             Flow::DisputesRetrieve
             | Flow::DisputesList
+            | Flow::DisputesFilters
             | Flow::DisputesEvidenceSubmit
             | Flow::AttachDisputeEvidence
             | Flow::RetrieveDisputeEvidence
diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs
index 40184c5fcfa..b287b5e9790 100644
--- a/crates/router/src/routes/refunds.rs
+++ b/crates/router/src/routes/refunds.rs
@@ -381,7 +381,7 @@ pub async fn get_refunds_filters(state: web::Data<AppState>, req: HttpRequest) -
 /// To list the refunds filters associated with list of connectors, currencies and payment statuses
 #[utoipa::path(
     get,
-    path = "/refunds/v2/filter/profile",
+    path = "/refunds/v2/profile/filter",
     responses(
         (status = 200, description = "List of static filters", body = RefundListFilters),
     ),
diff --git a/crates/router/src/types/storage/dispute.rs b/crates/router/src/types/storage/dispute.rs
index 45f2073708b..28a9573b333 100644
--- a/crates/router/src/types/storage/dispute.rs
+++ b/crates/router/src/types/storage/dispute.rs
@@ -4,6 +4,7 @@ use diesel::{associations::HasTable, ExpressionMethods, QueryDsl};
 pub use diesel_models::dispute::{Dispute, DisputeNew, DisputeUpdate};
 use diesel_models::{errors, query::generics::db_metrics, schema::dispute::dsl};
 use error_stack::ResultExt;
+use hyperswitch_domain_models::disputes;
 
 use crate::{connection::PgPooledConn, logger};
 
@@ -12,7 +13,7 @@ pub trait DisputeDbExt: Sized {
     async fn filter_by_constraints(
         conn: &PgPooledConn,
         merchant_id: &common_utils::id_type::MerchantId,
-        dispute_list_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_list_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<Self>, errors::DatabaseError>;
 
     async fn get_dispute_status_with_count(
@@ -28,45 +29,77 @@ impl DisputeDbExt for Dispute {
     async fn filter_by_constraints(
         conn: &PgPooledConn,
         merchant_id: &common_utils::id_type::MerchantId,
-        dispute_list_constraints: api_models::disputes::DisputeListConstraints,
+        dispute_list_constraints: &disputes::DisputeListConstraints,
     ) -> CustomResult<Vec<Self>, errors::DatabaseError> {
         let mut filter = <Self as HasTable>::table()
             .filter(dsl::merchant_id.eq(merchant_id.to_owned()))
             .order(dsl::modified_at.desc())
             .into_boxed();
 
-        if let Some(profile_id) = dispute_list_constraints.profile_id {
-            filter = filter.filter(dsl::profile_id.eq(profile_id));
+        let mut search_by_payment_or_dispute_id = false;
+
+        if let (Some(payment_id), Some(dispute_id)) = (
+            &dispute_list_constraints.payment_id,
+            &dispute_list_constraints.dispute_id,
+        ) {
+            search_by_payment_or_dispute_id = true;
+            filter = filter
+                .filter(dsl::payment_id.eq(payment_id.to_owned()))
+                .or_filter(dsl::dispute_id.eq(dispute_id.to_owned()));
+        };
+
+        if !search_by_payment_or_dispute_id {
+            if let Some(payment_id) = &dispute_list_constraints.payment_id {
+                filter = filter.filter(dsl::payment_id.eq(payment_id.to_owned()));
+            };
         }
-        if let Some(received_time) = dispute_list_constraints.received_time {
-            filter = filter.filter(dsl::created_at.eq(received_time));
+        if !search_by_payment_or_dispute_id {
+            if let Some(dispute_id) = &dispute_list_constraints.dispute_id {
+                filter = filter.filter(dsl::dispute_id.eq(dispute_id.clone()));
+            };
         }
-        if let Some(received_time_lt) = dispute_list_constraints.received_time_lt {
-            filter = filter.filter(dsl::created_at.lt(received_time_lt));
+
+        if let Some(time_range) = dispute_list_constraints.time_range {
+            filter = filter.filter(dsl::created_at.ge(time_range.start_time));
+
+            if let Some(end_time) = time_range.end_time {
+                filter = filter.filter(dsl::created_at.le(end_time));
+            }
         }
-        if let Some(received_time_gt) = dispute_list_constraints.received_time_gt {
-            filter = filter.filter(dsl::created_at.gt(received_time_gt));
+
+        if let Some(profile_id) = &dispute_list_constraints.profile_id {
+            filter = filter.filter(dsl::profile_id.eq_any(profile_id.clone()));
         }
-        if let Some(received_time_lte) = dispute_list_constraints.received_time_lte {
-            filter = filter.filter(dsl::created_at.le(received_time_lte));
+        if let Some(connector_list) = &dispute_list_constraints.connector {
+            filter = filter.filter(dsl::connector.eq_any(connector_list.clone()));
         }
-        if let Some(received_time_gte) = dispute_list_constraints.received_time_gte {
-            filter = filter.filter(dsl::created_at.ge(received_time_gte));
+
+        if let Some(reason) = &dispute_list_constraints.reason {
+            filter = filter.filter(dsl::connector_reason.eq(reason.clone()));
         }
-        if let Some(connector) = dispute_list_constraints.connector {
-            filter = filter.filter(dsl::connector.eq(connector));
+        if let Some(dispute_stage) = &dispute_list_constraints.dispute_stage {
+            filter = filter.filter(dsl::dispute_stage.eq_any(dispute_stage.clone()));
         }
-        if let Some(reason) = dispute_list_constraints.reason {
-            filter = filter.filter(dsl::connector_reason.eq(reason));
+        if let Some(dispute_status) = &dispute_list_constraints.dispute_status {
+            filter = filter.filter(dsl::dispute_status.eq_any(dispute_status.clone()));
         }
-        if let Some(dispute_stage) = dispute_list_constraints.dispute_stage {
-            filter = filter.filter(dsl::dispute_stage.eq(dispute_stage));
+
+        if let Some(currency_list) = &dispute_list_constraints.currency {
+            let currency: Vec<String> = currency_list
+                .iter()
+                .map(|currency| currency.to_string())
+                .collect();
+
+            filter = filter.filter(dsl::currency.eq_any(currency));
         }
-        if let Some(dispute_status) = dispute_list_constraints.dispute_status {
-            filter = filter.filter(dsl::dispute_status.eq(dispute_status));
+        if let Some(merchant_connector_id) = &dispute_list_constraints.merchant_connector_id {
+            filter = filter.filter(dsl::merchant_connector_id.eq(merchant_connector_id.clone()))
         }
         if let Some(limit) = dispute_list_constraints.limit {
-            filter = filter.limit(limit);
+            filter = filter.limit(limit.into());
+        }
+        if let Some(offset) = dispute_list_constraints.offset {
+            filter = filter.offset(offset.into());
         }
 
         logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 12e30f54549..603c5cc91c1 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -274,6 +274,8 @@ pub enum Flow {
     DisputesRetrieve,
     /// Dispute List flow
     DisputesList,
+    /// Dispute Filters flow
+    DisputesFilters,
     /// Cards Info flow
     CardsInfo,
     /// Create File flow

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Add filters for disputes list

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Closes #5626 


## How did you test it?
The following endpoints will give profile or merchant level filters:
The start_time and end_time are in primitive data time format:
`start_time`=`2024-07-03T18:00:00.000Z`
`end_time`=`2024-10-03T18:00:00.000Z`
```
curl --location --request GET 'http://localhost:8080/disputes/filter' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer JWT' \
--data 
```
```
curl --location --request GET 'http://localhost:8080/disputes/profile/filter' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer JWT' \
--data 
```
Response: Set of dropdown filters:
```
{
    "connector": { List of configured connectors},
    "currency": [
        "AED",
        "ALL",
        "AMD",
 ... and other list of available currencies
    ],
    "dispute_status": [
        "dispute_opened",
        "dispute_expired",
        "dispute_accepted",
        "dispute_cancelled",
        "dispute_challenged",
        "dispute_won",
        "dispute_lost"
    ],
    "dispute_stage": [
        "pre_dispute",
        "dispute",
        "pre_arbitration"
    ]
}
```

The list api will accept set of filters in query param and gives desired response
```
curl --location 'http://localhost:8080/disputes/list?connector=c1,c2,c3&currency=cur1,cur2,cur3&dispute_stage=d1,d2&dispute_status=ds1,ds2' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer JWT' \
--data ''
```
```
curl --location 'http://localhost:8080/disputes/list?start_time=2024-07-03T18:00:00.000Z&end_time=2024-10-03T18:00:00.000Z' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer JWT' \
--data ''
```
Response will be list after applying the filters passed in query param.
```
[
    {
        "dispute_id": "dp_c5dsfPtddlzuGN6kEusg",
        "payment_id": "test_F2wxddVYyES3ZmxdYE07",
        "attempt_id": "test_F2wxddVYyES3ZmxdYE07_1",
        "amount": "1040",
        "currency": "GBP",
        "dispute_stage": "dispute",
        "dispute_status": "dispute_expired",
        "connector": "checkout",
        "connector_status": "DisputeExpired",
        "connector_dispute_id": "dsp_c0db6a8d577h763b1c00",
        "connector_reason": "",
        "connector_reason_code": "10.4",
        "challenge_required_by": "2024-07-03T18:00:00.000Z",
        "connector_created_at": "2024-06-13T14:39:16.710Z",
        "connector_updated_at": "2024-07-03T18:00:36.610Z",
        "created_at": "2024-06-13T14:39:17.037Z",
        "profile_id": "pro_cz810B3XlrAPQu69pCi6",
        "merchant_connector_id": "mca_6rzLRT6znmFkdTpSLu0x"
    }
    ]
```


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [ ] I formatted the code `cargo +nightly fmt --all`
- [ ] I addressed lints thrown by `cargo clippy`
- [ ] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
