feat(refunds_v2): Add refunds list flow in v2 apis (#7966)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Amey Wale
2025-05-12 19:27:05 +05:30
committed by GitHub
parent 28b62e2693
commit 839eb2e8fb
14 changed files with 716 additions and 17 deletions

View File

@ -3306,6 +3306,43 @@
]
}
},
"/v2/refunds/list": {
"get": {
"tags": [
"Refunds"
],
"summary": "Refunds - List",
"description": "To list the refunds associated with a payment_id or with the merchant, if payment_id is not provided",
"operationId": "List all Refunds",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefundListRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "List of refunds",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RefundListResponse"
}
}
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/v2/process_tracker/revenue_recovery_workflow/{revenue_recovery_id}": {
"get": {
"tags": [
@ -20502,13 +20539,11 @@
"nullable": true
},
"refund_id": {
"type": "string",
"description": "The identifier for the refund",
"nullable": true
},
"profile_id": {
"type": "string",
"description": "The identifier for business profile",
"allOf": [
{
"$ref": "#/components/schemas/common_utils.id_type.GlobalRefundId"
}
],
"nullable": true
},
"limit": {
@ -20539,7 +20574,7 @@
"description": "The list of connectors to filter refunds list",
"nullable": true
},
"merchant_connector_id": {
"connector_id_list": {
"type": "array",
"items": {
"type": "string"

View File

@ -330,6 +330,7 @@ pub struct RefundErrorDetails {
pub message: String,
}
#[cfg(feature = "v1")]
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
pub struct RefundListRequest {
/// The identifier for the payment
@ -361,7 +362,35 @@ pub struct RefundListRequest {
#[schema(value_type = Option<Vec<RefundStatus>>)]
pub refund_status: Option<Vec<enums::RefundStatus>>,
}
#[cfg(feature = "v2")]
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)]
pub struct RefundListRequest {
/// The identifier for the payment
#[schema(value_type = Option<String>)]
pub payment_id: Option<common_utils::id_type::GlobalPaymentId>,
/// The identifier for the refund
pub refund_id: Option<common_utils::id_type::GlobalRefundId>,
/// Limit on the number of objects to return
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)
#[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
#[schema(value_type = Option<Vec<String>>)]
pub connector_id_list: Option<Vec<common_utils::id_type::MerchantConnectorAccountId>>,
/// The list of currencies to filter refunds list
#[schema(value_type = Option<Vec<Currency>>)]
pub currency: Option<Vec<enums::Currency>>,
/// The list of refund statuses to filter refunds list
#[schema(value_type = Option<Vec<RefundStatus>>)]
pub refund_status: Option<Vec<enums::RefundStatus>>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, ToSchema)]
pub struct RefundListResponse {
/// The number of refunds included in the list

View File

@ -17,6 +17,7 @@ v2 = ["api_models/v2", "diesel_models/v2", "common_utils/v2", "common_types/v2"]
v1 = ["api_models/v1", "diesel_models/v1", "common_utils/v1", "common_types/v1"]
customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2"]
payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2"]
refunds_v2 = ["api_models/refunds_v2"]
dummy_connector = []
revenue_recovery= []

View File

@ -1,5 +1,9 @@
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
use crate::business_profile::Profile;
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
use crate::errors;
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
pub struct RefundListConstraints {
pub payment_id: Option<common_utils::id_type::PaymentId>,
pub refund_id: Option<String>,
@ -14,6 +18,22 @@ pub struct RefundListConstraints {
pub refund_status: Option<Vec<common_enums::RefundStatus>>,
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
pub struct RefundListConstraints {
pub payment_id: Option<common_utils::id_type::GlobalPaymentId>,
pub refund_id: Option<common_utils::id_type::GlobalRefundId>,
pub profile_id: common_utils::id_type::ProfileId,
pub limit: Option<i64>,
pub offset: Option<i64>,
pub time_range: Option<common_utils::types::TimeRange>,
pub amount_filter: Option<api_models::payments::AmountFilter>,
pub connector: Option<Vec<String>>,
pub connector_id_list: Option<Vec<common_utils::id_type::MerchantConnectorAccountId>>,
pub currency: Option<Vec<common_enums::Currency>>,
pub refund_status: Option<Vec<common_enums::RefundStatus>>,
}
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
impl
TryFrom<(
api_models::refunds::RefundListRequest,
@ -80,3 +100,35 @@ impl
})
}
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
impl From<(api_models::refunds::RefundListRequest, Profile)> for RefundListConstraints {
fn from((value, profile): (api_models::refunds::RefundListRequest, Profile)) -> Self {
let api_models::refunds::RefundListRequest {
payment_id,
refund_id,
connector,
currency,
refund_status,
limit,
offset,
time_range,
amount_filter,
connector_id_list,
} = value;
Self {
payment_id,
refund_id,
profile_id: profile.get_id().to_owned(),
limit,
offset,
time_range,
amount_filter,
connector,
connector_id_list,
currency,
refund_status,
}
}
}

View File

@ -149,6 +149,7 @@ Never share your secret api keys. Keep them guarded and secure.
//Routes for refunds
routes::refunds::refunds_create,
routes::refunds::refunds_retrieve,
routes::refunds::refunds_list,
// Routes for Revenue Recovery flow under Process Tracker
routes::revenue_recovery::revenue_recovery_pt_retrieve_api

View File

@ -128,6 +128,7 @@ pub async fn refunds_update() {}
operation_id = "List all Refunds",
security(("api_key" = []))
)]
#[cfg(feature = "v1")]
pub fn refunds_list() {}
/// Refunds - List For the Given profiles
@ -233,3 +234,20 @@ pub async fn refunds_create() {}
)]
#[cfg(feature = "v2")]
pub async fn refunds_retrieve() {}
/// Refunds - List
///
/// To list the refunds associated with a payment_id or with the merchant, if payment_id is not provided
#[utoipa::path(
get,
path = "/v2/refunds/list",
request_body=RefundListRequest,
responses(
(status = 200, description = "List of refunds", body = RefundListResponse),
),
tag = "Refunds",
operation_id = "List all Refunds",
security(("api_key" = []))
)]
#[cfg(feature = "v2")]
pub fn refunds_list() {}

