mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +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 utoipa::ToSchema; | ||||
|  | ||||
| use crate::enums::{self, RoutableConnectors, TransactionType}; | ||||
| use crate::enums::{RoutableConnectors, TransactionType}; | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] | ||||
| #[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 connector: RoutableConnectors, | ||||
|     pub business_label: Option<String>, | ||||
|     pub business_country: Option<enums::CountryAlpha2>, | ||||
|     pub business_sub_label: Option<String>, | ||||
|     pub enabled: bool, | ||||
| } | ||||
|  | ||||
| impl 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 | ||||
|             }) | ||||
|     } | ||||
| } | ||||
| impl common_utils::events::ApiEventMetric for DetailedConnectorChoice {} | ||||
|  | ||||
| #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, strum::Display, ToSchema)] | ||||
| #[serde(rename_all = "snake_case")] | ||||
|  | ||||
| @ -9,7 +9,6 @@ use api_models::{ | ||||
| }; | ||||
| #[cfg(not(feature = "business_profile_routing"))] | ||||
| use common_utils::ext_traits::{Encode, StringExt}; | ||||
| #[cfg(not(feature = "business_profile_routing"))] | ||||
| use diesel_models::configs; | ||||
| #[cfg(feature = "business_profile_routing")] | ||||
| 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( | ||||
|     state: AppState, | ||||
|     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( | ||||
|     db: &dyn StorageInterface, | ||||
|     merchant_id: &str, | ||||
|     business_profile_id: &str, | ||||
| ) -> 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; | ||||
|  | ||||
|     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( | ||||
|     db: &dyn StorageInterface, | ||||
|     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 | ||||
| #[inline(always)] | ||||
| pub fn get_pg_agnostic_mandate_config_key(merchant_id: &str) -> String { | ||||
|     format!("pg_agnostic_mandate_{merchant_id}") | ||||
| pub fn get_pg_agnostic_mandate_config_key(business_profile_id: &str) -> String { | ||||
|     format!("pg_agnostic_mandate_{business_profile_id}") | ||||
| } | ||||
|  | ||||
| /// 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( | ||||
|                 web::resource("/default") | ||||
|                     .route(web::get().to(|state, req| { | ||||
|  | ||||
| @ -225,6 +225,7 @@ impl From<Flow> for ApiIdentifier { | ||||
|             | Flow::ReconTokenRequest | ||||
|             | Flow::ReconServiceRequest | ||||
|             | 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")] | ||||
| #[instrument(skip_all)] | ||||
| pub async fn routing_retrieve_default_config_for_profiles( | ||||
|  | ||||
| @ -206,6 +206,8 @@ pub enum Flow { | ||||
|     RoutingRetrieveConfig, | ||||
|     /// Routing retrieve active config | ||||
|     RoutingRetrieveActiveConfig, | ||||
|     /// Update connector agnostic mandate config | ||||
|     CreateConnectorAgnosticMandateConfig, | ||||
|     /// Routing retrieve default config | ||||
|     RoutingRetrieveDefaultConfig, | ||||
|     /// Routing retrieve dictionary | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Shankar Singh C
					Shankar Singh C