Bug: feat: Add user flow for non-email usecase



If a user (merchant) does not have email service enabled, then the current invite user flow does not work.

## Feature Requirement

For a merchant with no email service enabled (feature flag), have a add user flow. In this flow, the merchant is asked for a email id and password. A user will be created with this new email ID and password and he will be able to log in.
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 07909a35782..f5af31c8e7f 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -86,6 +86,7 @@ pub struct InviteUserRequest {
 #[derive(Debug, serde::Serialize)]
 pub struct InviteUserResponse {
     pub is_email_sent: bool,
+    pub password: Option<Secret<String>>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 532f8208ecf..b1a582cedec 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1,7 +1,5 @@
 use api_models::user as user_api;
-#[cfg(feature = "email")]
-use diesel_models::user_role::UserRoleNew;
-use diesel_models::{enums::UserStatus, user as storage_user};
+use diesel_models::{enums::UserStatus, user as storage_user, user_role::UserRoleNew};
 #[cfg(feature = "email")]
 use error_stack::IntoReport;
 use error_stack::ResultExt;
@@ -342,7 +340,6 @@ pub async fn reset_password(
     Ok(ApplicationResponse::StatusOk)
 }
 
-#[cfg(feature = "email")]
 pub async fn invite_user(
     state: AppState,
     request: user_api::InviteUserRequest,
@@ -395,6 +392,7 @@ pub async fn invite_user(
 
         Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
             is_email_sent: false,
+            password: None,
         }))
     } else if invitee_user
         .as_ref()
@@ -432,25 +430,37 @@ pub async fn invite_user(
                 }
             })?;
 
-        let email_contents = email_types::InviteUser {
-            recipient_email: invitee_email,
-            user_name: domain::UserName::new(new_user.get_name())?,
-            settings: state.conf.clone(),
-            subject: "You have been invited to join Hyperswitch Community!",
-        };
-
-        let send_email_result = state
-            .email_client
-            .compose_and_send_email(
-                Box::new(email_contents),
-                state.conf.proxy.https_url.as_ref(),
-            )
-            .await;
-
-        logger::info!(?send_email_result);
+        let is_email_sent;
+        #[cfg(feature = "email")]
+        {
+            let email_contents = email_types::InviteUser {
+                recipient_email: invitee_email,
+                user_name: domain::UserName::new(new_user.get_name())?,
+                settings: state.conf.clone(),
+                subject: "You have been invited to join Hyperswitch Community!",
+            };
+            let send_email_result = state
+                .email_client
+                .compose_and_send_email(
+                    Box::new(email_contents),
+                    state.conf.proxy.https_url.as_ref(),
+                )
+                .await;
+            logger::info!(?send_email_result);
+            is_email_sent = send_email_result.is_ok();
+        }
+        #[cfg(not(feature = "email"))]
+        {
+            is_email_sent = false;
+        }
 
         Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
-            is_email_sent: send_email_result.is_ok(),
+            is_email_sent,
+            password: if cfg!(not(feature = "email")) {
+                Some(new_user.get_password().get_secret())
+            } else {
+                None
+            },
         }))
     } else {
         Err(UserErrors::InternalServerError.into())
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 6625a206be2..015e3305de1 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -879,6 +879,7 @@ impl User {
             .service(web::resource("/user/update_role").route(web::post().to(update_user_role)))
             .service(web::resource("/role/list").route(web::get().to(list_roles)))
             .service(web::resource("/role/{role_id}").route(web::get().to(get_role)))
+            .service(web::resource("/user/invite").route(web::post().to(invite_user)))
             .service(
                 web::resource("/data")
                     .route(web::get().to(get_multiple_dashboard_metadata))
@@ -901,7 +902,6 @@ impl User {
                 )
                 .service(web::resource("/forgot_password").route(web::post().to(forgot_password)))
                 .service(web::resource("/reset_password").route(web::post().to(reset_password)))
-                .service(web::resource("/user/invite").route(web::post().to(invite_user)))
                 .service(
                     web::resource("/signup_with_merchant_id")
                         .route(web::post().to(user_signup_with_merchant_id)),
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 7f0f0db3b69..a77b82c550e 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -333,7 +333,6 @@ pub async fn reset_password(
     .await
 }
 
-#[cfg(feature = "email")]
 pub async fn invite_user(
     state: web::Data<AppState>,
     req: HttpRequest,
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index 8f204814ec4..d271ed5e29d 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -489,6 +489,10 @@ impl NewUser {
         self.new_merchant.clone()
     }
 
+    pub fn get_password(&self) -> UserPassword {
+        self.password.clone()
+    }
+
     pub async fn insert_user_in_db(
         &self,
         db: &dyn StorageInterface,
@@ -683,8 +687,7 @@ impl TryFrom<InviteeUserRequestWithInvitedUserToken> for NewUser {
         let user_id = uuid::Uuid::new_v4().to_string();
         let email = value.0.email.clone().try_into()?;
         let name = UserName::new(value.0.name.clone())?;
-        let password = password::generate_password_hash(uuid::Uuid::new_v4().to_string().into())?;
-        let password = UserPassword::new(password)?;
+        let password = UserPassword::new(uuid::Uuid::new_v4().to_string().into())?;
         let new_merchant = NewUserMerchant::try_from(value)?;
 
         Ok(Self {

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [X] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Enabled invite api to work without "email" feature flag
<!-- Describe your changes in detail -->


### Additional Changes

- [X] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
allows user to use user management without email feature flag. 
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
```
curl --location --request POST '<URL>/user/user/invite' \
--header 'Authorization: Bearer <JWT>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "email",
    "name": "name",
    "role_id": "valid_role_id"
}'
```
Expected Response:
with email flag
```
{ "is_email_sent": true,
  "password": null
}
```

without email flag
```
{ "is_email_sent": false,
  "password": "some_password"
}
```
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [X] I formatted the code `cargo +nightly fmt --all`
- [X] I addressed lints thrown by `cargo clippy`
- [X] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