View File

@ -39,7 +39,7 @@ customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswit
payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"]
dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"]
revenue_recovery =["api_models/revenue_recovery","hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery","hyperswitch_connectors/revenue_recovery"]
refunds_v2 = ["api_models/refunds_v2", "diesel_models/refunds_v2", "storage_impl/refunds_v2"]
refunds_v2 = ["api_models/refunds_v2", "diesel_models/refunds_v2", "storage_impl/refunds_v2", "hyperswitch_domain_models/refunds_v2"]
# Partial Auth
# The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request.

View File

@ -4,6 +4,7 @@ use api_models::{enums::Connector, refunds::RefundErrorDetails};
use common_utils::{id_type, types as common_utils_types};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
refunds::RefundListConstraints,
router_data::{ErrorResponse, RouterData},
router_data_v2::RefundFlowData,
};
@ -734,6 +735,56 @@ pub fn build_refund_update_for_rsync(
}
}
// ********************************************** Refund list **********************************************
/// If payment_id is provided, lists all the refunds associated with that particular payment_id
/// If payment_id is not provided, lists the refunds associated with that particular merchant - to the limit specified,if no limits given, it is 10 by default
#[instrument(skip_all)]
#[cfg(feature = "olap")]
pub async fn refund_list(
state: SessionState,
merchant_account: domain::MerchantAccount,
profile: domain::Profile,
req: refunds::RefundListRequest,
) -> errors::RouterResponse<refunds::RefundListResponse> {
let db = state.store;
let limit = refunds_validator::validate_refund_list(req.limit)?;
let offset = req.offset.unwrap_or_default();
let refund_list = db
.filter_refund_by_constraints(
merchant_account.get_id(),
RefundListConstraints::from((req.clone(), profile.clone())),
merchant_account.storage_scheme,
limit,
offset,
)
.await
.to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
let data: Vec<refunds::RefundResponse> = refund_list
.into_iter()
.map(refunds::RefundResponse::foreign_try_from)
.collect::<Result<_, _>>()?;
let total_count = db
.get_total_count_of_refunds(
merchant_account.get_id(),
RefundListConstraints::from((req, profile)),
merchant_account.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;
Ok(services::ApplicationResponse::Json(
api_models::refunds::RefundListResponse {
count: data.len(),
total_count,
data,
},
))
}
// ********************************************** VALIDATIONS **********************************************
#[instrument(skip_all)]

View File

@ -2844,6 +2844,26 @@ impl RefundInterface for KafkaStore {
.await
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn filter_refund_by_constraints(
&self,
merchant_id: &id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
storage_scheme: MerchantStorageScheme,
limit: i64,
offset: i64,
) -> CustomResult<Vec<storage::Refund>, errors::StorageError> {
self.diesel_store
.filter_refund_by_constraints(
merchant_id,
refund_details,
storage_scheme,
limit,
offset,
)
.await
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "refunds_v2"),
@ -2892,6 +2912,18 @@ impl RefundInterface for KafkaStore {
.get_total_count_of_refunds(merchant_id, refund_details, storage_scheme)
.await
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn get_total_count_of_refunds(
&self,
merchant_id: &id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
self.diesel_store
.get_total_count_of_refunds(merchant_id, refund_details, storage_scheme)
.await
}
}
#[async_trait::async_trait]

View File

@ -91,6 +91,16 @@ pub trait RefundInterface {
offset: i64,
) -> CustomResult<Vec<diesel_models::refund::Refund>, errors::StorageError>;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn filter_refund_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
storage_scheme: enums::MerchantStorageScheme,
limit: i64,
offset: i64,
) -> CustomResult<Vec<diesel_models::refund::Refund>, errors::StorageError>;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "refunds_v2"),
@ -127,6 +137,14 @@ pub trait RefundInterface {
refund_details: &refunds::RefundListConstraints,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError>;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn get_total_count_of_refunds(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError>;
}
#[cfg(not(feature = "kv_store"))]
@ -925,6 +943,28 @@ mod storage {
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
#[instrument(skip_all)]
async fn filter_refund_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
_storage_scheme: enums::MerchantStorageScheme,
limit: i64,
offset: i64,
) -> CustomResult<Vec<diesel_models::refund::Refund>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
<diesel_models::refund::Refund as storage_types::RefundDbExt>::filter_by_constraints(
&conn,
merchant_id,
refund_details,
limit,
offset,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "refunds_v2"),
@ -983,6 +1023,24 @@ mod storage {
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
#[instrument(skip_all)]
async fn get_total_count_of_refunds(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
<diesel_models::refund::Refund as storage_types::RefundDbExt>::get_refunds_count(
&conn,
merchant_id,
refund_details,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
}
}
@ -1369,6 +1427,115 @@ impl RefundInterface for MockDb {
Ok(filtered_refunds)
}
#[cfg(all(feature = "v2", feature = "refunds_v2", feature = "olap"))]
async fn filter_refund_by_constraints(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
_storage_scheme: enums::MerchantStorageScheme,
limit: i64,
offset: i64,
) -> CustomResult<Vec<diesel_models::refund::Refund>, errors::StorageError> {
let mut unique_connectors = HashSet::new();
let mut unique_connector_ids = HashSet::new();
let mut unique_currencies = HashSet::new();
let mut unique_statuses = HashSet::new();
// Fill the hash sets with data from refund_details
if let Some(connectors) = &refund_details.connector {
connectors.iter().for_each(|connector| {
unique_connectors.insert(connector);
});
}
if let Some(connector_id_list) = &refund_details.connector_id_list {
connector_id_list.iter().for_each(|unique_connector_id| {
unique_connector_ids.insert(unique_connector_id);
});
}
if let Some(currencies) = &refund_details.currency {
currencies.iter().for_each(|currency| {
unique_currencies.insert(currency);
});
}
if let Some(refund_statuses) = &refund_details.refund_status {
refund_statuses.iter().for_each(|refund_status| {
unique_statuses.insert(refund_status);
});
}
let refunds = self.refunds.lock().await;
let filtered_refunds = refunds
.iter()
.filter(|refund| refund.merchant_id == *merchant_id)
.filter(|refund| {
refund_details
.payment_id
.clone()
.map_or(true, |id| id == refund.payment_id)
})
.filter(|refund| {
refund_details
.refund_id
.clone()
.map_or(true, |id| id == refund.id)
})
.filter(|refund| {
refund
.profile_id
.as_ref()
.is_some_and(|profile_id| profile_id == &refund_details.profile_id)
})
.filter(|refund| {
refund.created_at
>= refund_details.time_range.map_or(
common_utils::date_time::now() - time::Duration::days(60),
|range| range.start_time,
)
&& refund.created_at
<= refund_details
.time_range
.map_or(common_utils::date_time::now(), |range| {
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
>= MinorUnit::new(amount.start_amount.unwrap_or(i64::MIN))
&& refund.refund_amount
<= MinorUnit::new(amount.end_amount.unwrap_or(i64::MAX))
})
})
.filter(|refund| {
unique_connectors.is_empty() || unique_connectors.contains(&refund.connector)
})
.filter(|refund| {
unique_connector_ids.is_empty()
|| refund
.connector_id
.as_ref()
.is_some_and(|id| unique_connector_ids.contains(id))
})
.filter(|refund| {
unique_currencies.is_empty() || unique_currencies.contains(&refund.currency)
})
.filter(|refund| {
unique_statuses.is_empty() || unique_statuses.contains(&refund.refund_status)
})
.skip(usize::try_from(offset).unwrap_or_default())
.take(usize::try_from(limit).unwrap_or(MAX_LIMIT))
.cloned()
.collect::<Vec<_>>();
Ok(filtered_refunds)
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "refunds_v2"),
@ -1586,4 +1753,111 @@ impl RefundInterface for MockDb {
Ok(filtered_refunds_count)
}
#[cfg(all(feature = "v2", feature = "refunds_v2", feature = "olap"))]
async fn get_total_count_of_refunds(
&self,
merchant_id: &common_utils::id_type::MerchantId,
refund_details: refunds::RefundListConstraints,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
let mut unique_connectors = HashSet::new();
let mut unique_connector_ids = HashSet::new();
let mut unique_currencies = HashSet::new();
let mut unique_statuses = HashSet::new();
// Fill the hash sets with data from refund_details
if let Some(connectors) = &refund_details.connector {
connectors.iter().for_each(|connector| {
unique_connectors.insert(connector);
});
}
if let Some(connector_id_list) = &refund_details.connector_id_list {
connector_id_list.iter().for_each(|unique_connector_id| {
unique_connector_ids.insert(unique_connector_id);
});
}
if let Some(currencies) = &refund_details.currency {
currencies.iter().for_each(|currency| {
unique_currencies.insert(currency);
});
}
if let Some(refund_statuses) = &refund_details.refund_status {
refund_statuses.iter().for_each(|refund_status| {
unique_statuses.insert(refund_status);
});
}
let refunds = self.refunds.lock().await;
let filtered_refunds = refunds
.iter()
.filter(|refund| refund.merchant_id == *merchant_id)
.filter(|refund| {
refund_details
.payment_id
.clone()
.map_or(true, |id| id == refund.payment_id)
})
.filter(|refund| {
refund_details
.refund_id
.clone()
.map_or(true, |id| id == refund.id)
})
.filter(|refund| {
refund
.profile_id
.as_ref()
.is_some_and(|profile_id| profile_id == &refund_details.profile_id)
})
.filter(|refund| {
refund.created_at
>= refund_details.time_range.map_or(
common_utils::date_time::now() - time::Duration::days(60),
|range| range.start_time,
)
&& refund.created_at
<= refund_details
.time_range
.map_or(common_utils::date_time::now(), |range| {
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
>= MinorUnit::new(amount.start_amount.unwrap_or(i64::MIN))
&& refund.refund_amount
<= MinorUnit::new(amount.end_amount.unwrap_or(i64::MAX))
})
})
.filter(|refund| {
unique_connectors.is_empty() || unique_connectors.contains(&refund.connector)
})
.filter(|refund| {
unique_connector_ids.is_empty()
|| refund
.connector_id
.as_ref()
.is_some_and(|id| unique_connector_ids.contains(id))
})
.filter(|refund| {
unique_currencies.is_empty() || unique_currencies.contains(&refund.currency)
})
.filter(|refund| {
unique_statuses.is_empty() || unique_statuses.contains(&refund.refund_status)
})
.cloned()
.collect::<Vec<_>>();
let filtered_refunds_count = filtered_refunds.len().try_into().unwrap_or_default();
Ok(filtered_refunds_count)
}
}

View File

@ -1169,14 +1169,26 @@ impl Refunds {
}
}
#[cfg(all(feature = "v2", feature = "refunds_v2", feature = "oltp"))]
#[cfg(all(
feature = "v2",
feature = "refunds_v2",
any(feature = "olap", feature = "oltp")
))]
impl Refunds {
pub fn server(state: AppState) -> Scope {
let mut route = web::scope("/v2/refunds").app_data(web::Data::new(state));
#[cfg(feature = "olap")]
{
route =
route.service(web::resource("/list").route(web::get().to(refunds::refunds_list)));
}
#[cfg(feature = "oltp")]
{
route = route
.service(web::resource("").route(web::post().to(refunds::refunds_create)))
.service(web::resource("/{id}").route(web::get().to(refunds::refunds_retrieve)));
}
route
}

View File

@ -375,6 +375,37 @@ pub async fn refunds_list(
.await
}
#[cfg(all(feature = "v2", feature = "refunds_v2", feature = "olap"))]
#[instrument(skip_all, fields(flow = ?Flow::RefundsList))]
pub async fn refunds_list(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<api_models::refunds::RefundListRequest>,
) -> HttpResponse {
let flow = Flow::RefundsList;
Box::pin(api::server_wrap(
flow,
state,
&req,
payload.into_inner(),
|state, auth: auth::AuthenticationData, req, _| {
refund_list(state, auth.merchant_account, auth.profile, req)
},
auth::auth_type(
&auth::V2ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
&auth::JWTAuth {
permission: Permission::MerchantRefundRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "refunds_v2"),

View File

@ -3,8 +3,8 @@ pub use api_models::refunds::RefundRequest;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
pub use api_models::refunds::RefundsCreateRequest;
pub use api_models::refunds::{
RefundResponse, RefundStatus, RefundType, RefundUpdateRequest, RefundsRetrieveBody,
RefundsRetrieveRequest,
RefundListRequest, RefundListResponse, RefundResponse, RefundStatus, RefundType,
RefundUpdateRequest, RefundsRetrieveBody, RefundsRetrieveRequest,
};
pub use hyperswitch_domain_models::router_flow_types::refunds::{Execute, RSync};
pub use hyperswitch_interfaces::api::refunds::{Refund, RefundExecute, RefundSync};

View File

@ -5,11 +5,14 @@ use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Q
pub use diesel_models::refund::{
Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal,
};
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
use diesel_models::schema::refund::dsl;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
use diesel_models::schema_v2::refund::dsl;
use diesel_models::{
enums::{Currency, RefundStatus},
errors,
query::generics::db_metrics,
schema::refund::dsl,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::refunds;
@ -27,6 +30,15 @@ pub trait RefundDbExt: Sized {
offset: i64,
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn filter_by_constraints(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
refund_list_details: refunds::RefundListConstraints,
limit: i64,
offset: i64,
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
async fn filter_by_meta_constraints(
conn: &PgPooledConn,
@ -48,6 +60,13 @@ pub trait RefundDbExt: Sized {
profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
time_range: &common_utils::types::TimeRange,
) -> CustomResult<Vec<(RefundStatus, i64)>, errors::DatabaseError>;
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn get_refunds_count(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
refund_list_details: refunds::RefundListConstraints,
) -> CustomResult<i64, errors::DatabaseError>;
}
#[async_trait::async_trait]
@ -164,6 +183,82 @@ impl RefundDbExt for Refund {
.attach_printable_lazy(|| "Error filtering records by predicate")
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn filter_by_constraints(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
refund_list_details: refunds::RefundListConstraints,
limit: i64,
offset: i64,
) -> 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(payment_id) = &refund_list_details.payment_id {
filter = filter.filter(dsl::payment_id.eq(payment_id.to_owned()));
}
if let Some(refund_id) = &refund_list_details.refund_id {
filter = filter.filter(dsl::id.eq(refund_id.to_owned()));
}
if let Some(time_range) = &refund_list_details.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));
}
}
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 {
filter = filter.filter(dsl::connector.eq_any(connector));
}
if let Some(connector_id_list) = refund_list_details.connector_id_list {
filter = filter.filter(dsl::connector_id.eq_any(connector_id_list));
}
if let Some(filter_currency) = refund_list_details.currency {
filter = filter.filter(dsl::currency.eq_any(filter_currency));
}
if let Some(filter_refund_status) = refund_list_details.refund_status {
filter = filter.filter(dsl::refund_status.eq_any(filter_refund_status));
}
filter = filter.limit(limit).offset(offset);
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
filter.get_results_async(conn),
db_metrics::DatabaseOperation::Filter,
)
.await
.change_context(errors::DatabaseError::NotFound)
.attach_printable_lazy(|| "Error filtering records by predicate")
// todo!()
}
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
async fn filter_by_meta_constraints(
conn: &PgPooledConn,
@ -309,6 +404,74 @@ impl RefundDbExt for Refund {
.attach_printable_lazy(|| "Error filtering count of refunds")
}
#[cfg(all(feature = "v2", feature = "refunds_v2"))]
async fn get_refunds_count(
conn: &PgPooledConn,
merchant_id: &common_utils::id_type::MerchantId,
refund_list_details: refunds::RefundListConstraints,
) -> CustomResult<i64, errors::DatabaseError> {
let mut filter = <Self as HasTable>::table()
.count()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.into_boxed();
if let Some(payment_id) = &refund_list_details.payment_id {
filter = filter.filter(dsl::payment_id.eq(payment_id.to_owned()));
}
if let Some(refund_id) = &refund_list_details.refund_id {
filter = filter.filter(dsl::id.eq(refund_id.to_owned()));
}
if let Some(time_range) = refund_list_details.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));
}
}
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 {
filter = filter.filter(dsl::connector.eq_any(connector));
}
if let Some(connector_id_list) = refund_list_details.connector_id_list {
filter = filter.filter(dsl::connector_id.eq_any(connector_id_list));
}
if let Some(filter_currency) = refund_list_details.currency {
filter = filter.filter(dsl::currency.eq_any(filter_currency));
}
if let Some(filter_refund_status) = refund_list_details.refund_status {
filter = filter.filter(dsl::refund_status.eq_any(filter_refund_status));
}
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
filter
.get_result_async::<i64>(conn)
.await
.change_context(errors::DatabaseError::NotFound)
.attach_printable_lazy(|| "Error filtering count of refunds")
}
#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "refunds_v2")))]
async fn get_refund_status_with_count(
conn: &PgPooledConn,