diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index bf4eeb79fef1..25de88212639 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -523,7 +523,7 @@ pub struct MerchantConnectorWebhookDetails { pub additional_secret: Option>, } -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, ToSchema)] pub struct MerchantConnectorInfo { pub connector_label: String, pub merchant_connector_id: String, diff --git a/crates/api_models/src/events/refund.rs b/crates/api_models/src/events/refund.rs index 424a3191db66..7ab10a23b71d 100644 --- a/crates/api_models/src/events/refund.rs +++ b/crates/api_models/src/events/refund.rs @@ -1,8 +1,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::refunds::{ - RefundListMetaData, RefundListRequest, RefundListResponse, RefundRequest, RefundResponse, - RefundUpdateRequest, RefundsRetrieveRequest, + RefundListFilters, RefundListMetaData, RefundListRequest, RefundListResponse, RefundRequest, + RefundResponse, RefundUpdateRequest, RefundsRetrieveRequest, }; impl ApiEventMetric for RefundRequest { @@ -61,3 +61,9 @@ impl ApiEventMetric for RefundListMetaData { Some(ApiEventsType::ResourceListAPI) } } + +impl ApiEventMetric for RefundListFilters { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ResourceListAPI) + } +} diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index f1ce53580aab..6009372489d5 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3576,9 +3576,11 @@ pub struct PaymentListFiltersV2 { pub authentication_type: Vec, } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] pub struct AmountFilter { + /// The start amount to filter list of transactions which are greater than or equal to the start amount pub start_amount: Option, + /// The end amount to filter list of transactions which are less than or equal to the end amount pub end_amount: Option, } diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index ea28ed56af3d..369aa0a66023 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -1,10 +1,15 @@ +use std::collections::HashMap; + use common_utils::pii; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use utoipa::ToSchema; -use super::payments::TimeRange; -use crate::{admin, enums}; +use super::payments::{AmountFilter, TimeRange}; +use crate::{ + admin::{self, MerchantConnectorInfo}, + enums, +}; #[derive(Default, Debug, ToSchema, Clone, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -146,11 +151,15 @@ pub struct RefundListRequest { pub limit: Option, /// The starting point within a list of objects pub offset: Option, - /// 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). + /// 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, + /// The amount to filter reufnds list. Amount takes two option fields start_amount and end_amount from which objects can be filtered as per required scenarios (less_than, greater_than, equal_to and range) + pub amount_filter: Option, /// The list of connectors to filter refunds list pub connector: Option>, + /// The list of merchant connector ids to filter the refunds list for selected label + pub merchant_connector_id: Option>, /// The list of currencies to filter refunds list #[schema(value_type = Option>)] pub currency: Option>, @@ -181,6 +190,18 @@ pub struct RefundListMetaData { pub refund_status: Vec, } +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct RefundListFilters { + /// 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>, + /// The list of available currency filters + #[schema(value_type = Vec)] + pub currency: Vec, + /// The list of available refund status filters + #[schema(value_type = Vec)] + pub refund_status: Vec, +} + /// The status for refunds #[derive( Debug, diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 1304d350e3ed..8b0974cc4f8e 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1514,6 +1514,7 @@ pub enum PaymentType { PartialEq, strum::Display, strum::EnumString, + strum::EnumIter, serde::Serialize, serde::Deserialize, )] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 7abbc3b77e42..6c1a32fc22dd 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -418,6 +418,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, api_models::payments::TimeRange, + api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, api_models::mandates::MandateResponse, api_models::mandates::MandateCardDetails, diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 2d6cac3f6e0d..2ddc5e1f6c24 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -1,9 +1,16 @@ pub mod validator; +#[cfg(feature = "olap")] +use std::collections::HashMap; + +#[cfg(feature = "olap")] +use api_models::admin::MerchantConnectorInfo; use common_utils::ext_traits::AsyncExt; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; use scheduler::{consumer::types::process_data, utils as process_tracker_utils}; +#[cfg(feature = "olap")] +use strum::IntoEnumIterator; use crate::{ consts, @@ -767,6 +774,48 @@ pub async fn refund_filter_list( Ok(services::ApplicationResponse::Json(filter_list)) } +#[instrument(skip_all)] +#[cfg(feature = "olap")] +pub async fn get_filters_for_refunds( + state: AppState, + merchant_account: domain::MerchantAccount, +) -> RouterResponse { + let merchant_connector_accounts = if let services::ApplicationResponse::Json(data) = + super::admin::list_payment_connectors(state, merchant_account.merchant_id).await? + { + data + } else { + return Err(errors::ApiErrorResponse::InternalServerError.into()); + }; + + let connector_map = merchant_connector_accounts + .into_iter() + .filter_map(|merchant_connector_account| { + merchant_connector_account.connector_label.map(|label| { + let info = MerchantConnectorInfo { + connector_label: label, + merchant_connector_id: merchant_connector_account.merchant_connector_id, + }; + (merchant_connector_account.connector_name, info) + }) + }) + .fold( + HashMap::new(), + |mut map: HashMap>, (connector_name, info)| { + map.entry(connector_name).or_default().push(info); + map + }, + ); + + Ok(services::ApplicationResponse::Json( + api_models::refunds::RefundListFilters { + connector: connector_map, + currency: enums::Currency::iter().collect(), + refund_status: enums::RefundStatus::iter().collect(), + }, + )) +} + impl ForeignFrom for api::RefundResponse { fn foreign_from(refund: storage::Refund) -> Self { let refund = refund; diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index ca3921d6199c..df1130b7af67 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -930,6 +930,7 @@ impl RefundInterface for MockDb { offset: i64, ) -> CustomResult, errors::StorageError> { let mut unique_connectors = HashSet::new(); + let mut unique_merchant_connector_ids = HashSet::new(); let mut unique_currencies = HashSet::new(); let mut unique_statuses = HashSet::new(); @@ -940,6 +941,14 @@ impl RefundInterface for MockDb { }); } + if let Some(merchant_connector_ids) = &refund_details.merchant_connector_id { + merchant_connector_ids + .iter() + .for_each(|unique_merchant_connector_id| { + unique_merchant_connector_ids.insert(unique_merchant_connector_id); + }); + } + if let Some(currencies) = &refund_details.currency { currencies.iter().for_each(|currency| { unique_currencies.insert(currency); @@ -982,9 +991,25 @@ impl RefundInterface for MockDb { range.end_time.unwrap_or_else(common_utils::date_time::now) }) }) + .filter(|refund| { + refund_details + .amount_filter + .as_ref() + .map_or(true, |amount| { + refund.refund_amount >= amount.start_amount.unwrap_or(i64::MIN) + && refund.refund_amount <= amount.end_amount.unwrap_or(i64::MAX) + }) + }) .filter(|refund| { unique_connectors.is_empty() || unique_connectors.contains(&refund.connector) }) + .filter(|refund| { + unique_merchant_connector_ids.is_empty() + || refund + .merchant_connector_id + .as_ref() + .map_or(false, |id| unique_merchant_connector_ids.contains(id)) + }) .filter(|refund| { unique_currencies.is_empty() || unique_currencies.contains(&refund.currency) }) @@ -1054,6 +1079,7 @@ impl RefundInterface for MockDb { _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { let mut unique_connectors = HashSet::new(); + let mut unique_merchant_connector_ids = HashSet::new(); let mut unique_currencies = HashSet::new(); let mut unique_statuses = HashSet::new(); @@ -1064,6 +1090,14 @@ impl RefundInterface for MockDb { }); } + if let Some(merchant_connector_ids) = &refund_details.merchant_connector_id { + merchant_connector_ids + .iter() + .for_each(|unique_merchant_connector_id| { + unique_merchant_connector_ids.insert(unique_merchant_connector_id); + }); + } + if let Some(currencies) = &refund_details.currency { currencies.iter().for_each(|currency| { unique_currencies.insert(currency); @@ -1106,9 +1140,25 @@ impl RefundInterface for MockDb { range.end_time.unwrap_or_else(common_utils::date_time::now) }) }) + .filter(|refund| { + refund_details + .amount_filter + .as_ref() + .map_or(true, |amount| { + refund.refund_amount >= amount.start_amount.unwrap_or(i64::MIN) + && refund.refund_amount <= amount.end_amount.unwrap_or(i64::MAX) + }) + }) .filter(|refund| { unique_connectors.is_empty() || unique_connectors.contains(&refund.connector) }) + .filter(|refund| { + unique_merchant_connector_ids.is_empty() + || refund + .merchant_connector_id + .as_ref() + .map_or(false, |id| unique_merchant_connector_ids.contains(id)) + }) .filter(|refund| { unique_currencies.is_empty() || unique_currencies.contains(&refund.currency) }) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 1c680b8bc8e9..21cc994381eb 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -333,7 +333,7 @@ impl Payments { .route(web::post().to(payments_list_by_filter)), ) .service(web::resource("/filter").route(web::post().to(get_filters_for_payments))) - .service(web::resource("/filter_v2").route(web::get().to(get_payment_filters))) + .service(web::resource("/v2/filter").route(web::get().to(get_payment_filters))) } #[cfg(feature = "oltp")] { @@ -716,7 +716,8 @@ impl Refunds { { route = route .service(web::resource("/list").route(web::post().to(refunds_list))) - .service(web::resource("/filter").route(web::post().to(refunds_filter_list))); + .service(web::resource("/filter").route(web::post().to(refunds_filter_list))) + .service(web::resource("/v2/filter").route(web::get().to(get_refunds_filters))); } #[cfg(feature = "oltp")] { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 7b10247dded9..30b582079e32 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -136,7 +136,8 @@ impl From for ApiIdentifier { | Flow::RefundsRetrieve | Flow::RefundsRetrieveForceSync | Flow::RefundsUpdate - | Flow::RefundsList => Self::Refunds, + | Flow::RefundsList + | Flow::RefundsFilters => Self::Refunds, Flow::FrmFulfillment | Flow::IncomingWebhookReceive diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index d68c7138213f..3df6c8716682 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -230,6 +230,7 @@ pub async fn refunds_list( ) .await } + /// Refunds - Filter /// /// To list the refunds filters associated with list of connectors, currencies and payment statuses @@ -267,3 +268,36 @@ pub async fn refunds_filter_list( ) .await } + +/// Refunds - Filter V2 +/// +/// To list the refunds filters associated with list of connectors, currencies and payment statuses +#[utoipa::path( + get, + path = "/refunds/v2/filter", + responses( + (status = 200, description = "List of static filters", body = RefundListFilters), + ), + tag = "Refunds", + operation_id = "List all filters for Refunds", + security(("api_key" = [])) +)] +#[instrument(skip_all, fields(flow = ?Flow::RefundsFilters))] +#[cfg(feature = "olap")] +pub async fn get_refunds_filters(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::RefundsFilters; + api::server_wrap( + flow, + state, + &req, + (), + |state, auth, _, _| get_filters_for_refunds(state, auth.merchant_account), + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::RefundRead), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + ) + .await +} diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 76bdf8655f6f..75030721ec53 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -1,3 +1,4 @@ +use api_models::payments::AmountFilter; use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::errors::CustomResult; use diesel::{associations::HasTable, ExpressionMethods, QueryDsl}; @@ -104,10 +105,30 @@ impl RefundDbExt for Refund { } } - if let Some(connector) = refund_list_details.clone().connector { + filter = match refund_list_details.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => filter.filter(dsl::refund_amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => filter.filter(dsl::refund_amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => filter.filter(dsl::refund_amount.le(end)), + _ => filter, + }; + + if let Some(connector) = refund_list_details.connector.clone() { filter = filter.filter(dsl::connector.eq_any(connector)); } + if let Some(merchant_connector_id) = refund_list_details.merchant_connector_id.clone() { + filter = filter.filter(dsl::merchant_connector_id.eq_any(merchant_connector_id)); + } + if let Some(filter_currency) = &refund_list_details.currency { filter = filter.filter(dsl::currency.eq_any(filter_currency.clone())); } @@ -227,10 +248,30 @@ impl RefundDbExt for Refund { } } - if let Some(connector) = refund_list_details.clone().connector { + filter = match refund_list_details.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => filter.filter(dsl::refund_amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => filter.filter(dsl::refund_amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => filter.filter(dsl::refund_amount.le(end)), + _ => filter, + }; + + if let Some(connector) = refund_list_details.connector.clone() { filter = filter.filter(dsl::connector.eq_any(connector)); } + if let Some(merchant_connector_id) = refund_list_details.merchant_connector_id.clone() { + filter = filter.filter(dsl::merchant_connector_id.eq_any(merchant_connector_id)) + } + if let Some(filter_currency) = &refund_list_details.currency { filter = filter.filter(dsl::currency.eq_any(filter_currency.clone())); } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index a893386ab87d..ffa492bc60d6 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -190,6 +190,8 @@ pub enum Flow { RefundsUpdate, /// Refunds list flow. RefundsList, + /// Refunds filters flow + RefundsFilters, // Retrieve forex flow. RetrieveForexFlow, /// Toggles recon service for a merchant. diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 259d16695234..cb82a2e7675b 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -4910,6 +4910,23 @@ "AliPayRedirection": { "type": "object" }, + "AmountFilter": { + "type": "object", + "properties": { + "start_amount": { + "type": "integer", + "format": "int64", + "description": "The start amount to filter list of transactions which are greater than or equal to the start amount", + "nullable": true + }, + "end_amount": { + "type": "integer", + "format": "int64", + "description": "The end amount to filter list of transactions which are less than or equal to the end amount", + "nullable": true + } + } + }, "AmountInfo": { "type": "object", "required": [ @@ -16785,6 +16802,14 @@ "description": "The starting point within a list of objects", "nullable": true }, + "amount_filter": { + "allOf": [ + { + "$ref": "#/components/schemas/AmountFilter" + } + ], + "nullable": true + }, "connector": { "type": "array", "items": { @@ -16793,6 +16818,14 @@ "description": "The list of connectors to filter refunds list", "nullable": true }, + "merchant_connector_id": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of merchant connector ids to filter the refunds list for selected label", + "nullable": true + }, "currency": { "type": "array", "items": {