mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(router): add filters for refunds (#1501)
Co-authored-by: Sampras Lopes <lsampras@protonmail.com>
This commit is contained in:
@ -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 {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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")]
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user