mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(router): Create a merchant config for enable processor agnostic MIT (#4025)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -11,7 +11,7 @@ pub use euclid::{ | |||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use utoipa::ToSchema; | use utoipa::ToSchema; | ||||||
|  |  | ||||||
| use crate::enums::{self, RoutableConnectors, TransactionType}; | use crate::enums::{RoutableConnectors, TransactionType}; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] | #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] | ||||||
| #[serde(tag = "type", content = "data", rename_all = "snake_case")] | #[serde(tag = "type", content = "data", rename_all = "snake_case")] | ||||||
| @ -299,34 +299,12 @@ impl From<RoutableConnectorChoice> for ast::ConnectorChoice { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] | ||||||
| pub struct DetailedConnectorChoice { | pub struct DetailedConnectorChoice { | ||||||
|     pub connector: RoutableConnectors, |     pub enabled: bool, | ||||||
|     pub business_label: Option<String>, |  | ||||||
|     pub business_country: Option<enums::CountryAlpha2>, |  | ||||||
|     pub business_sub_label: Option<String>, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl DetailedConnectorChoice { | impl common_utils::events::ApiEventMetric for DetailedConnectorChoice {} | ||||||
|     pub fn get_connector_label(&self) -> Option<String> { |  | ||||||
|         self.business_country |  | ||||||
|             .as_ref() |  | ||||||
|             .zip(self.business_label.as_ref()) |  | ||||||
|             .map(|(business_country, business_label)| { |  | ||||||
|                 let mut base_label = format!( |  | ||||||
|                     "{}_{:?}_{}", |  | ||||||
|                     self.connector, business_country, business_label |  | ||||||
|                 ); |  | ||||||
|  |  | ||||||
|                 if let Some(ref sub_label) = self.business_sub_label { |  | ||||||
|                     base_label.push('_'); |  | ||||||
|                     base_label.push_str(sub_label); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 base_label |  | ||||||
|             }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)] | #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ use api_models::{ | |||||||
| }; | }; | ||||||
| #[cfg(not(feature = "business_profile_routing"))] | #[cfg(not(feature = "business_profile_routing"))] | ||||||
| use common_utils::ext_traits::{Encode, StringExt}; | use common_utils::ext_traits::{Encode, StringExt}; | ||||||
| #[cfg(not(feature = "business_profile_routing"))] |  | ||||||
| use diesel_models::configs; | use diesel_models::configs; | ||||||
| #[cfg(feature = "business_profile_routing")] | #[cfg(feature = "business_profile_routing")] | ||||||
| use diesel_models::routing_algorithm::RoutingAlgorithm; | use diesel_models::routing_algorithm::RoutingAlgorithm; | ||||||
| @ -807,6 +806,37 @@ pub async fn retrieve_linked_routing_config( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub async fn upsert_connector_agnostic_mandate_config( | ||||||
|  |     state: AppState, | ||||||
|  |     business_profile_id: &str, | ||||||
|  |     mandate_config: routing_types::DetailedConnectorChoice, | ||||||
|  | ) -> RouterResponse<routing_types::DetailedConnectorChoice> { | ||||||
|  |     let key = helpers::get_pg_agnostic_mandate_config_key(business_profile_id); | ||||||
|  |  | ||||||
|  |     let mandate_config_str = mandate_config.enabled.to_string(); | ||||||
|  |  | ||||||
|  |     let find_config = state | ||||||
|  |         .store | ||||||
|  |         .find_config_by_key_unwrap_or(&key, Some(mandate_config_str.clone())) | ||||||
|  |         .await | ||||||
|  |         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |         .attach_printable("error saving pg agnostic mandate config to db")?; | ||||||
|  |  | ||||||
|  |     if find_config.config != mandate_config_str { | ||||||
|  |         let config_update = configs::ConfigUpdate::Update { | ||||||
|  |             config: Some(mandate_config_str), | ||||||
|  |         }; | ||||||
|  |         state | ||||||
|  |             .store | ||||||
|  |             .update_config_by_key(&key, config_update) | ||||||
|  |             .await | ||||||
|  |             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||||
|  |             .attach_printable("error saving pg agnostic mandate config to db")?; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Ok(service_api::ApplicationResponse::Json(mandate_config)) | ||||||
|  | } | ||||||
|  |  | ||||||
| pub async fn retrieve_default_routing_config_for_profiles( | pub async fn retrieve_default_routing_config_for_profiles( | ||||||
|     state: AppState, |     state: AppState, | ||||||
|     merchant_account: domain::MerchantAccount, |     merchant_account: domain::MerchantAccount, | ||||||
|  | |||||||
| @ -273,9 +273,9 @@ pub async fn update_business_profile_active_algorithm_ref( | |||||||
|  |  | ||||||
| pub async fn get_merchant_connector_agnostic_mandate_config( | pub async fn get_merchant_connector_agnostic_mandate_config( | ||||||
|     db: &dyn StorageInterface, |     db: &dyn StorageInterface, | ||||||
|     merchant_id: &str, |     business_profile_id: &str, | ||||||
| ) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> { | ) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> { | ||||||
|     let key = get_pg_agnostic_mandate_config_key(merchant_id); |     let key = get_pg_agnostic_mandate_config_key(business_profile_id); | ||||||
|     let maybe_config = db.find_config_by_key(&key).await; |     let maybe_config = db.find_config_by_key(&key).await; | ||||||
|  |  | ||||||
|     match maybe_config { |     match maybe_config { | ||||||
| @ -312,29 +312,6 @@ pub async fn get_merchant_connector_agnostic_mandate_config( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub async fn update_merchant_connector_agnostic_mandate_config( |  | ||||||
|     db: &dyn StorageInterface, |  | ||||||
|     merchant_id: &str, |  | ||||||
|     mandate_config: Vec<routing_types::DetailedConnectorChoice>, |  | ||||||
| ) -> RouterResult<Vec<routing_types::DetailedConnectorChoice>> { |  | ||||||
|     let key = get_pg_agnostic_mandate_config_key(merchant_id); |  | ||||||
|     let mandate_config_str = mandate_config |  | ||||||
|         .encode_to_string_of_json() |  | ||||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) |  | ||||||
|         .attach_printable("unable to serialize pg agnostic mandate config during update")?; |  | ||||||
|  |  | ||||||
|     let config_update = configs::ConfigUpdate::Update { |  | ||||||
|         config: Some(mandate_config_str), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     db.update_config_by_key(&key, config_update) |  | ||||||
|         .await |  | ||||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) |  | ||||||
|         .attach_printable("error saving pg agnostic mandate config to db")?; |  | ||||||
|  |  | ||||||
|     Ok(mandate_config) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub async fn validate_connectors_in_routing_config( | pub async fn validate_connectors_in_routing_config( | ||||||
|     db: &dyn StorageInterface, |     db: &dyn StorageInterface, | ||||||
|     key_store: &domain::MerchantKeyStore, |     key_store: &domain::MerchantKeyStore, | ||||||
| @ -465,8 +442,8 @@ pub fn get_routing_dictionary_key(merchant_id: &str) -> String { | |||||||
|  |  | ||||||
| /// Provides the identifier for the specific merchant's agnostic_mandate_config | /// Provides the identifier for the specific merchant's agnostic_mandate_config | ||||||
| #[inline(always)] | #[inline(always)] | ||||||
| pub fn get_pg_agnostic_mandate_config_key(merchant_id: &str) -> String { | pub fn get_pg_agnostic_mandate_config_key(business_profile_id: &str) -> String { | ||||||
|     format!("pg_agnostic_mandate_{merchant_id}") |     format!("pg_agnostic_mandate_{business_profile_id}") | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Provides the identifier for the specific merchant's default_config | /// Provides the identifier for the specific merchant's default_config | ||||||
|  | |||||||
| @ -437,6 +437,10 @@ impl Routing { | |||||||
|                         ) |                         ) | ||||||
|                     })), |                     })), | ||||||
|             ) |             ) | ||||||
|  |             .service( | ||||||
|  |                 web::resource("/business_profile/{business_profile_id}/configs/pg_agnostic_mit") | ||||||
|  |                     .route(web::post().to(cloud_routing::upsert_connector_agnostic_mandate_config)), | ||||||
|  |             ) | ||||||
|             .service( |             .service( | ||||||
|                 web::resource("/default") |                 web::resource("/default") | ||||||
|                     .route(web::get().to(|state, req| { |                     .route(web::get().to(|state, req| { | ||||||
|  | |||||||
| @ -225,6 +225,7 @@ impl From<Flow> for ApiIdentifier { | |||||||
|             | Flow::ReconTokenRequest |             | Flow::ReconTokenRequest | ||||||
|             | Flow::ReconServiceRequest |             | Flow::ReconServiceRequest | ||||||
|             | Flow::ReconVerifyToken => Self::Recon, |             | Flow::ReconVerifyToken => Self::Recon, | ||||||
|  |             Flow::CreateConnectorAgnosticMandateConfig => Self::Routing, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -566,6 +566,44 @@ pub async fn routing_retrieve_linked_config( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "olap")] | ||||||
|  | #[instrument(skip_all)] | ||||||
|  | pub async fn upsert_connector_agnostic_mandate_config( | ||||||
|  |     state: web::Data<AppState>, | ||||||
|  |     req: HttpRequest, | ||||||
|  |     json_payload: web::Json<routing_types::DetailedConnectorChoice>, | ||||||
|  |     path: web::Path<String>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     use crate::services::authentication::AuthenticationData; | ||||||
|  |  | ||||||
|  |     let flow = Flow::CreateConnectorAgnosticMandateConfig; | ||||||
|  |     let business_profile_id = path.into_inner(); | ||||||
|  |  | ||||||
|  |     Box::pin(oss_api::server_wrap( | ||||||
|  |         flow, | ||||||
|  |         state, | ||||||
|  |         &req, | ||||||
|  |         json_payload.into_inner(), | ||||||
|  |         |state, _auth: AuthenticationData, mandate_config| { | ||||||
|  |             Box::pin(routing::upsert_connector_agnostic_mandate_config( | ||||||
|  |                 state, | ||||||
|  |                 &business_profile_id, | ||||||
|  |                 mandate_config, | ||||||
|  |             )) | ||||||
|  |         }, | ||||||
|  |         #[cfg(not(feature = "release"))] | ||||||
|  |         auth::auth_type( | ||||||
|  |             &auth::ApiKeyAuth, | ||||||
|  |             &auth::JWTAuth(Permission::RoutingWrite), | ||||||
|  |             req.headers(), | ||||||
|  |         ), | ||||||
|  |         #[cfg(feature = "release")] | ||||||
|  |         &auth::JWTAuth(Permission::RoutingWrite), | ||||||
|  |         api_locking::LockAction::NotApplicable, | ||||||
|  |     )) | ||||||
|  |     .await | ||||||
|  | } | ||||||
|  |  | ||||||
| #[cfg(feature = "olap")] | #[cfg(feature = "olap")] | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub async fn routing_retrieve_default_config_for_profiles( | pub async fn routing_retrieve_default_config_for_profiles( | ||||||
|  | |||||||
| @ -206,6 +206,8 @@ pub enum Flow { | |||||||
|     RoutingRetrieveConfig, |     RoutingRetrieveConfig, | ||||||
|     /// Routing retrieve active config |     /// Routing retrieve active config | ||||||
|     RoutingRetrieveActiveConfig, |     RoutingRetrieveActiveConfig, | ||||||
|  |     /// Update connector agnostic mandate config | ||||||
|  |     CreateConnectorAgnosticMandateConfig, | ||||||
|     /// Routing retrieve default config |     /// Routing retrieve default config | ||||||
|     RoutingRetrieveDefaultConfig, |     RoutingRetrieveDefaultConfig, | ||||||
|     /// Routing retrieve dictionary |     /// Routing retrieve dictionary | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Shankar Singh C
					Shankar Singh C