Skip to content

Commit

Permalink
feat(router): add total count for payments list (#1912)
Browse files Browse the repository at this point in the history
  • Loading branch information
apoorvdixit88 authored Aug 29, 2023
1 parent 784702d commit 7a5c841
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 16 deletions.
13 changes: 12 additions & 1 deletion crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1976,13 +1976,24 @@ pub struct PaymentListResponse {
// The list of payments response objects
pub data: Vec<PaymentsResponse>,
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct PaymentListResponseV2 {
/// The number of payments included in the list for given constraints
pub count: usize,
/// The total number of available payments for given constraints
pub total_count: i64,
/// The list of payments response objects
pub data: Vec<PaymentsResponse>,
}

#[derive(Clone, Debug, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaymentListFilterConstraints {
/// The identifier for payment
pub payment_id: Option<String>,
/// The starting point within a list of objects, limit on number of object will be some constant for join query
/// The limit on the number of objects. The max limit is 20
pub limit: Option<u32>,
/// The starting point within a list of objects
pub offset: Option<u32>,
/// 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)]
Expand Down
11 changes: 9 additions & 2 deletions crates/data_models/src/payments/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{errors, MerchantStorageScheme};
const QUERY_LIMIT: u32 = 20;
const MAX_LIMIT: u32 = 100;
#[async_trait::async_trait]
pub trait PaymentIntentInterface {
Expand Down Expand Up @@ -54,6 +53,14 @@ pub trait PaymentIntentInterface {
Vec<(PaymentIntent, super::payment_attempt::PaymentAttempt)>,
errors::StorageError,
>;

#[cfg(feature = "olap")]
async fn get_filtered_active_attempt_ids_for_total_count(
&self,
merchant_id: &str,
constraints: &PaymentIntentFetchConstraints,
storage_scheme: MerchantStorageScheme,
) -> error_stack::Result<Vec<String>, errors::StorageError>;
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -420,7 +427,7 @@ impl From<api_models::payments::PaymentListFilterConstraints> for PaymentIntentF
customer_id: None,
starting_after_id: None,
ending_before_id: None,
limit: Some(QUERY_LIMIT),
limit: value.limit,
}
}
}
Expand Down
1 change: 0 additions & 1 deletion crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ pub struct PaymentListFilters {
pub status: Vec<storage_enums::IntentStatus>,
pub payment_method: Vec<storage_enums::PaymentMethod>,
}

#[derive(
Clone, Debug, Default, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize,
)]
Expand Down
41 changes: 38 additions & 3 deletions crates/diesel_models/src/query/payment_attempt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::collections::HashSet;

use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl, Table};
use diesel::{
associations::HasTable, debug_query, pg::Pg, BoolExpressionMethods, ExpressionMethods,
QueryDsl, Table,
};
use error_stack::{IntoReport, ResultExt};
use router_env::{instrument, tracing};

Expand All @@ -14,6 +17,7 @@ use crate::{
PaymentListFilters,
},
payment_intent::PaymentIntent,
query::generics::db_metrics,
schema::payment_attempt::dsl,
PgPooledConn, StorageResult,
};
Expand Down Expand Up @@ -209,14 +213,14 @@ impl PaymentAttempt {
pi: &[PaymentIntent],
merchant_id: &str,
) -> StorageResult<PaymentListFilters> {
let active_attempts: Vec<String> = pi
let active_attempt_ids: Vec<String> = pi
.iter()
.map(|payment_intent| payment_intent.clone().active_attempt_id)
.collect();

let filter = <Self as HasTable>::table()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.filter(dsl::attempt_id.eq_any(active_attempts));
.filter(dsl::attempt_id.eq_any(active_attempt_ids));

let intent_status: Vec<IntentStatus> = pi
.iter()
Expand Down Expand Up @@ -273,4 +277,35 @@ impl PaymentAttempt {

Ok(filters)
}
pub async fn get_total_count_of_attempts(
conn: &PgPooledConn,
merchant_id: &str,
active_attempt_ids: &[String],
connector: Option<Vec<String>>,
payment_method: Option<Vec<enums::PaymentMethod>>,
) -> StorageResult<i64> {
let mut filter = <Self as HasTable>::table()
.count()
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
.filter(dsl::attempt_id.eq_any(active_attempt_ids.to_owned()))
.into_boxed();

if let Some(connector) = connector.clone() {
filter = filter.filter(dsl::connector.eq_any(connector));
}

if let Some(payment_method) = payment_method.clone() {
filter = filter.filter(dsl::payment_method.eq_any(payment_method));
}
router_env::logger::debug!(query = %debug_query::<Pg, _>(&filter).to_string());

db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
filter.get_result_async::<i64>(conn),
db_metrics::DatabaseOperation::Filter,
)
.await
.into_report()
.change_context(errors::DatabaseError::Others)
.attach_printable("Error filtering count of payments")
}
}
32 changes: 29 additions & 3 deletions crates/router/src/core/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ use crate::{
utils::{add_connector_http_status_code_metrics, Encode, OptionExt, ValueExt},
};

