mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(vsaas): integrate onboarding flow for vertical saas (#7884)
This commit is contained in:
		| @ -4,6 +4,7 @@ use api_models::{ | ||||
|     admin::{self as admin_types}, | ||||
|     enums as api_enums, routing as routing_types, | ||||
| }; | ||||
| use common_enums::{MerchantAccountType, OrganizationType}; | ||||
| use common_utils::{ | ||||
|     date_time, | ||||
|     ext_traits::{AsyncExt, Encode, OptionExt, ValueExt}, | ||||
| @ -144,6 +145,7 @@ pub async fn update_organization( | ||||
|         organization_name: req.organization_name, | ||||
|         organization_details: req.organization_details, | ||||
|         metadata: req.metadata, | ||||
|         platform_merchant_id: req.platform_merchant_id, | ||||
|     }; | ||||
|     state | ||||
|         .accounts_store | ||||
| @ -342,6 +344,31 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { | ||||
|             .create_or_validate(db) | ||||
|             .await?; | ||||
|  | ||||
|         let merchant_account_type = match organization.get_organization_type() { | ||||
|             OrganizationType::Standard => MerchantAccountType::Standard, | ||||
|  | ||||
|             OrganizationType::Platform => { | ||||
|                 let accounts = state | ||||
|                     .store | ||||
|                     .list_merchant_accounts_by_organization_id( | ||||
|                         &state.into(), | ||||
|                         &organization.get_organization_id(), | ||||
|                     ) | ||||
|                     .await | ||||
|                     .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; | ||||
|  | ||||
|                 let platform_account_exists = accounts | ||||
|                     .iter() | ||||
|                     .any(|account| account.merchant_account_type == MerchantAccountType::Platform); | ||||
|  | ||||
|                 if platform_account_exists { | ||||
|                     MerchantAccountType::Connected | ||||
|                 } else { | ||||
|                     MerchantAccountType::Platform | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let key = key_store.key.clone().into_inner(); | ||||
|         let key_manager_state = state.into(); | ||||
|  | ||||
| @ -411,6 +438,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { | ||||
|                     version: common_types::consts::API_VERSION, | ||||
|                     is_platform_account: false, | ||||
|                     product_type: self.product_type, | ||||
|                     merchant_account_type, | ||||
|                 }, | ||||
|             ) | ||||
|         } | ||||
| @ -467,7 +495,10 @@ impl CreateOrValidateOrganization { | ||||
|         match self { | ||||
|             #[cfg(feature = "v1")] | ||||
|             Self::Create => { | ||||
|                 let new_organization = api_models::organization::OrganizationNew::new(None); | ||||
|                 let new_organization = api_models::organization::OrganizationNew::new( | ||||
|                     OrganizationType::Standard, | ||||
|                     None, | ||||
|                 ); | ||||
|                 let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|                 db.insert_organization(db_organization) | ||||
|                     .await | ||||
| @ -635,6 +666,18 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { | ||||
|             .create_or_validate(db) | ||||
|             .await?; | ||||
|  | ||||
|         let merchant_account_type = match organization.get_organization_type() { | ||||
|             OrganizationType::Standard => MerchantAccountType::Standard, | ||||
|             // Blocking v2 merchant account create for platform | ||||
|             OrganizationType::Platform => { | ||||
|                 return Err(errors::ApiErrorResponse::InvalidRequestData { | ||||
|                     message: "Merchant account creation is not allowed for a platform organization" | ||||
|                         .to_string(), | ||||
|                 } | ||||
|                 .into()) | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let key = key_store.key.into_inner(); | ||||
|         let id = identifier.to_owned(); | ||||
|         let key_manager_state = state.into(); | ||||
| @ -681,6 +724,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { | ||||
|                     is_platform_account: false, | ||||
|                     version: common_types::consts::API_VERSION, | ||||
|                     product_type: self.product_type, | ||||
|                     merchant_account_type, | ||||
|                 }), | ||||
|             ) | ||||
|         } | ||||
|  | ||||
| @ -1497,6 +1497,71 @@ pub async fn create_tenant_user( | ||||
|     Ok(ApplicationResponse::StatusOk) | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| pub async fn create_platform_account( | ||||
|     state: SessionState, | ||||
|     user_from_token: auth::UserFromToken, | ||||
|     req: user_api::PlatformAccountCreateRequest, | ||||
| ) -> UserResponse<user_api::PlatformAccountCreateResponse> { | ||||
|     let user_from_db = user_from_token.get_user_from_db(&state).await?; | ||||
|  | ||||
|     let new_merchant = domain::NewUserMerchant::try_from(req)?; | ||||
|     let new_organization = new_merchant.get_new_organization(); | ||||
|     let organization = new_organization.insert_org_in_db(state.clone()).await?; | ||||
|  | ||||
|     let merchant_account = new_merchant | ||||
|         .create_new_merchant_and_insert_in_db(state.to_owned()) | ||||
|         .await?; | ||||
|  | ||||
|     state | ||||
|         .accounts_store | ||||
|         .update_organization_by_org_id( | ||||
|             &organization.get_organization_id(), | ||||
|             diesel_models::organization::OrganizationUpdate::Update { | ||||
|                 organization_name: None, | ||||
|                 organization_details: None, | ||||
|                 metadata: None, | ||||
|                 platform_merchant_id: Some(merchant_account.get_id().to_owned()), | ||||
|             }, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(UserErrors::InternalServerError)?; | ||||
|  | ||||
|     let now = common_utils::date_time::now(); | ||||
|  | ||||
|     let user_role = domain::NewUserRole { | ||||
|         user_id: user_from_db.get_user_id().to_owned(), | ||||
|         role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(), | ||||
|         status: UserStatus::Active, | ||||
|         created_by: user_from_token.user_id.clone(), | ||||
|         last_modified_by: user_from_token.user_id.clone(), | ||||
|         created_at: now, | ||||
|         last_modified: now, | ||||
|         entity: domain::NoLevel, | ||||
|     }; | ||||
|  | ||||
|     user_role | ||||
|         .add_entity(domain::OrganizationLevel { | ||||
|             tenant_id: user_from_token | ||||
|                 .tenant_id | ||||
|                 .clone() | ||||
|                 .unwrap_or(state.tenant.tenant_id.clone()), | ||||
|             org_id: merchant_account.organization_id.clone(), | ||||
|         }) | ||||
|         .insert_in_v2(&state) | ||||
|         .await?; | ||||
|  | ||||
|     Ok(ApplicationResponse::Json( | ||||
|         user_api::PlatformAccountCreateResponse { | ||||
|             org_id: organization.get_organization_id(), | ||||
|             org_name: organization.get_organization_name(), | ||||
|             org_type: organization.organization_type.unwrap_or_default(), | ||||
|             merchant_id: merchant_account.get_id().to_owned(), | ||||
|             merchant_account_type: merchant_account.merchant_account_type, | ||||
|         }, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| pub async fn create_org_merchant_for_user( | ||||
|     state: SessionState, | ||||
| @ -1537,6 +1602,7 @@ pub async fn create_merchant_account( | ||||
|             merchant_id: domain_merchant_account.get_id().to_owned(), | ||||
|             merchant_name: domain_merchant_account.merchant_name, | ||||
|             product_type: domain_merchant_account.product_type, | ||||
|             merchant_account_type: domain_merchant_account.merchant_account_type, | ||||
|             version: domain_merchant_account.version, | ||||
|         }, | ||||
|     )) | ||||
| @ -2893,6 +2959,7 @@ pub async fn list_orgs_for_user( | ||||
|     .map(|org| user_api::ListOrgsForUserResponse { | ||||
|         org_id: org.get_organization_id(), | ||||
|         org_name: org.get_organization_name(), | ||||
|         org_type: org.organization_type.unwrap_or_default(), | ||||
|     }) | ||||
|     .collect::<Vec<_>>(); | ||||
|  | ||||
| @ -2975,6 +3042,7 @@ pub async fn list_merchants_for_user_in_org( | ||||
|                 merchant_name: merchant_account.merchant_name.clone(), | ||||
|                 merchant_id: merchant_account.get_id().to_owned(), | ||||
|                 product_type: merchant_account.product_type, | ||||
|                 merchant_account_type: merchant_account.merchant_account_type, | ||||
|                 version: merchant_account.version, | ||||
|             }) | ||||
|             .collect::<Vec<_>>(), | ||||
|  | ||||
| @ -119,12 +119,14 @@ impl OrganizationInterface for super::MockDb { | ||||
|                     organization_name, | ||||
|                     organization_details, | ||||
|                     metadata, | ||||
|                     platform_merchant_id, | ||||
|                 } => { | ||||
|                     organization_name | ||||
|                         .as_ref() | ||||
|                         .map(|org_name| org.set_organization_name(org_name.to_owned())); | ||||
|                     organization_details.clone_into(&mut org.organization_details); | ||||
|                     metadata.clone_into(&mut org.metadata); | ||||
|                     platform_merchant_id.clone_into(&mut org.platform_merchant_id); | ||||
|                     org | ||||
|                 } | ||||
|             }) | ||||
|  | ||||
| @ -2193,6 +2193,7 @@ impl User { | ||||
|             .service( | ||||
|                 web::resource("/tenant_signup").route(web::post().to(user::create_tenant_user)), | ||||
|             ) | ||||
|             .service(web::resource("/create_platform").route(web::post().to(user::create_platform))) | ||||
|             .service(web::resource("/create_org").route(web::post().to(user::user_org_create))) | ||||
|             .service( | ||||
|                 web::resource("/create_merchant") | ||||
|  | ||||
| @ -248,6 +248,7 @@ impl From<Flow> for ApiIdentifier { | ||||
|             | Flow::SwitchOrg | ||||
|             | Flow::SwitchMerchantV2 | ||||
|             | Flow::SwitchProfile | ||||
|             | Flow::CreatePlatformAccount | ||||
|             | Flow::UserOrgMerchantCreate | ||||
|             | Flow::UserMerchantAccountCreate | ||||
|             | Flow::GenerateSampleData | ||||
|  | ||||
| @ -259,6 +259,29 @@ pub async fn create_tenant_user( | ||||
|     .await | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| pub async fn create_platform( | ||||
|     state: web::Data<AppState>, | ||||
|     req: HttpRequest, | ||||
|     json_payload: web::Json<user_api::PlatformAccountCreateRequest>, | ||||
| ) -> HttpResponse { | ||||
|     let flow = Flow::CreatePlatformAccount; | ||||
|     Box::pin(api::server_wrap( | ||||
|         flow, | ||||
|         state, | ||||
|         &req, | ||||
|         json_payload.into_inner(), | ||||
|         |state, user: auth::UserFromToken, json_payload, _| { | ||||
|             user_core::create_platform_account(state, user, json_payload) | ||||
|         }, | ||||
|         &auth::JWTAuth { | ||||
|             permission: Permission::OrganizationAccountWrite, | ||||
|         }, | ||||
|         api_locking::LockAction::NotApplicable, | ||||
|     )) | ||||
|     .await | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "v1")] | ||||
| pub async fn user_org_create( | ||||
|     state: web::Data<AppState>, | ||||
|  | ||||
| @ -23,7 +23,7 @@ use hyperswitch_domain_models::api::ApplicationResponse; | ||||
| use masking::{ExposeInterface, PeekInterface, Secret}; | ||||
| use once_cell::sync::Lazy; | ||||
| use rand::distributions::{Alphanumeric, DistString}; | ||||
| use router_env::{env, logger}; | ||||
| use router_env::logger; | ||||
| use time::PrimitiveDateTime; | ||||
| use unicode_segmentation::UnicodeSegmentation; | ||||
| #[cfg(feature = "keymanager_create")] | ||||
| @ -267,9 +267,10 @@ impl NewUserOrganization { | ||||
| impl TryFrom<user_api::SignUpWithMerchantIdRequest> for NewUserOrganization { | ||||
|     type Error = error_stack::Report<UserErrors>; | ||||
|     fn try_from(value: user_api::SignUpWithMerchantIdRequest) -> UserResult<Self> { | ||||
|         let new_organization = api_org::OrganizationNew::new(Some( | ||||
|             UserCompanyName::new(value.company_name)?.get_secret(), | ||||
|         )); | ||||
|         let new_organization = api_org::OrganizationNew::new( | ||||
|             common_enums::OrganizationType::Standard, | ||||
|             Some(UserCompanyName::new(value.company_name)?.get_secret()), | ||||
|         ); | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|         Ok(Self(db_organization)) | ||||
|     } | ||||
| @ -277,7 +278,8 @@ impl TryFrom<user_api::SignUpWithMerchantIdRequest> for NewUserOrganization { | ||||
|  | ||||
| impl From<user_api::SignUpRequest> for NewUserOrganization { | ||||
|     fn from(_value: user_api::SignUpRequest) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew::new(None); | ||||
|         let new_organization = | ||||
|             api_org::OrganizationNew::new(common_enums::OrganizationType::Standard, None); | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|         Self(db_organization) | ||||
|     } | ||||
| @ -285,7 +287,8 @@ impl From<user_api::SignUpRequest> for NewUserOrganization { | ||||
|  | ||||
| impl From<user_api::ConnectAccountRequest> for NewUserOrganization { | ||||
|     fn from(_value: user_api::ConnectAccountRequest) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew::new(None); | ||||
|         let new_organization = | ||||
|             api_org::OrganizationNew::new(common_enums::OrganizationType::Standard, None); | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|         Self(db_organization) | ||||
|     } | ||||
| @ -297,6 +300,7 @@ impl From<(user_api::CreateInternalUserRequest, id_type::OrganizationId)> for Ne | ||||
|     ) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew { | ||||
|             org_id, | ||||
|             org_type: common_enums::OrganizationType::Standard, | ||||
|             org_name: None, | ||||
|         }; | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
| @ -308,15 +312,28 @@ impl From<UserMerchantCreateRequestWithToken> for NewUserOrganization { | ||||
|     fn from(value: UserMerchantCreateRequestWithToken) -> Self { | ||||
|         Self(diesel_org::OrganizationNew::new( | ||||
|             value.2.org_id, | ||||
|             common_enums::OrganizationType::Standard, | ||||
|             Some(value.1.company_name), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<user_api::PlatformAccountCreateRequest> for NewUserOrganization { | ||||
|     fn from(value: user_api::PlatformAccountCreateRequest) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew::new( | ||||
|             common_enums::OrganizationType::Platform, | ||||
|             Some(value.organization_name.expose()), | ||||
|         ); | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|         Self(db_organization) | ||||
|     } | ||||
| } | ||||
|  | ||||
| type InviteeUserRequestWithInvitedUserToken = (user_api::InviteUserRequest, UserFromToken); | ||||
| impl From<InviteeUserRequestWithInvitedUserToken> for NewUserOrganization { | ||||
|     fn from(_value: InviteeUserRequestWithInvitedUserToken) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew::new(None); | ||||
|         let new_organization = | ||||
|             api_org::OrganizationNew::new(common_enums::OrganizationType::Standard, None); | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
|         Self(db_organization) | ||||
|     } | ||||
| @ -331,6 +348,7 @@ impl From<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for Ne | ||||
|     ) -> Self { | ||||
|         let new_organization = api_org::OrganizationNew { | ||||
|             org_id: merchant_account_identifier.org_id, | ||||
|             org_type: common_enums::OrganizationType::Standard, | ||||
|             org_name: None, | ||||
|         }; | ||||
|         let db_organization = ForeignFrom::foreign_from(new_organization); | ||||
| @ -349,7 +367,11 @@ impl ForeignFrom<api_models::user::UserOrgMerchantCreateRequest> | ||||
|             metadata, | ||||
|             .. | ||||
|         } = item; | ||||
|         let mut org_new_db = Self::new(org_id, Some(organization_name.expose())); | ||||
|         let mut org_new_db = Self::new( | ||||
|             org_id, | ||||
|             common_enums::OrganizationType::Standard, | ||||
|             Some(organization_name.expose()), | ||||
|         ); | ||||
|         org_new_db.organization_details = organization_details; | ||||
|         org_new_db.metadata = metadata; | ||||
|         org_new_db | ||||
| @ -702,11 +724,8 @@ impl TryFrom<UserMerchantCreateRequestWithToken> for NewUserMerchant { | ||||
|     type Error = error_stack::Report<UserErrors>; | ||||
|  | ||||
|     fn try_from(value: UserMerchantCreateRequestWithToken) -> UserResult<Self> { | ||||
|         let merchant_id = if matches!(env::which(), env::Env::Production) { | ||||
|             id_type::MerchantId::try_from(MerchantId::new(value.1.company_name.clone())?)? | ||||
|         } else { | ||||
|             id_type::MerchantId::new_from_unix_timestamp() | ||||
|         }; | ||||
|         let merchant_id = | ||||
|             utils::user::generate_env_specific_merchant_id(value.1.company_name.clone())?; | ||||
|         let (user_from_storage, user_merchant_create, user_from_token) = value; | ||||
|         Ok(Self { | ||||
|             merchant_id, | ||||
| @ -723,6 +742,24 @@ impl TryFrom<UserMerchantCreateRequestWithToken> for NewUserMerchant { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<user_api::PlatformAccountCreateRequest> for NewUserMerchant { | ||||
|     type Error = error_stack::Report<UserErrors>; | ||||
|  | ||||
|     fn try_from(value: user_api::PlatformAccountCreateRequest) -> UserResult<Self> { | ||||
|         let merchant_id = utils::user::generate_env_specific_merchant_id( | ||||
|             value.organization_name.clone().expose(), | ||||
|         )?; | ||||
|  | ||||
|         let new_organization = NewUserOrganization::from(value); | ||||
|         Ok(Self { | ||||
|             company_name: None, | ||||
|             merchant_id, | ||||
|             new_organization, | ||||
|             product_type: Some(consts::user::DEFAULT_PRODUCT_TYPE), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct MerchantAccountIdentifier { | ||||
|     pub merchant_id: id_type::MerchantId, | ||||
|  | ||||
| @ -1825,7 +1825,7 @@ impl ForeignFrom<api_models::organization::OrganizationNew> | ||||
|     for diesel_models::organization::OrganizationNew | ||||
| { | ||||
|     fn foreign_from(item: api_models::organization::OrganizationNew) -> Self { | ||||
|         Self::new(item.org_id, item.org_name) | ||||
|         Self::new(item.org_id, item.org_type, item.org_name) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1833,13 +1833,17 @@ impl ForeignFrom<api_models::organization::OrganizationCreateRequest> | ||||
|     for diesel_models::organization::OrganizationNew | ||||
| { | ||||
|     fn foreign_from(item: api_models::organization::OrganizationCreateRequest) -> Self { | ||||
|         let org_new = api_models::organization::OrganizationNew::new(None); | ||||
|         // Create a new organization with a standard type by default | ||||
|         let org_new = api_models::organization::OrganizationNew::new( | ||||
|             common_enums::OrganizationType::Standard, | ||||
|             None, | ||||
|         ); | ||||
|         let api_models::organization::OrganizationCreateRequest { | ||||
|             organization_name, | ||||
|             organization_details, | ||||
|             metadata, | ||||
|         } = item; | ||||
|         let mut org_new_db = Self::new(org_new.org_id, Some(organization_name)); | ||||
|         let mut org_new_db = Self::new(org_new.org_id, org_new.org_type, Some(organization_name)); | ||||
|         org_new_db.organization_details = organization_details; | ||||
|         org_new_db.metadata = metadata; | ||||
|         org_new_db | ||||
|  | ||||
| @ -293,11 +293,7 @@ pub fn create_merchant_account_request_for_org( | ||||
|     org: organization::Organization, | ||||
|     product_type: common_enums::MerchantProductType, | ||||
| ) -> UserResult<api_models::admin::MerchantAccountCreate> { | ||||
|     let merchant_id = if matches!(env::which(), env::Env::Production) { | ||||
|         id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().expose())?)? | ||||
|     } else { | ||||
|         id_type::MerchantId::new_from_unix_timestamp() | ||||
|     }; | ||||
|     let merchant_id = generate_env_specific_merchant_id(req.merchant_name.clone().expose())?; | ||||
|  | ||||
|     let company_name = domain::UserCompanyName::new(req.merchant_name.expose())?; | ||||
|     Ok(api_models::admin::MerchantAccountCreate { | ||||
| @ -390,3 +386,12 @@ pub async fn set_lineage_context_in_cache( | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub fn generate_env_specific_merchant_id(value: String) -> UserResult<id_type::MerchantId> { | ||||
|     if matches!(env::which(), env::Env::Production) { | ||||
|         let raw_id = domain::MerchantId::new(value)?; | ||||
|         Ok(id_type::MerchantId::try_from(raw_id)?) | ||||
|     } else { | ||||
|         Ok(id_type::MerchantId::new_from_unix_timestamp()) | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Apoorv Dixit
					Apoorv Dixit