mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(router): add mandates list api (#1143)
This commit is contained in:
		 Sai Harsha Vardhan
					Sai Harsha Vardhan
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							bd8868efd0
						
					
				
				
					commit
					75ba3ff09f
				
			| @ -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) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user