mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
feat(router): add mandates list api (#1143)
This commit is contained in:
committed by
GitHub
parent
bd8868efd0
commit
75ba3ff09f
@ -1,5 +1,6 @@
|
||||
use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::PrimitiveDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{enums as api_enums, payments};
|
||||
@ -60,3 +61,33 @@ pub struct MandateCardDetails {
|
||||
/// A unique identifier alias to identify a particular card
|
||||
pub card_fingerprint: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, ToSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MandateListConstraints {
|
||||
/// limit on the number of objects to return
|
||||
pub limit: Option<i64>,
|
||||
/// status of the mandate
|
||||
pub mandate_status: Option<api_enums::MandateStatus>,
|
||||
/// connector linked to mandate
|
||||
pub connector: Option<String>,
|
||||
/// The time at which mandate is created
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
pub created_time: Option<PrimitiveDateTime>,
|
||||
/// Time less than the mandate created time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "created_time.lt")]
|
||||
pub created_time_lt: Option<PrimitiveDateTime>,
|
||||
/// Time greater than the mandate created time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "created_time.gt")]
|
||||
pub created_time_gt: Option<PrimitiveDateTime>,
|
||||
/// Time less than or equals to the mandate created time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "created_time.lte")]
|
||||
pub created_time_lte: Option<PrimitiveDateTime>,
|
||||
/// Time greater than or equals to the mandate created time
|
||||
#[schema(example = "2022-09-10T10:11:12Z")]
|
||||
#[serde(rename = "created_time.gte")]
|
||||
pub created_time_gte: Option<PrimitiveDateTime>,
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use common_utils::{ext_traits::Encode, pii};
|
||||
use error_stack::{report, ResultExt};
|
||||
use futures::future;
|
||||
use router_env::{instrument, logger, tracing};
|
||||
use storage_models::enums as storage_enums;
|
||||
|
||||
@ -259,6 +260,25 @@ where
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
pub async fn retrieve_mandates_list(
|
||||
state: &AppState,
|
||||
merchant_account: storage::MerchantAccount,
|
||||
constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> RouterResponse<Vec<api_models::mandates::MandateResponse>> {
|
||||
let mandates = state
|
||||
.store
|
||||
.find_mandates_by_merchant_id(&merchant_account.merchant_id, constraints)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Unable to retrieve mandates")?;
|
||||
let mandates_list = future::try_join_all(mandates.into_iter().map(|mandate| {
|
||||
mandates::MandateResponse::from_db_mandate(state, mandate, &merchant_account)
|
||||
}))
|
||||
.await?;
|
||||
Ok(services::ApplicationResponse::Json(mandates_list))
|
||||
}
|
||||
|
||||
impl ForeignTryFrom<Result<types::PaymentsResponseData, types::ErrorResponse>>
|
||||
for Option<pii::SecretSerdeValue>
|
||||
{
|
||||
|
||||
@ -4,7 +4,7 @@ use super::{MockDb, Store};
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
types::storage,
|
||||
types::storage::{self, MandateDbExt},
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@ -28,6 +28,12 @@ pub trait MandateInterface {
|
||||
mandate: storage::MandateUpdate,
|
||||
) -> CustomResult<storage::Mandate, errors::StorageError>;
|
||||
|
||||
async fn find_mandates_by_merchant_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
mandate_constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError>;
|
||||
|
||||
async fn insert_mandate(
|
||||
&self,
|
||||
mandate: storage::MandateNew,
|
||||
@ -73,6 +79,18 @@ impl MandateInterface for Store {
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn find_mandates_by_merchant_id(
|
||||
&self,
|
||||
merchant_id: &str,
|
||||
mandate_constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_read(self).await?;
|
||||
storage::Mandate::filter_by_constraints(&conn, merchant_id, mandate_constraints)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn insert_mandate(
|
||||
&self,
|
||||
mandate: storage::MandateNew,
|
||||
@ -116,6 +134,15 @@ impl MandateInterface for MockDb {
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn find_mandates_by_merchant_id(
|
||||
&self,
|
||||
_merchant_id: &str,
|
||||
_mandate_constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> CustomResult<Vec<storage::Mandate>, errors::StorageError> {
|
||||
// [#172]: Implement function for `MockDb`
|
||||
Err(errors::StorageError::MockDbError)?
|
||||
}
|
||||
|
||||
async fn insert_mandate(
|
||||
&self,
|
||||
_mandate: storage::MandateNew,
|
||||
|
||||
@ -365,6 +365,8 @@ impl Mandates {
|
||||
|
||||
#[cfg(feature = "olap")]
|
||||
{
|
||||
route =
|
||||
route.service(web::resource("/list").route(web::get().to(retrieve_mandates_list)));
|
||||
route = route.service(web::resource("/{id}").route(web::get().to(get_mandate)));
|
||||
}
|
||||
#[cfg(feature = "oltp")]
|
||||
|
||||
@ -87,3 +87,44 @@ pub async fn revoke_mandate(
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Mandates - List Mandates
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/mandates/list",
|
||||
params(
|
||||
("limit" = Option<i64>, Query, description = "The maximum number of Mandate Objects to include in the response"),
|
||||
("mandate_status" = Option<MandateStatus>, Query, description = "The status of mandate"),
|
||||
("connector" = Option<String>, Query, description = "The connector linked to mandate"),
|
||||
("created_time" = Option<PrimitiveDateTime>, Query, description = "The time at which mandate is created"),
|
||||
("created_time.lt" = Option<PrimitiveDateTime>, Query, description = "Time less than the mandate created time"),
|
||||
("created_time.gt" = Option<PrimitiveDateTime>, Query, description = "Time greater than the mandate created time"),
|
||||
("created_time.lte" = Option<PrimitiveDateTime>, Query, description = "Time less than or equals to the mandate created time"),
|
||||
("created_time.gte" = Option<PrimitiveDateTime>, Query, description = "Time greater than or equals to the mandate created time"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "The mandate list was retrieved successfully", body = Vec<MandateResponse>),
|
||||
(status = 401, description = "Unauthorized request")
|
||||
),
|
||||
tag = "Mandates",
|
||||
operation_id = "List Mandates",
|
||||
security(("api_key" = []))
|
||||
)]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::MandatesList))]
|
||||
pub async fn retrieve_mandates_list(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
payload: web::Query<api_models::mandates::MandateListConstraints>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::MandatesList;
|
||||
let payload = payload.into_inner();
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
mandate::retrieve_mandates_list,
|
||||
auth::auth_type(&auth::ApiKeyAuth, &auth::JWTAuth, req.headers()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@ -1,3 +1,71 @@
|
||||
use async_bb8_diesel::AsyncRunQueryDsl;
|
||||
use common_utils::errors::CustomResult;
|
||||
use diesel::{associations::HasTable, ExpressionMethods, QueryDsl};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
pub use storage_models::mandate::{
|
||||
Mandate, MandateNew, MandateUpdate, MandateUpdateInternal, SingleUseMandate,
|
||||
};
|
||||
use storage_models::{errors, schema::mandate::dsl};
|
||||
|
||||
use crate::{connection::PgPooledConn, logger, types::transformers::ForeignInto};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait MandateDbExt: Sized {
|
||||
async fn filter_by_constraints(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: &str,
|
||||
mandate_list_constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> CustomResult<Vec<Self>, errors::DatabaseError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MandateDbExt for Mandate {
|
||||
async fn filter_by_constraints(
|
||||
conn: &PgPooledConn,
|
||||
merchant_id: &str,
|
||||
mandate_list_constraints: api_models::mandates::MandateListConstraints,
|
||||
) -> CustomResult<Vec<Self>, errors::DatabaseError> {
|
||||
let mut filter = <Self as HasTable>::table()
|
||||
.filter(dsl::merchant_id.eq(merchant_id.to_owned()))
|
||||
.order(dsl::created_at.desc())
|
||||
.into_boxed();
|
||||
|
||||
if let Some(created_time) = mandate_list_constraints.created_time {
|
||||
filter = filter.filter(dsl::created_at.eq(created_time));
|
||||
}
|
||||
if let Some(created_time_lt) = mandate_list_constraints.created_time_lt {
|
||||
filter = filter.filter(dsl::created_at.lt(created_time_lt));
|
||||
}
|
||||
if let Some(created_time_gt) = mandate_list_constraints.created_time_gt {
|
||||
filter = filter.filter(dsl::created_at.gt(created_time_gt));
|
||||
}
|
||||
if let Some(created_time_lte) = mandate_list_constraints.created_time_lte {
|
||||
filter = filter.filter(dsl::created_at.le(created_time_lte));
|
||||
}
|
||||
if let Some(created_time_gte) = mandate_list_constraints.created_time_gte {
|
||||
filter = filter.filter(dsl::created_at.ge(created_time_gte));
|
||||
}
|
||||
if let Some(connector) = mandate_list_constraints.connector {
|
||||
filter = filter.filter(dsl::connector.eq(connector));
|
||||
}
|
||||
if let Some(mandate_status) = mandate_list_constraints.mandate_status {
|
||||
let storage_mandate_status: storage_models::enums::MandateStatus =
|
||||
mandate_status.foreign_into();
|
||||
filter = filter.filter(dsl::mandate_status.eq(storage_mandate_status));
|
||||
}
|
||||
if let Some(limit) = mandate_list_constraints.limit {
|
||||
filter = filter.limit(limit);
|
||||
}
|
||||
|
||||
logger::debug!(query = %diesel::debug_query::<diesel::pg::Pg, _>(&filter).to_string());
|
||||
|
||||
filter
|
||||
.get_results_async(conn)
|
||||
.await
|
||||
.into_report()
|
||||
// The query built here returns an empty Vec when no records are found, and if any error does occur,
|
||||
// it would be an internal database error, due to which we are raising a DatabaseError::Unknown error
|
||||
.change_context(errors::DatabaseError::Others)
|
||||
.attach_printable("Error filtering mandates by specified constraints")
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +76,12 @@ impl ForeignFrom<storage_enums::MandateStatus> for api_enums::MandateStatus {
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<api_enums::MandateStatus> for storage_enums::MandateStatus {
|
||||
fn foreign_from(status: api_enums::MandateStatus) -> Self {
|
||||
frunk::labelled_convert_from(status)
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<api_enums::PaymentMethod> for storage_enums::PaymentMethod {
|
||||
fn foreign_from(pm_type: api_enums::PaymentMethod) -> Self {
|
||||
frunk::labelled_convert_from(pm_type)
|
||||
|
||||
@ -96,6 +96,8 @@ pub enum Flow {
|
||||
MandatesRetrieve,
|
||||
/// Mandates revoke flow.
|
||||
MandatesRevoke,
|
||||
/// Mandates list flow.
|
||||
MandatesList,
|
||||
/// Payment methods create flow.
|
||||
PaymentMethodsCreate,
|
||||
/// Payment methods list flow.
|
||||
|
||||
Reference in New Issue
Block a user