feat(router): add filters for refunds (#1501)

Co-authored-by: Sampras Lopes <lsampras@protonmail.com>
This commit is contained in:
Apoorv Dixit
2023-06-30 18:24:41 +05:30
committed by GitHub
parent 7bb0aa5ceb
commit 88860b9c0b
10 changed files with 342 additions and 60 deletions

View File

@ -507,10 +507,13 @@ pub enum WalletIssuer {
Debug, Debug,
Default, Default,
Eq, Eq,
Hash,
PartialEq, PartialEq,
strum::Display, strum::Display,
strum::EnumString, strum::EnumString,
frunk::LabelledGeneric, frunk::LabelledGeneric,
serde::Deserialize,
serde::Serialize,
)] )]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum RefundStatus { pub enum RefundStatus {

View File

@ -1,4 +1,4 @@
use common_utils::{custom_serde, pii}; use common_utils::pii;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use utoipa::ToSchema; use utoipa::ToSchema;
@ -132,29 +132,28 @@ pub struct RefundListRequest {
pub payment_id: Option<String>, pub payment_id: Option<String>,
/// Limit on the number of objects to return /// Limit on the number of objects to return
pub limit: Option<i64>, pub limit: Option<i64>,
/// The time at which refund is created /// The starting point within a list of objects
#[serde(default, with = "custom_serde::iso8601::option")] pub offset: Option<i64>,
pub created: Option<PrimitiveDateTime>, /// 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).
/// Time less than the refund created time pub time_range: Option<TimeRange>,
#[serde(default, rename = "created.lt", with = "custom_serde::iso8601::option")] /// The list of connectors to filter refunds list
pub created_lt: Option<PrimitiveDateTime>, pub connector: Option<Vec<String>>,
/// Time greater than the refund created time /// The list of currencies to filter refunds list
#[serde(default, rename = "created.gt", with = "custom_serde::iso8601::option")] #[schema(value_type = Option<Vec<Currency>>)]
pub created_gt: Option<PrimitiveDateTime>, pub currency: Option<Vec<enums::Currency>>,
/// Time less than or equals to the refund created time /// The list of refund statuses to filter refunds list
#[serde( #[schema(value_type = Option<Vec<RefundStatus>>)]
default, pub refund_status: Option<Vec<enums::RefundStatus>>,
rename = "created.lte", }
with = "custom_serde::iso8601::option"
)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, ToSchema)]
pub created_lte: Option<PrimitiveDateTime>, pub struct TimeRange {
/// Time greater than or equals to the refund created time /// The start time to filter refunds list or to get list of filters. To get list of filters start time is needed to be passed
#[serde( #[serde(with = "common_utils::custom_serde::iso8601")]
default, pub start_time: PrimitiveDateTime,
rename = "created.gte", /// The end time to filter refunds list or to get list of filters. If not passed the default time is now
with = "custom_serde::iso8601::option" #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
)] pub end_time: Option<PrimitiveDateTime>,
pub created_gte: Option<PrimitiveDateTime>,
} }
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
@ -165,9 +164,31 @@ pub struct RefundListResponse {
pub data: Vec<RefundResponse>, pub data: Vec<RefundResponse>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, ToSchema)]
pub struct RefundListMetaData {
/// The list of available connector filters
pub connector: Vec<String>,
/// 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 status: Vec<enums::RefundStatus>,
}
/// The status for refunds /// The status for refunds
#[derive( #[derive(
Debug, Eq, Clone, Copy, PartialEq, Default, Deserialize, Serialize, ToSchema, strum::Display, Debug,
Eq,
Clone,
Copy,
PartialEq,
Default,
Deserialize,
Serialize,
ToSchema,
strum::Display,
strum::EnumIter,
)] )]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum RefundStatus { pub enum RefundStatus {

View File

@ -24,6 +24,7 @@ use crate::{
}, },
utils::{self, OptionExt}, utils::{self, OptionExt},
}; };
// ********************************************** REFUND EXECUTE ********************************************** // ********************************************** REFUND EXECUTE **********************************************
#[instrument(skip_all)] #[instrument(skip_all)]
@ -646,20 +647,24 @@ pub async fn refund_list(
req: api_models::refunds::RefundListRequest, req: api_models::refunds::RefundListRequest,
) -> RouterResponse<api_models::refunds::RefundListResponse> { ) -> RouterResponse<api_models::refunds::RefundListResponse> {
let limit = validator::validate_refund_list(req.limit)?; let limit = validator::validate_refund_list(req.limit)?;
let offset = req.offset.unwrap_or_default();
let refund_list = db let refund_list = db
.filter_refund_by_constraints( .filter_refund_by_constraints(
&merchant_account.merchant_id, &merchant_account.merchant_id,
&req, &req,
merchant_account.storage_scheme, merchant_account.storage_scheme,
limit, limit,
offset,
) )
.await .await
.change_context(errors::ApiErrorResponse::RefundNotFound)?; .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
let data: Vec<refunds::RefundResponse> = refund_list let data: Vec<refunds::RefundResponse> = refund_list
.into_iter() .into_iter()
.map(ForeignInto::foreign_into) .map(ForeignInto::foreign_into)
.collect(); .collect();
Ok(services::ApplicationResponse::Json( Ok(services::ApplicationResponse::Json(
api_models::refunds::RefundListResponse { api_models::refunds::RefundListResponse {
size: data.len(), size: data.len(),
@ -668,6 +673,25 @@ pub async fn refund_list(
)) ))
} }
#[instrument(skip_all)]
#[cfg(feature = "olap")]
pub async fn refund_filter_list(
db: &dyn db::StorageInterface,
merchant_account: domain::MerchantAccount,
req: api_models::refunds::TimeRange,
) -> RouterResponse<api_models::refunds::RefundListMetaData> {
let filter_list = db
.filter_refund_by_meta_constraints(
&merchant_account.merchant_id,
&req,
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
Ok(services::ApplicationResponse::Json(filter_list))
}
impl ForeignFrom<storage::Refund> for api::RefundResponse { impl ForeignFrom<storage::Refund> for api::RefundResponse {
fn foreign_from(refund: storage::Refund) -> Self { fn foreign_from(refund: storage::Refund) -> Self {
let refund = refund; let refund = refund;

View File

@ -10,6 +10,11 @@ use crate::{
utils::{self, OptionExt}, utils::{self, OptionExt},
}; };
// Limit constraints for refunds list flow
pub const LOWER_LIMIT: i64 = 1;
pub const UPPER_LIMIT: i64 = 100;
pub const DEFAULT_LIMIT: i64 = 10;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum RefundValidationError { pub enum RefundValidationError {
#[error("The payment attempt was not successful")] #[error("The payment attempt was not successful")]
@ -125,7 +130,7 @@ pub async fn validate_uniqueness_of_refund_id_against_merchant_id(
pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::ApiErrorResponse> { pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::ApiErrorResponse> {
match limit { match limit {
Some(limit_val) => { Some(limit_val) => {
if !(1..=100).contains(&limit_val) { if !(LOWER_LIMIT..=UPPER_LIMIT).contains(&limit_val) {
Err(errors::ApiErrorResponse::InvalidRequestData { Err(errors::ApiErrorResponse::InvalidRequestData {
message: "limit should be in between 1 and 100".to_string(), message: "limit should be in between 1 and 100".to_string(),
} }
@ -134,7 +139,7 @@ pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::Api
Ok(limit_val) Ok(limit_val)
} }
} }
None => Ok(10), None => Ok(DEFAULT_LIMIT),
} }
} }

View File

@ -1,11 +1,19 @@
#[cfg(feature = "olap")]
use std::collections::HashSet;
use storage_models::{errors::DatabaseError, refund::RefundUpdateInternal}; use storage_models::{errors::DatabaseError, refund::RefundUpdateInternal};
use super::MockDb; use super::MockDb;
#[cfg(feature = "olap")]
use crate::types::transformers::ForeignInto;
use crate::{ use crate::{
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
types::storage::{self as storage_types, enums}, types::storage::{self as storage_types, enums},
}; };
#[cfg(feature = "olap")]
const MAX_LIMIT: usize = 100;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait RefundInterface { pub trait RefundInterface {
async fn find_refund_by_internal_reference_id_merchant_id( async fn find_refund_by_internal_reference_id_merchant_id(
@ -64,7 +72,16 @@ pub trait RefundInterface {
refund_details: &api_models::refunds::RefundListRequest, refund_details: &api_models::refunds::RefundListRequest,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError>; ) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError>;
#[cfg(feature = "olap")]
async fn filter_refund_by_meta_constraints(
&self,
merchant_id: &str,
refund_details: &api_models::refunds::TimeRange,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::StorageError>;
} }
#[cfg(not(feature = "kv_store"))] #[cfg(not(feature = "kv_store"))]
@ -189,6 +206,7 @@ mod storage {
refund_details: &api_models::refunds::RefundListRequest, refund_details: &api_models::refunds::RefundListRequest,
_storage_scheme: enums::MerchantStorageScheme, _storage_scheme: enums::MerchantStorageScheme,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> { ) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?; let conn = connection::pg_connection_read(self).await?;
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints( <storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(
@ -196,6 +214,25 @@ mod storage {
merchant_id, merchant_id,
refund_details, refund_details,
limit, limit,
offset,
)
.await
.map_err(Into::into)
.into_report()
}
#[cfg(feature = "olap")]
async fn filter_refund_by_meta_constraints(
&self,
merchant_id: &str,
refund_details: &api_models::refunds::TimeRange,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_meta_constraints(
&conn,
merchant_id,
refund_details,
) )
.await .await
.map_err(Into::into) .map_err(Into::into)
@ -584,11 +621,32 @@ mod storage {
refund_details: &api_models::refunds::RefundListRequest, refund_details: &api_models::refunds::RefundListRequest,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> { ) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
match storage_scheme { match storage_scheme {
enums::MerchantStorageScheme::PostgresOnly => { enums::MerchantStorageScheme::PostgresOnly => {
let conn = connection::pg_connection_read(self).await?; let conn = connection::pg_connection_read(self).await?;
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(&conn, merchant_id, refund_details, limit) <storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(&conn, merchant_id, refund_details, limit, offset)
.await
.map_err(Into::into)
.into_report()
}
enums::MerchantStorageScheme::RedisKv => Err(errors::StorageError::KVError.into()),
}
}
#[cfg(feature = "olap")]
async fn filter_refund_by_meta_constraints(
&self,
merchant_id: &str,
refund_details: &api_models::refunds::TimeRange,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::StorageError> {
match storage_scheme {
enums::MerchantStorageScheme::PostgresOnly => {
let conn = connection::pg_connection_read(self).await?;
<storage_models::refund::Refund as storage_types::RefundDbExt>::filter_by_meta_constraints(&conn, merchant_id, refund_details)
.await .await
.map_err(Into::into) .map_err(Into::into)
.into_report() .into_report()
@ -760,14 +818,64 @@ impl RefundInterface for MockDb {
_refund_details: &api_models::refunds::RefundListRequest, _refund_details: &api_models::refunds::RefundListRequest,
_storage_scheme: enums::MerchantStorageScheme, _storage_scheme: enums::MerchantStorageScheme,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> { ) -> CustomResult<Vec<storage_models::refund::Refund>, errors::StorageError> {
let refunds = self.refunds.lock().await; Ok(self
.refunds
Ok(refunds .lock()
.await
.iter() .iter()
.filter(|refund| refund.merchant_id == merchant_id) .filter(|refund| refund.merchant_id == merchant_id)
.take(usize::try_from(limit).unwrap_or(usize::MAX)) .skip(usize::try_from(offset).unwrap_or_default())
.take(usize::try_from(limit).unwrap_or(MAX_LIMIT))
.cloned() .cloned()
.collect::<Vec<_>>()) .collect::<Vec<_>>())
} }
#[cfg(feature = "olap")]
async fn filter_refund_by_meta_constraints(
&self,
_merchant_id: &str,
refund_details: &api_models::refunds::TimeRange,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::StorageError> {
let refunds = self.refunds.lock().await;
let start_time = refund_details.start_time;
let end_time = refund_details
.end_time
.unwrap_or_else(common_utils::date_time::now);
let filtered_refunds = refunds
.iter()
.filter(|refund| refund.created_at >= start_time && refund.created_at <= end_time)
.cloned()
.collect::<Vec<storage_models::refund::Refund>>();
let mut refund_meta_data = api_models::refunds::RefundListMetaData {
connector: vec![],
currency: vec![],
status: vec![],
};
let mut unique_connectors = HashSet::new();
let mut unique_currencies = HashSet::new();
let mut unique_statuses = HashSet::new();
for refund in filtered_refunds.into_iter() {
unique_connectors.insert(refund.connector);
let currency: api_models::enums::Currency = refund.currency.foreign_into();
unique_currencies.insert(currency);
let status: api_models::enums::RefundStatus = refund.refund_status.foreign_into();
unique_statuses.insert(status);
}
refund_meta_data.connector = unique_connectors.into_iter().collect();
refund_meta_data.currency = unique_currencies.into_iter().collect();
refund_meta_data.status = unique_statuses.into_iter().collect();
Ok(refund_meta_data)
}
} }

View File

@ -245,6 +245,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::RedirectResponse, api_models::payments::RedirectResponse,
api_models::refunds::RefundListRequest, api_models::refunds::RefundListRequest,
api_models::refunds::RefundListResponse, api_models::refunds::RefundListResponse,
api_models::refunds::TimeRange,
api_models::mandates::MandateRevokedResponse, api_models::mandates::MandateRevokedResponse,
api_models::mandates::MandateResponse, api_models::mandates::MandateResponse,
api_models::mandates::MandateCardDetails, api_models::mandates::MandateCardDetails,

View File

@ -243,7 +243,9 @@ impl Refunds {
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
{ {
route = route.service(web::resource("/list").route(web::get().to(refunds_list))); route = route
.service(web::resource("/list").route(web::post().to(refunds_list)))
.service(web::resource("/filter").route(web::post().to(refunds_filter_list)));
} }
#[cfg(feature = "oltp")] #[cfg(feature = "oltp")]
{ {

View File

@ -179,17 +179,9 @@ pub async fn refunds_update(
/// ///
/// To list the refunds associated with a payment_id or with the merchant, if payment_id is not provided /// To list the refunds associated with a payment_id or with the merchant, if payment_id is not provided
#[utoipa::path( #[utoipa::path(
get, post,
path = "/refunds/list", path = "/refunds/list",
params( request_body=RefundListRequest,
("payment_id" = String, Query, description = "The identifier for the payment"),
("limit" = i64, Query, description = "Limit on the number of objects to return"),
("created" = PrimitiveDateTime, Query, description = "The time at which refund is created"),
("created_lt" = PrimitiveDateTime, Query, description = "Time less than the refund created time"),
("created_gt" = PrimitiveDateTime, Query, description = "Time greater than the refund created time"),
("created_lte" = PrimitiveDateTime, Query, description = "Time less than or equals to the refund created time"),
("created_gte" = PrimitiveDateTime, Query, description = "Time greater than or equals to the refund created time")
),
responses( responses(
(status = 200, description = "List of refunds", body = RefundListResponse), (status = 200, description = "List of refunds", body = RefundListResponse),
), ),
@ -199,11 +191,10 @@ pub async fn refunds_update(
)] )]
#[instrument(skip_all, fields(flow = ?Flow::RefundsList))] #[instrument(skip_all, fields(flow = ?Flow::RefundsList))]
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
// #[get("/list")]
pub async fn refunds_list( pub async fn refunds_list(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
payload: web::Query<api_models::refunds::RefundListRequest>, payload: web::Json<api_models::refunds::RefundListRequest>,
) -> HttpResponse { ) -> HttpResponse {
let flow = Flow::RefundsList; let flow = Flow::RefundsList;
api::server_wrap( api::server_wrap(
@ -216,3 +207,36 @@ pub async fn refunds_list(
) )
.await .await
} }
/// Refunds - Filter
///
/// To list the refunds filters associated with list of connectors, currencies and payment statuses
#[utoipa::path(
post,
path = "/refunds/filter",
request_body=TimeRange,
responses(
(status = 200, description = "List of filters", body = RefundListMetaData),
),
tag = "Refunds",
operation_id = "List all filters for Refunds",
security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::RefundsList))]
#[cfg(feature = "olap")]
pub async fn refunds_filter_list(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<api_models::refunds::TimeRange>,
) -> HttpResponse {
let flow = Flow::RefundsList;
api::server_wrap(
flow,
state.get_ref(),
&req,
payload.into_inner(),
|state, auth, req| refund_filter_list(&*state.store, auth.merchant_account, req),
&auth::ApiKeyAuth,
)
.await
}

View File

@ -5,9 +5,13 @@ use error_stack::{IntoReport, ResultExt};
pub use storage_models::refund::{ pub use storage_models::refund::{
Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal, Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal,
}; };
use storage_models::{errors, schema::refund::dsl}; use storage_models::{
enums::{Currency, RefundStatus},
errors,
schema::refund::dsl,
};
use crate::{connection::PgPooledConn, logger}; use crate::{connection::PgPooledConn, logger, types::transformers::ForeignInto};
#[cfg(feature = "kv_store")] #[cfg(feature = "kv_store")]
impl crate::utils::storage_partitioning::KvStorePartition for Refund {} impl crate::utils::storage_partitioning::KvStorePartition for Refund {}
@ -19,7 +23,14 @@ pub trait RefundDbExt: Sized {
merchant_id: &str, merchant_id: &str,
refund_list_details: &api_models::refunds::RefundListRequest, refund_list_details: &api_models::refunds::RefundListRequest,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<Self>, errors::DatabaseError>; ) -> CustomResult<Vec<Self>, errors::DatabaseError>;
async fn filter_by_meta_constraints(
conn: &PgPooledConn,
merchant_id: &str,
refund_list_details: &api_models::refunds::TimeRange,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::DatabaseError>;
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -29,6 +40,7 @@ impl RefundDbExt for Refund {
merchant_id: &str, merchant_id: &str,
refund_list_details: &api_models::refunds::RefundListRequest, refund_list_details: &api_models::refunds::RefundListRequest,
limit: i64, limit: i64,
offset: i64,
) -> CustomResult<Vec<Self>, errors::DatabaseError> { ) -> CustomResult<Vec<Self>, errors::DatabaseError> {
let mut filter = <Self as HasTable>::table() let mut filter = <Self as HasTable>::table()
.filter(dsl::merchant_id.eq(merchant_id.to_owned())) .filter(dsl::merchant_id.eq(merchant_id.to_owned()))
@ -40,24 +52,36 @@ impl RefundDbExt for Refund {
filter = filter.filter(dsl::payment_id.eq(pid.to_owned())); filter = filter.filter(dsl::payment_id.eq(pid.to_owned()));
} }
None => { None => {
filter = filter.limit(limit); filter = filter.limit(limit).offset(offset);
} }
}; };
if let Some(created) = refund_list_details.created { if let Some(time_range) = refund_list_details.time_range {
filter = filter.filter(dsl::created_at.eq(created)); 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(created_lt) = refund_list_details.created_lt {
filter = filter.filter(dsl::created_at.lt(created_lt)); if let Some(connector) = refund_list_details.clone().connector {
filter = filter.filter(dsl::connector.eq_any(connector));
} }
if let Some(created_gt) = refund_list_details.created_gt {
filter = filter.filter(dsl::created_at.gt(created_gt)); if let Some(filter_currency) = &refund_list_details.currency {
let currency: Vec<Currency> = filter_currency
.iter()
.map(|currency| (*currency).foreign_into())
.collect();
filter = filter.filter(dsl::currency.eq_any(currency));
} }
if let Some(created_lte) = refund_list_details.created_lte {
filter = filter.filter(dsl::created_at.le(created_lte)); if let Some(filter_refund_status) = &refund_list_details.refund_status {
} let refund_status: Vec<RefundStatus> = filter_refund_status
if let Some(created_gte) = refund_list_details.created_gte { .iter()
filter = filter.filter(dsl::created_at.gt(created_gte)); .map(|refund_status| (*refund_status).foreign_into())
.collect();
filter = filter.filter(dsl::refund_status.eq_any(refund_status));
} }
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string()); logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
@ -69,4 +93,68 @@ impl RefundDbExt for Refund {
.change_context(errors::DatabaseError::NotFound) .change_context(errors::DatabaseError::NotFound)
.attach_printable_lazy(|| "Error filtering records by predicate") .attach_printable_lazy(|| "Error filtering records by predicate")
} }
async fn filter_by_meta_constraints(
conn: &PgPooledConn,
merchant_id: &str,
refund_list_details: &api_models::refunds::TimeRange,
) -> CustomResult<api_models::refunds::RefundListMetaData, errors::DatabaseError> {
let start_time = refund_list_details.start_time;
let end_time = refund_list_details
.end_time
.unwrap_or_else(common_utils::date_time::now);
let filter = <Self as HasTable>::table()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.order(dsl::modified_at.desc())
.filter(dsl::created_at.ge(start_time))
.filter(dsl::created_at.le(end_time));
let filter_connector: Vec<String> = filter
.clone()
.select(dsl::connector)
.distinct()
.order_by(dsl::connector.asc())
.get_results_async(conn)
.await
.into_report()
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering records by connector")?;
let filter_currency: Vec<Currency> = filter
.clone()
.select(dsl::currency)
.distinct()
.order_by(dsl::currency.asc())
.get_results_async(conn)
.await
.into_report()
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering records by currency")?;
let filter_status: Vec<RefundStatus> = filter
.select(dsl::refund_status)
.distinct()
.order_by(dsl::refund_status.asc())
.get_results_async(conn)
.await
.into_report()
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering records by refund status")?;
let meta = api_models::refunds::RefundListMetaData {
connector: filter_connector,
currency: filter_currency
.into_iter()
.map(|curr| curr.foreign_into())
.collect(),
status: filter_status
.into_iter()
.map(|curr| curr.foreign_into())
.collect(),
};
Ok(meta)
}
} }

View File

@ -298,6 +298,12 @@ impl ForeignFrom<storage_enums::RefundStatus> for api_enums::RefundStatus {
} }
} }
impl ForeignFrom<api_enums::RefundStatus> for storage_enums::RefundStatus {
fn foreign_from(status: api_enums::RefundStatus) -> Self {
frunk::labelled_convert_from(status)
}
}
impl ForeignFrom<api_enums::CaptureMethod> for storage_enums::CaptureMethod { impl ForeignFrom<api_enums::CaptureMethod> for storage_enums::CaptureMethod {
fn foreign_from(capture_method: api_enums::CaptureMethod) -> Self { fn foreign_from(capture_method: api_enums::CaptureMethod) -> Self {
frunk::labelled_convert_from(capture_method) frunk::labelled_convert_from(capture_method)