feat(disputes): add filters for disputes list (#5637)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Apoorv Dixit
2024-09-22 23:39:37 +05:30
committed by GitHub
parent 37925626e6
commit 365f5680b7
14 changed files with 451 additions and 130 deletions

View File

@ -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()
}

View File

@ -75,7 +75,7 @@ impl_api_event_type!(
RetrievePaymentLinkRequest,
PaymentLinkListConstraints,
MandateId,
DisputeListConstraints,
DisputeListGetConstraints,
RetrieveApiKeyResponse,
ProfileResponse,
ProfileUpdate,
@ -171,3 +171,9 @@ impl ApiEventMetric for PaymentMethodIntentCreate {
Some(ApiEventsType::PaymentMethodCreate)
}
}
impl ApiEventMetric for DisputeListFilters {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::ResourceListAPI)
}
}

View File

@ -1817,6 +1817,7 @@ pub enum CardNetwork {
serde::Deserialize,
serde::Serialize,
strum::Display,
strum::EnumIter,
strum::EnumString,
ToSchema,
)]

View File

@ -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,
})
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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_id
.as_ref()
.map_or(true, |id| &dispute.dispute_id == id)
&& dispute_constraints
.payment_id
.as_ref()
.map_or(true, |id| &dispute.payment_id == id)
&& dispute_constraints
.profile_id
.as_ref()
.map_or(true, |profile_ids| {
dispute
.profile_id
.as_ref()
.map_or(true, |id| profile_ids.contains(id))
})
&& dispute_constraints
.dispute_status
.as_ref()
.map(|status| status == &d.dispute_status)
.unwrap_or(true)
.map_or(true, |statuses| statuses.contains(&dispute.dispute_status))
&& dispute_constraints
.dispute_stage
.as_ref()
.map(|stage| stage == &d.dispute_stage)
.unwrap_or(true)
&& dispute_constraints
.reason
.map_or(true, |stages| stages.contains(&dispute.dispute_stage))
&& dispute_constraints.reason.as_ref().map_or(true, |reason| {
dispute
.connector_reason
.as_ref()
.and_then(|reason| {
d.connector_reason
.as_ref()
.map(|connector_reason| connector_reason == reason)
.map_or(true, |d_reason| d_reason == reason)
})
.unwrap_or(true)
&& dispute_constraints
.connector
.as_ref()
.map(|connector| connector == &d.connector)
.unwrap_or(true)
&& dispute_constraints
.received_time
.as_ref()
.map(|received_time| received_time == &d.created_at)
.unwrap_or(true)
&& dispute_constraints
.received_time_lt
.as_ref()
.map(|received_time_lt| received_time_lt > &d.created_at)
.unwrap_or(true)
&& dispute_constraints
.received_time_gt
.as_ref()
.map(|received_time_gt| received_time_gt < &d.created_at)
.unwrap_or(true)
&& dispute_constraints
.received_time_lte
.as_ref()
.map(|received_time_lte| received_time_lte >= &d.created_at)
.unwrap_or(true)
&& dispute_constraints
.received_time_gte
.as_ref()
.map(|received_time_gte| received_time_gte <= &d.created_at)
.unwrap_or(true)
.map_or(true, |connectors| {
connectors
.iter()
.any(|connector| dispute.connector.as_str() == *connector)
})
.take(
dispute_constraints
.limit
.and_then(|limit| usize::try_from(limit).ok())
.unwrap_or(usize::MAX),
)
&& dispute_constraints
.merchant_connector_id
.as_ref()
.map_or(true, |id| {
dispute.merchant_connector_id.as_ref() == Some(id)
})
&& dispute_constraints
.currency
.as_ref()
.map_or(true, |currencies| {
currencies
.iter()
.any(|currency| dispute.currency.as_str() == currency.to_string())
})
&& dispute_constraints
.time_range
.as_ref()
.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)
})
})
.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

View File

@ -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
}

View File

@ -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)),

View File

@ -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,

View File

@ -172,6 +172,7 @@ impl From<Flow> for ApiIdentifier {
Flow::DisputesRetrieve
| Flow::DisputesList
| Flow::DisputesFilters
| Flow::DisputesEvidenceSubmit
| Flow::AttachDisputeEvidence
| Flow::RetrieveDisputeEvidence

View File

@ -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),
),

View File

@ -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(received_time_lte) = dispute_list_constraints.received_time_lte {
filter = filter.filter(dsl::created_at.le(received_time_lte));
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_gte) = dispute_list_constraints.received_time_gte {
filter = filter.filter(dsl::created_at.ge(received_time_gte));
if let Some(connector_list) = &dispute_list_constraints.connector {
filter = filter.filter(dsl::connector.eq_any(connector_list.clone()));
}
if let Some(connector) = dispute_list_constraints.connector {
filter = filter.filter(dsl::connector.eq(connector));
if let Some(reason) = &dispute_list_constraints.reason {
filter = filter.filter(dsl::connector_reason.eq(reason.clone()));
}
if let Some(reason) = dispute_list_constraints.reason {
filter = filter.filter(dsl::connector_reason.eq(reason));
if let Some(dispute_stage) = &dispute_list_constraints.dispute_stage {
filter = filter.filter(dsl::dispute_stage.eq_any(dispute_stage.clone()));
}
if let Some(dispute_stage) = dispute_list_constraints.dispute_stage {
filter = filter.filter(dsl::dispute_stage.eq(dispute_stage));
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_status) = dispute_list_constraints.dispute_status {
filter = filter.filter(dsl::dispute_status.eq(dispute_status));
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(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());

View File

@ -274,6 +274,8 @@ pub enum Flow {
DisputesRetrieve,
/// Dispute List flow
DisputesList,
/// Dispute Filters flow
DisputesFilters,
/// Cards Info flow
CardsInfo,
/// Create File flow