Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(refunds): update refunds filters #4409

Merged
merged 12 commits into from May 13, 2024
2 changes: 1 addition & 1 deletion crates/api_models/src/admin.rs
Expand Up @@ -522,7 +522,7 @@ pub struct MerchantConnectorWebhookDetails {
pub additional_secret: Option<Secret<String>>,
}

#[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,
Expand Down
10 changes: 8 additions & 2 deletions 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 {
Expand Down Expand Up @@ -61,3 +61,9 @@ impl ApiEventMetric for RefundListMetaData {
Some(ApiEventsType::ResourceListAPI)
}
}

impl ApiEventMetric for RefundListFilters {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::ResourceListAPI)
}
}
4 changes: 3 additions & 1 deletion crates/api_models/src/payments.rs
Expand Up @@ -3444,9 +3444,11 @@ pub struct PaymentListFiltersV2 {
pub authentication_type: Vec<enums::AuthenticationType>,
}

#[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<i64>,
/// The end amount to filter list of transactions which are less than or equal to the end amount
pub end_amount: Option<i64>,
}

Expand Down
27 changes: 24 additions & 3 deletions 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)]
Expand Down Expand Up @@ -146,11 +151,15 @@ pub struct RefundListRequest {
pub limit: Option<i64>,
/// The starting point within a list of objects
pub offset: Option<i64>,
/// 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<TimeRange>,
/// 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<AmountFilter>,
/// The list of connectors to filter refunds list
pub connector: Option<Vec<String>>,
/// The list of merchant connector ids to filter the refunds list for selected label
pub merchant_connector_id: Option<Vec<String>>,
/// The list of currencies to filter refunds list
#[schema(value_type = Option<Vec<Currency>>)]
pub currency: Option<Vec<enums::Currency>>,
Expand Down Expand Up @@ -181,6 +190,18 @@ pub struct RefundListMetaData {
pub refund_status: Vec<enums::RefundStatus>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct RefundListFilters {
/// The list of available connector filters
pub connector: HashMap<String, Vec<MerchantConnectorInfo>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will be the key in this hashmap?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please mention it in the doc comment.

/// The list of available currency filters
#[schema(value_type = Vec<Currency>)]
pub currency: Vec<enums::Currency>,
/// The list of available refund status filters
#[schema(value_type = Vec<RefundStatus>)]
pub refund_status: Vec<enums::RefundStatus>,
}

/// The status for refunds
#[derive(
Debug,
Expand Down
1 change: 1 addition & 0 deletions crates/common_enums/src/enums.rs
Expand Up @@ -1499,6 +1499,7 @@ pub enum PaymentType {
PartialEq,
strum::Display,
strum::EnumString,
strum::EnumIter,
serde::Serialize,
serde::Deserialize,
)]
Expand Down
1 change: 1 addition & 0 deletions crates/openapi/src/openapi.rs
Expand Up @@ -414,6 +414,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,
Expand Down
54 changes: 54 additions & 0 deletions 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,
Expand Down Expand Up @@ -767,6 +774,53 @@ 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<api_models::refunds::RefundListFilters> {
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 mut connector_map: HashMap<String, Vec<MerchantConnectorInfo>> = HashMap::new();
merchant_connector_accounts
.iter()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use into_iter() and remove as_ref() and clone().

.filter_map(|merchant_connector_account| {
merchant_connector_account
.connector_label
.as_ref()
.map(|label| {
let info = MerchantConnectorInfo {
connector_label: label.clone(),
merchant_connector_id: merchant_connector_account
.merchant_connector_id
.clone(),
};
(merchant_connector_account.connector_name.clone(), info)
})
})
.for_each(|(connector_name, info)| {
connector_map
.entry(connector_name.clone())
.or_default()
.push(info);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Ok(services::ApplicationResponse::Json(
api_models::refunds::RefundListFilters {
connector: connector_map,
currency: enums::Currency::iter().collect(),
refund_status: enums::RefundStatus::iter().collect(),
},
))
}

impl ForeignFrom<storage::Refund> for api::RefundResponse {
fn foreign_from(refund: storage::Refund) -> Self {
let refund = refund;
Expand Down
50 changes: 50 additions & 0 deletions crates/router/src/db/refund.rs
Expand Up @@ -930,6 +930,7 @@ impl RefundInterface for MockDb {
offset: i64,
) -> CustomResult<Vec<diesel_models::refund::Refund>, 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();

Expand All @@ -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);
Expand Down Expand Up @@ -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)
})
Expand Down Expand Up @@ -1054,6 +1079,7 @@ impl RefundInterface for MockDb {
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, 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();

Expand All @@ -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);
Expand Down Expand Up @@ -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)
})
Expand Down
3 changes: 2 additions & 1 deletion crates/router/src/routes/app.rs
Expand Up @@ -702,7 +702,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("/filter_v2").route(web::get().to(get_refunds_filters)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be named with/v2 prefix or suffix.

}
#[cfg(feature = "oltp")]
{
Expand Down
3 changes: 2 additions & 1 deletion crates/router/src/routes/lock_utils.rs
Expand Up @@ -134,7 +134,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::RefundsRetrieve
| Flow::RefundsRetrieveForceSync
| Flow::RefundsUpdate
| Flow::RefundsList => Self::Refunds,
| Flow::RefundsList
| Flow::RefundsFilters => Self::Refunds,

Flow::FrmFulfillment
| Flow::IncomingWebhookReceive
Expand Down
34 changes: 34 additions & 0 deletions crates/router/src/routes/refunds.rs
Expand Up @@ -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
Expand Down Expand Up @@ -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/filter_v2",
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<AppState>, 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
}