#[cfg(feature = "olap")]
const PAYMENTS_LIST_MAX_LIMIT: u32 = 20;
#[instrument(skip_all, fields(payment_id, merchant_id))]
pub async fn payments_operation_core<F, Req, Op, FData>(
state: &AppState,
Expand Down Expand Up @@ -1318,11 +1320,14 @@ pub async fn apply_filters_on_payments(
db: &dyn StorageInterface,
merchant: domain::MerchantAccount,
constraints: api::PaymentListFilterConstraints,
) -> RouterResponse<api::PaymentListResponse> {
) -> RouterResponse<api::PaymentListResponseV2> {
use storage_impl::DataModelExt;

use crate::types::transformers::ForeignFrom;

let limit = &constraints.limit.unwrap_or(PAYMENTS_LIST_MAX_LIMIT);

helpers::validate_payment_list_request_for_joins(*limit, PAYMENTS_LIST_MAX_LIMIT)?;
let list: Vec<(storage::PaymentIntent, storage::PaymentAttempt)> = db
.get_filtered_payment_intents_attempt(
&merchant.merchant_id,
Expand All @@ -1338,9 +1343,30 @@ pub async fn apply_filters_on_payments(
let data: Vec<api::PaymentsResponse> =
list.into_iter().map(ForeignFrom::foreign_from).collect();

let active_attempt_ids = db
.get_filtered_active_attempt_ids_for_total_count(
&merchant.merchant_id,
&constraints.clone().into(),
merchant.storage_scheme,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

let total_count = db
.get_total_count_of_filtered_payment_attempts(
&merchant.merchant_id,
&active_attempt_ids,
constraints.connector,
constraints.payment_methods,
merchant.storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)?;

Ok(services::ApplicationResponse::Json(
api::PaymentListResponse {
size: data.len(),
api::PaymentListResponseV2 {
count: data.len(),
total_count,
data,
},
))
Expand Down
12 changes: 12 additions & 0 deletions crates/router/src/core/payments/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,18 @@ pub(super) fn validate_payment_list_request(
})?;
Ok(())
}
#[cfg(feature = "olap")]
pub(super) fn validate_payment_list_request_for_joins(
limit: u32,
max_limit: u32,
) -> CustomResult<(), errors::ApiErrorResponse> {
utils::when(limit > max_limit || limit < 1, || {
Err(errors::ApiErrorResponse::InvalidRequestData {
message: format!("limit should be in between 1 and {}", max_limit),
})
})?;
Ok(())
}

pub fn get_handle_response_url(
payment_id: String,
Expand Down
83 changes: 83 additions & 0 deletions crates/router/src/db/payment_attempt.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use api_models::enums::{Connector, PaymentMethod};

use super::MockDb;
use crate::{
core::errors::{self, CustomResult},
Expand Down Expand Up @@ -76,10 +78,20 @@ pub trait PaymentAttemptInterface {
merchant_id: &str,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<diesel_models::payment_attempt::PaymentListFilters, errors::StorageError>;

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<PaymentMethod>>,
storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError>;
}

#[cfg(not(feature = "kv_store"))]
mod storage {
use api_models::enums::{Connector, PaymentMethod};
use error_stack::IntoReport;
use storage_impl::DataModelExt;

Expand Down Expand Up @@ -211,6 +223,37 @@ mod storage {
.into_report()
}

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<PaymentMethod>>,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
let connector_strings = if let Some(connector_vec) = &connector {
Some(
connector_vec
.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>(),
)
} else {
None
};
PaymentAttempt::get_total_count_of_attempts(
&conn,
merchant_id,
active_attempt_ids,
connector_strings,
payment_methods,
)
.await
.map_err(Into::into)
.into_report()
}

async fn find_payment_attempt_by_preprocessing_id_merchant_id(
&self,
preprocessing_id: &str,
Expand Down Expand Up @@ -281,6 +324,17 @@ impl PaymentAttemptInterface for MockDb {
Err(errors::StorageError::MockDbError)?
}

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<PaymentMethod>>,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
Err(errors::StorageError::MockDbError)?
}

async fn find_payment_attempt_by_attempt_id_merchant_id(
&self,
_attempt_id: &str,
Expand Down Expand Up @@ -920,6 +974,35 @@ mod storage {
.map_err(Into::into)
.into_report()
}

async fn get_total_count_of_filtered_payment_attempts(
&self,
merchant_id: &str,
active_attempt_ids: &[String],
connector: Option<Vec<api_models::enums::Connector>>,
payment_methods: Option<Vec<api_models::enums::PaymentMethod>>,
_storage_scheme: enums::MerchantStorageScheme,
) -> CustomResult<i64, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;

let connector_strings = connector.as_ref().map(|connector_vec| {
connector_vec
.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
});

PaymentAttempt::get_total_count_of_attempts(
&conn,
merchant_id,
active_attempt_ids,
connector_strings,
payment_methods,
)
.await
.map_err(Into::into)
.into_report()
}
}

#[inline]
Expand Down
11 changes: 11 additions & 0 deletions crates/router/src/db/payment_intent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ impl PaymentIntentInterface for MockDb {
Err(errors::DataStorageError::MockDbError)?
}

#[cfg(feature = "olap")]
async fn get_filtered_active_attempt_ids_for_total_count(
&self,
_merchant_id: &str,
_constraints: &PaymentIntentFetchConstraints,
_storage_scheme: enums::MerchantStorageScheme,
) -> error_stack::Result<Vec<String>, errors::DataStorageError> {
// [#172]: Implement function for `MockDb`
Err(errors::DataStorageError::MockDbError)?
}

#[allow(clippy::panic)]
async fn insert_payment_intent(
&self,
Expand Down
13 changes: 7 additions & 6 deletions crates/router/src/types/api/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ pub use api_models::payments::{
CryptoData, CustomerAcceptance, MandateData, MandateTransactionType, MandateType,
MandateValidationFields, NextActionType, OnlineMandate, PayLaterData, PaymentIdType,
PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListResponse,
PaymentMethodData, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody,
PaymentRetrieveBodyWithCredentials, PaymentsCancelRequest, PaymentsCaptureRequest,
PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRequest, PaymentsResponse,
PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse,
PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken,
TimeRange, UrlDetails, VerifyRequest, VerifyResponse, WalletData,
PaymentListResponseV2, PaymentMethodData, PaymentMethodDataResponse, PaymentOp,
PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsCancelRequest,
PaymentsCaptureRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRequest,
PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest,
PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails,
RedirectionResponse, SessionToken, TimeRange, UrlDetails, VerifyRequest, VerifyResponse,
WalletData,
};
use error_stack::{IntoReport, ResultExt};
use masking::PeekInterface;
Expand Down
Loading

0 comments on commit 7a5c841

Please sign in to comment.