feat(router): add profile id and extra filters in lists (#2379)

This commit is contained in:
Apoorv Dixit
2023-10-04 19:39:45 +05:30
committed by GitHub
parent 409913fd75
commit ab2cde7993
15 changed files with 228 additions and 110 deletions

View File

@ -104,6 +104,8 @@ pub struct DisputeEvidenceBlock {
pub struct DisputeListConstraints {
/// limit on the number of objects to return
pub limit: Option<i64>,
/// The identifier for business profile
pub profile_id: Option<String>,
/// status of the dispute
pub dispute_status: Option<DisputeStatus>,
/// stage of the dispute

View File

@ -1999,10 +1999,13 @@ pub struct PaymentListResponseV2 {
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaymentListFilterConstraints {
/// The identifier for payment
pub payment_id: Option<String>,
/// The identifier for business profile
pub profile_id: Option<String>,
/// The identifier for customer
pub customer_id: Option<String>,
/// The limit on the number of objects. The default limit is 10 and max limit is 20
#[serde(default = "default_limit")]
pub limit: u32,
@ -2015,10 +2018,14 @@ pub struct PaymentListFilterConstraints {
pub connector: Option<Vec<api_enums::Connector>>,
/// The list of currencies to filter payments list
pub currency: Option<Vec<enums::Currency>>,
/// The list of payment statuses to filter payments list
/// The list of payment status to filter payments list
pub status: Option<Vec<enums::IntentStatus>>,
/// The list of payment methods to filter payments list
pub payment_methods: Option<Vec<enums::PaymentMethod>>,
pub payment_method: Option<Vec<enums::PaymentMethod>>,
/// The list of payment method types to filter payments list
pub payment_method_type: Option<Vec<enums::PaymentMethodType>>,
/// The list of authentication types to filter payments list
pub authentication_type: Option<Vec<enums::AuthenticationType>>,
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct PaymentListFilters {
@ -2030,6 +2037,10 @@ pub struct PaymentListFilters {
pub status: Vec<enums::IntentStatus>,
/// The list of available payment method filters
pub payment_method: Vec<enums::PaymentMethod>,
/// The list of available payment method types
pub payment_method_type: Vec<enums::PaymentMethodType>,
/// The list of available authentication types
pub authentication_type: Vec<enums::AuthenticationType>,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)]

View File

@ -132,6 +132,8 @@ pub struct RefundListRequest {
pub payment_id: Option<String>,
/// The identifier for the refund
pub refund_id: Option<String>,
/// The identifier for business profile
pub profile_id: Option<String>,
/// Limit on the number of objects to return
pub limit: Option<i64>,
/// The starting point within a list of objects

View File

@ -79,12 +79,15 @@ pub trait PaymentAttemptInterface {
storage_scheme: MerchantStorageScheme,
) -> error_stack::Result<PaymentListFilters, errors::StorageError>;
#[allow(clippy::too_many_arguments)]
async fn get_total_count_of_filtered_payment_attempts(
&self,
merchant_id: &str,
active_attempt_ids: &[String],
connector: Option<Vec<Connector>>,
payment_methods: Option<Vec<storage_enums::PaymentMethod>>,
payment_method: Option<Vec<storage_enums::PaymentMethod>>,
payment_method_type: Option<Vec<storage_enums::PaymentMethodType>>,
authentication_type: Option<Vec<storage_enums::AuthenticationType>>,
storage_scheme: MerchantStorageScheme,
) -> error_stack::Result<i64, errors::StorageError>;
}
@ -147,6 +150,8 @@ pub struct PaymentListFilters {
pub currency: Vec<storage_enums::Currency>,
pub status: Vec<storage_enums::IntentStatus>,
pub payment_method: Vec<storage_enums::PaymentMethod>,
pub payment_method_type: Vec<storage_enums::PaymentMethodType>,
pub authentication_type: Vec<storage_enums::AuthenticationType>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]

View File

@ -391,57 +391,66 @@ impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
}
pub enum PaymentIntentFetchConstraints {
Single {
payment_intent_id: String,
},
List {
offset: u32,
starting_at: Option<PrimitiveDateTime>,
ending_at: Option<PrimitiveDateTime>,
connector: Option<Vec<api_models::enums::Connector>>,
currency: Option<Vec<storage_enums::Currency>>,
status: Option<Vec<storage_enums::IntentStatus>>,
payment_methods: Option<Vec<storage_enums::PaymentMethod>>,
customer_id: Option<String>,
starting_after_id: Option<String>,
ending_before_id: Option<String>,
limit: Option<u32>,
},
Single { payment_intent_id: String },
List(Box<PaymentIntentListParams>),
}
pub struct PaymentIntentListParams {
pub offset: u32,
pub starting_at: Option<PrimitiveDateTime>,
pub ending_at: Option<PrimitiveDateTime>,
pub connector: Option<Vec<api_models::enums::Connector>>,
pub currency: Option<Vec<storage_enums::Currency>>,
pub status: Option<Vec<storage_enums::IntentStatus>>,
pub payment_method: Option<Vec<storage_enums::PaymentMethod>>,
pub payment_method_type: Option<Vec<storage_enums::PaymentMethodType>>,
pub authentication_type: Option<Vec<storage_enums::AuthenticationType>>,
pub profile_id: Option<String>,
pub customer_id: Option<String>,
pub starting_after_id: Option<String>,
pub ending_before_id: Option<String>,
pub limit: Option<u32>,
}
impl From<api_models::payments::PaymentListConstraints> for PaymentIntentFetchConstraints {
fn from(value: api_models::payments::PaymentListConstraints) -> Self {
Self::List {
Self::List(Box::new(PaymentIntentListParams {
offset: 0,
starting_at: value.created_gte.or(value.created_gt).or(value.created),
ending_at: value.created_lte.or(value.created_lt).or(value.created),
connector: None,
currency: None,
status: None,
payment_methods: None,
payment_method: None,
payment_method_type: None,
authentication_type: None,
profile_id: None,
customer_id: value.customer_id,
starting_after_id: value.starting_after,
ending_before_id: value.ending_before,
limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V1)),
}
}))
}
}
impl From<api_models::payments::TimeRange> for PaymentIntentFetchConstraints {
fn from(value: api_models::payments::TimeRange) -> Self {
Self::List {
Self::List(Box::new(PaymentIntentListParams {
offset: 0,
starting_at: Some(value.start_time),
ending_at: value.end_time,
connector: None,
currency: None,
status: None,
payment_methods: None,
payment_method: None,
payment_method_type: None,
authentication_type: None,
profile_id: None,
customer_id: None,
starting_after_id: None,
ending_before_id: None,
limit: None,
}
}))
}
}
@ -450,19 +459,22 @@ impl From<api_models::payments::PaymentListFilterConstraints> for PaymentIntentF
if let Some(payment_intent_id) = value.payment_id {
Self::Single { payment_intent_id }
} else {
Self::List {
Self::List(Box::new(PaymentIntentListParams {
offset: value.offset.unwrap_or_default(),
starting_at: value.time_range.map(|t| t.start_time),
ending_at: value.time_range.and_then(|t| t.end_time),
connector: value.connector,
currency: value.currency,
status: value.status,
payment_methods: value.payment_methods,
customer_id: None,
payment_method: value.payment_method,
payment_method_type: value.payment_method_type,
authentication_type: value.authentication_type,
profile_id: value.profile_id,
customer_id: value.customer_id,
starting_after_id: None,
ending_before_id: None,
limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V2)),
}
}))
}
}
}

View File

@ -216,6 +216,8 @@ impl PaymentAttempt {
Vec<enums::Currency>,
Vec<IntentStatus>,
Vec<enums::PaymentMethod>,
Vec<enums::PaymentMethodType>,
Vec<enums::AuthenticationType>,
)> {
let active_attempts: Vec<String> = pi
.iter()
@ -272,11 +274,39 @@ impl PaymentAttempt {
.flatten()
.collect::<Vec<enums::PaymentMethod>>();
let filter_payment_method_type = filter
.clone()
.select(dsl::payment_method_type)
.distinct()
.get_results_async::<Option<enums::PaymentMethodType>>(conn)
.await
.into_report()
.change_context(DatabaseError::Others)
.attach_printable("Error filtering records by payment method type")?
.into_iter()
.flatten()
.collect::<Vec<enums::PaymentMethodType>>();
let filter_authentication_type = filter
.clone()
.select(dsl::authentication_type)
.distinct()
.get_results_async::<Option<enums::AuthenticationType>>(conn)
.await
.into_report()
.change_context(DatabaseError::Others)
.attach_printable("Error filtering records by authentication type")?
.into_iter()
.flatten()
.collect::<Vec<enums::AuthenticationType>>();
Ok((
filter_connector,
filter_currency,
intent_status,
filter_payment_method,
filter_payment_method_type,
filter_authentication_type,
))
}
pub async fn get_total_count_of_attempts(
@ -285,6 +315,8 @@ impl PaymentAttempt {
active_attempt_ids: &[String],
connector: Option<Vec<String>>,
payment_method: Option<Vec<enums::PaymentMethod>>,
payment_method_type: Option<Vec<enums::PaymentMethodType>>,
authentication_type: Option<Vec<enums::AuthenticationType>>,
) -> StorageResult<i64> {
let mut filter = <Self as HasTable>::table()
.count()
@ -299,6 +331,12 @@ impl PaymentAttempt {
if let Some(payment_method) = payment_method.clone() {
filter = filter.filter(dsl::payment_method.eq_any(payment_method));
}
if let Some(payment_method_type) = payment_method_type.clone() {
filter = filter.filter(dsl::payment_method_type.eq_any(payment_method_type));
}
if let Some(authentication_type) = authentication_type.clone() {
filter = filter.filter(dsl::authentication_type.eq_any(authentication_type));
}
router_env::logger::debug!(query = %debug_query::<Pg, _>(&filter).to_string());
db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(

View File

@ -1651,7 +1651,9 @@ pub async fn apply_filters_on_payments(
&merchant.merchant_id,
&active_attempt_ids,
constraints.connector,
constraints.payment_methods,
constraints.payment_method,
constraints.payment_method_type,
constraints.authentication_type,
merchant.storage_scheme,
)
.await
@ -1698,6 +1700,8 @@ pub async fn get_filters_for_payments(
currency: filters.currency,
status: filters.status,
payment_method: filters.payment_method,
payment_method_type: filters.payment_method_type,
authentication_type: filters.authentication_type,
},
))
}

View File

@ -562,6 +562,7 @@ mod tests {
received_time_gt: None,
received_time_lte: None,
received_time_gte: None,
profile_id: None,
},
)
.await

View File

@ -902,6 +902,7 @@ impl RefundInterface for MockDb {
.clone()
.map_or(true, |id| id == refund.refund_id)
})
.filter(|refund| refund_details.profile_id == refund.profile_id)
.filter(|refund| {
refund.created_at
>= refund_details.time_range.map_or(
@ -1025,6 +1026,7 @@ impl RefundInterface for MockDb {
.clone()
.map_or(true, |id| id == refund.refund_id)
})
.filter(|refund| refund_details.profile_id == refund.profile_id)
.filter(|refund| {
refund.created_at
>= refund_details.time_range.map_or(

View File

@ -28,6 +28,9 @@ impl DisputeDbExt for Dispute {
.order(dsl::modified_at.desc())
.into_boxed();
if let Some(profile_id) = dispute_list_constraints.profile_id {
filter = filter.filter(dsl::profile_id.eq(profile_id));
}
if let Some(received_time) = dispute_list_constraints.received_time {
filter = filter.filter(dsl::created_at.eq(received_time));
}

View File

@ -67,6 +67,17 @@ impl RefundDbExt for Refund {
filter = filter.limit(limit).offset(offset);
}
};
match &refund_list_details.profile_id {
Some(profile_id) => {
filter = filter
.filter(dsl::profile_id.eq(profile_id.to_owned()))
.limit(limit)
.offset(offset);
}
None => {
filter = filter.limit(limit).offset(offset);
}
};
if let Some(time_range) = refund_list_details.time_range {
filter = filter.filter(dsl::created_at.ge(time_range.start_time));
@ -175,6 +186,9 @@ impl RefundDbExt for Refund {
if let Some(ref_id) = &refund_list_details.refund_id {
filter = filter.filter(dsl::refund_id.eq(ref_id.to_owned()));
}
if let Some(profile_id) = &refund_list_details.profile_id {
filter = filter.filter(dsl::profile_id.eq(profile_id.to_owned()));
}
if let Some(time_range) = refund_list_details.time_range {
filter = filter.filter(dsl::created_at.ge(time_range.start_time));

View File

@ -1,4 +1,4 @@
use api_models::enums::{Connector, PaymentMethod};
use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType};
use common_utils::errors::CustomResult;
use data_models::{
errors::StorageError,
@ -39,7 +39,9 @@ impl PaymentAttemptInterface for MockDb {
_merchant_id: &str,
_active_attempt_ids: &[String],
_connector: Option<Vec<Connector>>,
_payment_methods: Option<Vec<PaymentMethod>>,
_payment_method: Option<Vec<PaymentMethod>>,
_payment_method_type: Option<Vec<PaymentMethodType>>,
_authentication_type: Option<Vec<AuthenticationType>>,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<i64, StorageError> {
Err(StorageError::MockDbError)?

View File

@ -1,4 +1,4 @@
use api_models::enums::{Connector, PaymentMethod};
use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType};
use common_utils::errors::CustomResult;
use data_models::{
errors,
@ -176,11 +176,20 @@ impl<T: DatabaseStore> PaymentAttemptInterface for RouterStore<T> {
er.change_context(new_err)
})
.map(
|(connector, currency, status, payment_method)| PaymentListFilters {
|(
connector,
currency,
status,
payment_method,
payment_method_type,
authentication_type,
)| PaymentListFilters {
connector,
currency,
status,
payment_method,
payment_method_type,
authentication_type,
},
)
}
@ -248,7 +257,9 @@ impl<T: DatabaseStore> PaymentAttemptInterface for RouterStore<T> {
merchant_id: &str,
active_attempt_ids: &[String],
connector: Option<Vec<Connector>>,
payment_methods: Option<Vec<PaymentMethod>>,
payment_method: Option<Vec<PaymentMethod>>,
payment_method_type: Option<Vec<PaymentMethodType>>,
authentication_type: Option<Vec<AuthenticationType>>,
_storage_scheme: MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
let conn = self
@ -269,7 +280,9 @@ impl<T: DatabaseStore> PaymentAttemptInterface for RouterStore<T> {
merchant_id,
active_attempt_ids,
connector_strings,
payment_methods,
payment_method,
payment_method_type,
authentication_type,
)
.await
.map_err(|er| {
@ -826,7 +839,9 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
merchant_id: &str,
active_attempt_ids: &[String],
connector: Option<Vec<Connector>>,
payment_methods: Option<Vec<PaymentMethod>>,
payment_method: Option<Vec<PaymentMethod>>,
payment_method_type: Option<Vec<PaymentMethodType>>,
authentication_type: Option<Vec<AuthenticationType>>,
storage_scheme: MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
self.router_store
@ -834,7 +849,9 @@ impl<T: DatabaseStore> PaymentAttemptInterface for KVRouterStore<T> {
merchant_id,
active_attempt_ids,
connector,
payment_methods,
payment_method,
payment_method_type,
authentication_type,
storage_scheme,
)
.await

View File

@ -366,29 +366,20 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
PaymentIntentFetchConstraints::Single { payment_intent_id } => {
query = query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned()));
}
PaymentIntentFetchConstraints::List {
offset,
starting_at,
ending_at,
connector: _,
currency,
status,
payment_methods: _,
customer_id,
starting_after_id,
ending_before_id,
limit,
} => {
if let Some(limit) = limit {
query = query.limit((*limit).into());
};
if let Some(customer_id) = customer_id {
query = query.filter(pi_dsl::customer_id.eq(customer_id.clone()));
PaymentIntentFetchConstraints::List(params) => {
if let Some(limit) = params.limit {
query = query.limit(limit.into());
}
query = match (starting_at, starting_after_id) {
(Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(*starting_at)),
if let Some(customer_id) = &params.customer_id {
query = query.filter(pi_dsl::customer_id.eq(customer_id.clone()));
}
if let Some(profile_id) = &params.profile_id {
query = query.filter(pi_dsl::profile_id.eq(profile_id.clone()));
}
query = match (params.starting_at, &params.starting_after_id) {
(Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)),
(None, Some(starting_after_id)) => {
// TODO: Fetch partial columns for this query since we only need some columns
let starting_at = self
@ -404,8 +395,8 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
(None, None) => query,
};
query = match (ending_at, ending_before_id) {
(Some(ending_at), _) => query.filter(pi_dsl::created_at.le(*ending_at)),
query = match (params.ending_at, &params.ending_before_id) {
(Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)),
(None, Some(ending_before_id)) => {
// TODO: Fetch partial columns for this query since we only need some columns
let ending_at = self
@ -420,17 +411,26 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
}
(None, None) => query,
};
query = query.offset((*offset).into());
query = match currency {
query = query.offset(params.offset.into());
query = match &params.currency {
Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())),
None => query,
};
query = match status {
query = match &params.status {
Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())),
None => query,
};
if let Some(currency) = &params.currency {
query = query.filter(pi_dsl::currency.eq_any(currency.clone()));
}
if let Some(status) = &params.status {
query = query.filter(pi_dsl::status.eq_any(status.clone()));
}
}
}
@ -490,29 +490,21 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
PaymentIntentFetchConstraints::Single { payment_intent_id } => {
query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned()))
}
PaymentIntentFetchConstraints::List {
offset,
starting_at,
ending_at,
connector,
currency,
status,
payment_methods,
customer_id,
starting_after_id,
ending_before_id,
limit,
} => {
if let Some(limit) = limit {
query = query.limit((*limit).into());
PaymentIntentFetchConstraints::List(params) => {
if let Some(limit) = params.limit {
query = query.limit(limit.into());
}
if let Some(customer_id) = customer_id {
if let Some(customer_id) = &params.customer_id {
query = query.filter(pi_dsl::customer_id.eq(customer_id.clone()));
}
query = match (starting_at, starting_after_id) {
(Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(*starting_at)),
if let Some(profile_id) = &params.profile_id {
query = query.filter(pi_dsl::profile_id.eq(profile_id.clone()));
}
query = match (params.starting_at, &params.starting_after_id) {
(Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)),
(None, Some(starting_after_id)) => {
// TODO: Fetch partial columns for this query since we only need some columns
let starting_at = self
@ -528,8 +520,8 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
(None, None) => query,
};
query = match (ending_at, ending_before_id) {
(Some(ending_at), _) => query.filter(pi_dsl::created_at.le(*ending_at)),
query = match (params.ending_at, &params.ending_before_id) {
(Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)),
(None, Some(ending_before_id)) => {
// TODO: Fetch partial columns for this query since we only need some columns
let ending_at = self
@ -545,14 +537,14 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
(None, None) => query,
};
query = query.offset((*offset).into());
query = query.offset(params.offset.into());
query = match currency {
Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())),
None => query,
};
if let Some(currency) = &params.currency {
query = query.filter(pi_dsl::currency.eq_any(currency.clone()));
}
let connectors = connector
let connectors = params
.connector
.as_ref()
.map(|c| c.iter().map(|c| c.to_string()).collect::<Vec<String>>());
@ -561,18 +553,30 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
None => query,
};
query = match status {
query = match &params.status {
Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())),
None => query,
};
query = match payment_methods {
Some(payment_methods) => {
query.filter(pa_dsl::payment_method.eq_any(payment_methods.clone()))
query = match &params.payment_method {
Some(payment_method) => {
query.filter(pa_dsl::payment_method.eq_any(payment_method.clone()))
}
None => query,
};
query = match &params.payment_method_type {
Some(payment_method_type) => query
.filter(pa_dsl::payment_method_type.eq_any(payment_method_type.clone())),
None => query,
};
query = match &params.authentication_type {
Some(authentication_type) => query
.filter(pa_dsl::authentication_type.eq_any(authentication_type.clone())),
None => query,
};
query
}
};
@ -620,34 +624,30 @@ impl<T: DatabaseStore> PaymentIntentInterface for crate::RouterStore<T> {
PaymentIntentFetchConstraints::Single { payment_intent_id } => {
query.filter(pi_dsl::payment_id.eq(payment_intent_id.to_owned()))
}
PaymentIntentFetchConstraints::List {
starting_at,
ending_at,
currency,
status,
customer_id,
..
} => {
if let Some(customer_id) = customer_id {
PaymentIntentFetchConstraints::List(params) => {
if let Some(customer_id) = &params.customer_id {
query = query.filter(pi_dsl::customer_id.eq(customer_id.clone()));
}
if let Some(profile_id) = &params.profile_id {
query = query.filter(pi_dsl::profile_id.eq(profile_id.clone()));
}
query = match starting_at {
Some(starting_at) => query.filter(pi_dsl::created_at.ge(*starting_at)),
query = match params.starting_at {
Some(starting_at) => query.filter(pi_dsl::created_at.ge(starting_at)),
None => query,
};
query = match ending_at {
Some(ending_at) => query.filter(pi_dsl::created_at.le(*ending_at)),
query = match params.ending_at {
Some(ending_at) => query.filter(pi_dsl::created_at.le(ending_at)),
None => query,
};
query = match currency {
query = match &params.currency {
Some(currency) => query.filter(pi_dsl::currency.eq_any(currency.clone())),
None => query,
};
query = match status {
query = match &params.status {
Some(status) => query.filter(pi_dsl::status.eq_any(status.clone())),
None => query,
};

View File

@ -10184,6 +10184,11 @@
"description": "The identifier for the refund",
"nullable": true
},
"profile_id": {
"type": "string",
"description": "The identifier for business profile",
"nullable": true
},
"limit": {
"type": "integer",
"format": "int64",