Issue:
Bug: feat: signin and verify email changes for invite flow





Pull request description:
## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This PR adds 2 new APIs
1. `/user/v2/signin`
2. `/user/v2/verify_email`

These PRs will have checks as mentioned in the below diagram.
```mermaid
flowchart TD
    A["Connect Account"] -- Email --> B{"User Exists"}
    B -- No --> C["Create new \n Org \n Merchant \n User \n User Role"]
    C --> D["Send Verify Email"]
    D --> E["Verify Email Token"]
    B -- Yes --> D
    E --> H{"Is there\n any perferred \nmerchant"}
	subgraph Checks
	    F -- ==0 --> I{{"Send list of \nmerchants which\n user has access to\n along with an\n intermediate token"}}
	    F -- >=1 --> G[Select the first role with active status]
		H -- Yes --> M["Send the token \nwith that merchant_id"]
	    H -- No --> F{"How many \n active merchants\n does user have \naccess to"}
	    I -- merchant_ids, \nintermediate_token --> O["Accept Invite"]
	    O --> P["Change status to active"]
	    P --> G
	    G --> M
	end
    K["Sign In"] -- Email, Password --> H
```

### 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`
4. `crates/router/src/configs`
5. `loadtest/config`
-->


## Motivation and Context
<!--
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).
-->
To make the invitation process for users better.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
```curl
curl --location 'http://localhost:8080/user/v2/signin' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "email",
    "password": "password"
}'
```

or

```curl
curl --location 'http://localhost:8080/user/v2/verify_email' \
--header 'Content-Type: application/json' \
--data-raw '{
    "token": "email token"
}'
```
- Response:
	- If user has a preferred merchant id or user has a active user role
		```
		{
			"flow_type": "dashboard_entry",
			"token": "JWT with merchant_id, user_id, user_role",
			"merchant_id": "merchant_id",
			"name": "user name",
			"email": "user email",
			"verification_days_left": null,
			"user_role": "user role"
		}
		```

	- If user has no active user role
		```
		{
			"flow_type": "merchant_select",
			"token":  "JWT with only user_id",
			"merchants":  [
				{
					"merchant_id":  "merchant_id 1",
					"company_name":  "company_name 1",
					"is_active":  false
				},
				{
					"merchant_id":  "merchant_id 2",
					"company_name":  "company_name 2",
					"is_active":  false
				}
			]
		}
		```

## 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


Testing:

```curl
curl --location 'http://localhost:8080/user/v2/signin' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "email",
    "password": "password"
}'
```

or

```curl
curl --location 'http://localhost:8080/user/v2/verify_email' \
--header 'Content-Type: application/json' \
--data-raw '{
    "token": "email token"
}'
```
- Response:
	- If user has a preferred merchant id or user has a active user role
		```
		{
			"flow_type": "dashboard_entry",
			"token": "JWT with merchant_id, user_id, user_role",
			"merchant_id": "merchant_id",
			"name": "user name",
			"email": "user email",
			"verification_days_left": null,
			"user_role": "user role"
		}
		```

	- If user has no active user role
		```
		{
			"flow_type": "merchant_select",
			"token":  "JWT with only user_id",
			"merchants":  [
				{
					"merchant_id":  "merchant_id 1",
					"company_name":  "company_name 1",
					"is_active":  false
				},
				{
					"merchant_id":  "merchant_id 2",
					"company_name":  "company_name 2",
					"is_active":  false
				}
			]
		}
		```

Patch:
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index 40d082d1cad..04aabc071ae 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -12,9 +12,9 @@ use crate::user::{
     },
     AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
     DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
-    InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignUpRequest,
-    SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UpdateUserAccountDetailsRequest,
-    UserMerchantCreate, VerifyEmailRequest,
+    InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignInResponse,
+    SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
+    UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest,
 };
 
 impl ApiEventMetric for DashboardEntryResponse {
@@ -56,6 +56,7 @@ common_utils::impl_misc_api_event_type!(
     InviteUserResponse,
     VerifyEmailRequest,
     SendVerifyEmailRequest,
+    SignInResponse,
     UpdateUserAccountDetailsRequest
 );
 
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 056d1b593dc..89f42f58c39 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -39,7 +39,21 @@ pub struct DashboardEntryResponse {
 
 pub type SignInRequest = SignUpRequest;
 
-pub type SignInResponse = DashboardEntryResponse;
+#[derive(Debug, serde::Serialize)]
+#[serde(tag = "flow_type", rename_all = "snake_case")]
+pub enum SignInResponse {
+    MerchantSelect(MerchantSelectResponse),
+    DashboardEntry(DashboardEntryResponse),
+}
+
+#[derive(Debug, serde::Serialize)]
+pub struct MerchantSelectResponse {
+    pub token: Secret<String>,
+    pub name: Secret<String>,
+    pub email: pii::Email,
+    pub verification_days_left: Option<i64>,
+    pub merchants: Vec<UserMerchantAccount>,
+}
 
 #[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
 pub struct ConnectAccountRequest {
@@ -138,7 +152,7 @@ pub struct VerifyEmailRequest {
     pub token: Secret<String>,
 }
 
-pub type VerifyEmailResponse = DashboardEntryResponse;
+pub type VerifyEmailResponse = SignInResponse;
 
 #[derive(serde::Deserialize, Debug, serde::Serialize)]
 pub struct SendVerifyEmailRequest {
@@ -149,6 +163,7 @@ pub struct SendVerifyEmailRequest {
 pub struct UserMerchantAccount {
     pub merchant_id: String,
     pub merchant_name: OptionalEncryptableName,
+    pub is_active: bool,
 }
 
 #[cfg(feature = "recon")]
diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs
index 389cb10d7b5..d3b1679378e 100644
--- a/crates/router/src/core/errors/user.rs
+++ b/crates/router/src/core/errors/user.rs
@@ -58,6 +58,8 @@ pub enum UserErrors {
     InvalidDeleteOperation,
     #[error("MaxInvitationsError")]
     MaxInvitationsError,
+    #[error("RoleNotFound")]
+    RoleNotFound,
 }
 
 impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
@@ -146,6 +148,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
             Self::MaxInvitationsError => {
                 AER::BadRequest(ApiError::new(sub_code, 31, self.get_error_message(), None))
             }
+            Self::RoleNotFound => {
+                AER::BadRequest(ApiError::new(sub_code, 32, self.get_error_message(), None))
+            }
         }
     }
 }
@@ -178,6 +183,7 @@ impl UserErrors {
             Self::ChangePasswordError => "Old and new password cannot be the same",
             Self::InvalidDeleteOperation => "Delete Operation Not Supported",
             Self::MaxInvitationsError => "Maximum invite count per request exceeded",
+            Self::RoleNotFound => "Role Not Found",
         }
     }
 }
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index c2ed78f8665..ae66728e140 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -97,10 +97,10 @@ pub async fn signup(
     ))
 }
 
-pub async fn signin(
+pub async fn signin_without_invite_checks(
     state: AppState,
     request: user_api::SignInRequest,
-) -> UserResponse<user_api::SignInResponse> {
+) -> UserResponse<user_api::DashboardEntryResponse> {
     let user_from_db: domain::UserFromStorage = state
         .store
         .find_user_by_email(request.email.clone().expose().expose().as_str())
@@ -124,6 +124,50 @@ pub async fn signin(
     ))
 }
 
+pub async fn signin(
+    state: AppState,
+    request: user_api::SignInRequest,
+) -> UserResponse<user_api::SignInResponse> {
+    let user_from_db: domain::UserFromStorage = state
+        .store
+        .find_user_by_email(request.email.clone().expose().expose().as_str())
+        .await
+        .map_err(|e| {
+            if e.current_context().is_db_not_found() {
+                e.change_context(UserErrors::InvalidCredentials)
+            } else {
+                e.change_context(UserErrors::InternalServerError)
+            }
+        })?
+        .into();
+
+    user_from_db.compare_password(request.password)?;
+
+    let signin_strategy =
+        if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() {
+            let preferred_role = user_from_db
+                .get_role_from_db_by_merchant_id(&state, preferred_merchant_id.as_str())
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("User role with preferred_merchant_id not found")?;
+            domain::SignInWithRoleStrategyType::SingleRole(domain::SignInWithSingleRoleStrategy {
+                user: user_from_db,
+                user_role: preferred_role,
+            })
+        } else {
+            let user_roles = user_from_db.get_roles_from_db(&state).await?;
+            domain::SignInWithRoleStrategyType::decide_signin_strategy_by_user_roles(
+                user_from_db,
+                user_roles,
+            )
+            .await?
+        };
+
+    Ok(ApplicationResponse::Json(
+        signin_strategy.get_signin_response(&state).await?,
+    ))
+}
+
 #[cfg(feature = "email")]
 pub async fn connect_account(
     state: AppState,
@@ -832,22 +876,22 @@ pub async fn list_merchant_ids_for_user(
     state: AppState,
     user: auth::UserFromToken,
 ) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
-    let merchant_ids = utils::user_role::get_merchant_ids_for_user(&state, &user.user_id).await?;
+    let user_roles =
+        utils::user_role::get_active_user_roles_for_user(&state, &user.user_id).await?;
 
     let merchant_accounts = state
         .store
-        .list_multiple_merchant_accounts(merchant_ids)
+        .list_multiple_merchant_accounts(
+            user_roles
+                .iter()
+                .map(|role| role.merchant_id.clone())
+                .collect(),
+        )
         .await
         .change_context(UserErrors::InternalServerError)?;
 
     Ok(ApplicationResponse::Json(
-        merchant_accounts
-            .into_iter()
-            .map(|acc| user_api::UserMerchantAccount {
-                merchant_id: acc.merchant_id,
-                merchant_name: acc.merchant_name,
-            })
-            .collect(),
+        utils::user::get_multiple_merchant_details_with_status(user_roles, merchant_accounts)?,
     ))
 }
 
@@ -868,11 +912,38 @@ pub async fn get_users_for_merchant_account(
     Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users)))
 }
 
+#[cfg(feature = "email")]
+pub async fn verify_email_without_invite_checks(
+    state: AppState,
+    req: user_api::VerifyEmailRequest,
+) -> UserResponse<user_api::DashboardEntryResponse> {
+    let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
+        .await
+        .change_context(UserErrors::LinkInvalid)?;
+    let user = state
+        .store
+        .find_user_by_email(token.get_email())
+        .await
+        .change_context(UserErrors::InternalServerError)?;
+    let user = state
+        .store
+        .update_user_by_user_id(user.user_id.as_str(), storage_user::UserUpdate::VerifyUser)
+        .await
+        .change_context(UserErrors::InternalServerError)?;
+    let user_from_db: domain::UserFromStorage = user.into();
+    let user_role = user_from_db.get_role_from_db(state.clone()).await?;
+    let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
+
+    Ok(ApplicationResponse::Json(
+        utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
+    ))
+}
+
 #[cfg(feature = "email")]
 pub async fn verify_email(
     state: AppState,
     req: user_api::VerifyEmailRequest,
-) -> UserResponse<user_api::VerifyEmailResponse> {
+) -> UserResponse<user_api::SignInResponse> {
     let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
         .await
         .change_context(UserErrors::LinkInvalid)?;
@@ -890,11 +961,29 @@ pub async fn verify_email(
         .change_context(UserErrors::InternalServerError)?;
 
     let user_from_db: domain::UserFromStorage = user.into();
-    let user_role = user_from_db.get_role_from_db(state.clone()).await?;
-    let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
+
+    let signin_strategy =
+        if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() {
+            let preferred_role = user_from_db
+                .get_role_from_db_by_merchant_id(&state, preferred_merchant_id.as_str())
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("User role with preferred_merchant_id not found")?;
+            domain::SignInWithRoleStrategyType::SingleRole(domain::SignInWithSingleRoleStrategy {
+                user: user_from_db,
+                user_role: preferred_role,
+            })
+        } else {
+            let user_roles = user_from_db.get_roles_from_db(&state).await?;
+            domain::SignInWithRoleStrategyType::decide_signin_strategy_by_user_roles(
+                user_from_db,
+                user_roles,
+            )
+            .await?
+        };
 
     Ok(ApplicationResponse::Json(
-        utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
+        signin_strategy.get_signin_response(&state).await?,
     ))
 }
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 44822efddc4..7acb23c40ee 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -952,7 +952,10 @@ impl User {
         let mut route = web::scope("/user").app_data(web::Data::new(state));
 
         route = route
-            .service(web::resource("/signin").route(web::post().to(user_signin)))
+            .service(
+                web::resource("/signin").route(web::post().to(user_signin_without_invite_checks)),
+            )
+            .service(web::resource("/v2/signin").route(web::post().to(user_signin)))
             .service(web::resource("/change_password").route(web::post().to(change_password)))
             .service(web::resource("/internal_signup").route(web::post().to(internal_user_signup)))
             .service(web::resource("/switch_merchant").route(web::post().to(switch_merchant_id)))
@@ -961,14 +964,7 @@ impl User {
                     .route(web::post().to(user_merchant_account_create)),
             )
             .service(web::resource("/switch/list").route(web::get().to(list_merchant_ids_for_user)))
-            .service(web::resource("/user/list").route(web::get().to(get_user_details)))
             .service(web::resource("/permission_info").route(web::get().to(get_authorization_info)))
-            .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").route(web::get().to(get_role_from_token)))
-            .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("/user/invite/accept").route(web::post().to(accept_invitation)))
             .service(web::resource("/update").route(web::post().to(update_user_account_details)))
             .service(
                 web::resource("/user/invite_multiple").route(web::post().to(invite_multiple_user)),
@@ -980,6 +976,23 @@ impl User {
             )
             .service(web::resource("/user/delete").route(web::delete().to(delete_user_role)));
 
+        // User management
+        route = route.service(
+            web::scope("/user")
+                .service(web::resource("/list").route(web::get().to(get_user_details)))
+                .service(web::resource("/invite").route(web::post().to(invite_user)))
+                .service(web::resource("/invite/accept").route(web::post().to(accept_invitation)))
+                .service(web::resource("/update_role").route(web::post().to(update_user_role))),
+        );
+
+        // Role information
+        route = route.service(
+            web::scope("/role")
+                .service(web::resource("").route(web::get().to(get_role_from_token)))
+                .service(web::resource("/list").route(web::get().to(list_all_roles)))
+                .service(web::resource("/{role_id}").route(web::get().to(get_role))),
+        );
+
         #[cfg(feature = "dummy_connector")]
         {
             route = route.service(
@@ -1000,7 +1013,11 @@ impl User {
                     web::resource("/signup_with_merchant_id")
                         .route(web::post().to(user_signup_with_merchant_id)),
                 )
-                .service(web::resource("/verify_email").route(web::post().to(verify_email)))
+                .service(
+                    web::resource("/verify_email")
+                        .route(web::post().to(verify_email_without_invite_checks)),
+                )
+                .service(web::resource("/v2/verify_email").route(web::post().to(verify_email)))
                 .service(
                     web::resource("/verify_email_request")
                         .route(web::post().to(verify_email_request)),
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 6df8c7fb7a7..07894afe732 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -161,6 +161,7 @@ impl From<Flow> for ApiIdentifier {
 
             Flow::UserConnectAccount
             | Flow::UserSignUp
+            | Flow::UserSignInWithoutInviteChecks
             | Flow::UserSignIn
             | Flow::ChangePassword
             | Flow::SetDashboardMetadata
@@ -179,6 +180,7 @@ impl From<Flow> for ApiIdentifier {
             | Flow::InviteMultipleUser
             | Flow::DeleteUser
             | Flow::UserSignUpWithMerchantId
+            | Flow::VerifyEmailWithoutInviteChecks
             | Flow::VerifyEmail
             | Flow::VerifyEmailRequest
             | Flow::UpdateUserAccountDetails => Self::User,
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 02704cf701f..88e19ddf755 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -58,6 +58,25 @@ pub async fn user_signup(
     .await
 }
 
+pub async fn user_signin_without_invite_checks(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<user_api::SignInRequest>,
+) -> HttpResponse {
+    let flow = Flow::UserSignInWithoutInviteChecks;
+    let req_payload = json_payload.into_inner();
+    Box::pin(api::server_wrap(
+        flow.clone(),
+        state,
+        &http_req,
+        req_payload.clone(),
+        |state, _, req_body| user_core::signin_without_invite_checks(state, req_body),
+        &auth::NoAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 pub async fn user_signin(
     state: web::Data<AppState>,
     http_req: HttpRequest,
@@ -368,6 +387,25 @@ pub async fn invite_multiple_user(
     .await
 }
 
+#[cfg(feature = "email")]
+pub async fn verify_email_without_invite_checks(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<user_api::VerifyEmailRequest>,
+) -> HttpResponse {
+    let flow = Flow::VerifyEmailWithoutInviteChecks;
+    Box::pin(api::server_wrap(
+        flow.clone(),
+        state,
+        &http_req,
+        json_payload.into_inner(),
+        |state, _, req_payload| user_core::verify_email_without_invite_checks(state, req_payload),
+        &auth::NoAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
 #[cfg(feature = "email")]
 pub async fn verify_email(
     state: web::Data<AppState>,
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index f83134e5825..3f9ccda8651 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -29,7 +29,7 @@ pub async fn get_authorization_info(
     .await
 }
 
-pub async fn list_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
     let flow = Flow::ListRoles;
     Box::pin(api::server_wrap(
         flow,
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index bbe21f289aa..d3ea69ecd86 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -26,11 +26,12 @@ use crate::{
     db::StorageInterface,
     routes::AppState,
     services::{
+        authentication as auth,
         authentication::UserFromToken,
         authorization::{info, predefined_permissions},
     },
     types::transformers::ForeignFrom,
-    utils::user::password,
+    utils::{self, user::password},
 };
 
 pub mod dashboard_metadata;
@@ -733,7 +734,15 @@ impl UserFromStorage {
     pub async fn get_role_from_db(&self, state: AppState) -> UserResult<UserRole> {
         state
             .store
-            .find_user_role_by_user_id(self.get_user_id())
+            .find_user_role_by_user_id(&self.0.user_id)
+            .await
+            .change_context(UserErrors::InternalServerError)
+    }
+
+    pub async fn get_roles_from_db(&self, state: &AppState) -> UserResult<Vec<UserRole>> {
+        state
+            .store
+            .list_user_roles_by_user_id(&self.0.user_id)
             .await
             .change_context(UserErrors::InternalServerError)
     }
@@ -760,6 +769,29 @@ impl UserFromStorage {
         let days_left_for_verification = last_date_for_verification - today;
         Ok(Some(days_left_for_verification.whole_days()))
     }
+
+    pub fn get_preferred_merchant_id(&self) -> Option<String> {
+        self.0.preferred_merchant_id.clone()
+    }
+
+    pub async fn get_role_from_db_by_merchant_id(
+        &self,
+        state: &AppState,
+        merchant_id: &str,
+    ) -> UserResult<UserRole> {
+        state
+            .store
+            .find_user_role_by_user_id_merchant_id(self.get_user_id(), merchant_id)
+            .await
+            .map_err(|e| {
+                if e.current_context().is_db_not_found() {
+                    UserErrors::RoleNotFound
+                } else {
+                    UserErrors::InternalServerError
+                }
+            })
+            .into_report()
+    }
 }
 
 impl From<info::ModuleInfo> for user_role_api::ModuleInfo {
@@ -828,3 +860,101 @@ impl TryFrom<UserAndRoleJoined> for user_api::UserDetails {
         })
     }
 }
+
+pub enum SignInWithRoleStrategyType {
+    SingleRole(SignInWithSingleRoleStrategy),
+    MultipleRoles(SignInWithMultipleRolesStrategy),
+}
+
+impl SignInWithRoleStrategyType {
+    pub async fn decide_signin_strategy_by_user_roles(
+        user: UserFromStorage,
+        user_roles: Vec<UserRole>,
+    ) -> UserResult<Self> {
+        if user_roles.is_empty() {
+            return Err(UserErrors::InternalServerError.into());
+        }
+
+        if let Some(user_role) = user_roles
+            .iter()
+            .find(|role| role.status == UserStatus::Active)
+        {
+            Ok(Self::SingleRole(SignInWithSingleRoleStrategy {
+                user,
+                user_role: user_role.clone(),
+            }))
+        } else {
+            Ok(Self::MultipleRoles(SignInWithMultipleRolesStrategy {
+                user,
+                user_roles,
+            }))
+        }
+    }
+
+    pub async fn get_signin_response(
+        self,
+        state: &AppState,
+    ) -> UserResult<user_api::SignInResponse> {
+        match self {
+            Self::SingleRole(strategy) => strategy.get_signin_response(state).await,
+            Self::MultipleRoles(strategy) => strategy.get_signin_response(state).await,
+        }
+    }
+}
+
+pub struct SignInWithSingleRoleStrategy {
+    pub user: UserFromStorage,
+    pub user_role: UserRole,
+}
+
+impl SignInWithSingleRoleStrategy {
+    async fn get_signin_response(self, state: &AppState) -> UserResult<user_api::SignInResponse> {
+        let token =
+            utils::user::generate_jwt_auth_token(state, &self.user, &self.user_role).await?;
+        let dashboard_entry_response =
+            utils::user::get_dashboard_entry_response(state, self.user, self.user_role, token)?;
+        Ok(user_api::SignInResponse::DashboardEntry(
+            dashboard_entry_response,
+        ))
+    }
+}
+
+pub struct SignInWithMultipleRolesStrategy {
+    pub user: UserFromStorage,
+    pub user_roles: Vec<UserRole>,
+}
+
+impl SignInWithMultipleRolesStrategy {
+    async fn get_signin_response(self, state: &AppState) -> UserResult<user_api::SignInResponse> {
+        let merchant_accounts = state
+            .store
+            .list_multiple_merchant_accounts(
+                self.user_roles
+                    .iter()
+                    .map(|role| role.merchant_id.clone())
+                    .collect(),
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)?;
+
+        let merchant_details = utils::user::get_multiple_merchant_details_with_status(
+            self.user_roles,
+            merchant_accounts,
+        )?;
+
+        Ok(user_api::SignInResponse::MerchantSelect(
+            user_api::MerchantSelectResponse {
+                name: self.user.get_name(),
+                email: self.user.get_email(),
+                token: auth::UserAuthToken::new_token(
+                    self.user.get_user_id().to_string(),
+                    &state.conf,
+                )
+                .await?
+                .into(),
+                merchants: merchant_details,
+                verification_days_left: utils::user::get_verification_days_left(state, &self.user)?,
+            },
+        ))
+    }
+}
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index a3f9e7978aa..697d10f772e 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -1,5 +1,7 @@
+use std::collections::HashMap;
+
 use api_models::user as user_api;
-use diesel_models::user_role::UserRole;
+use diesel_models::{enums::UserStatus, user_role::UserRole};
 use error_stack::ResultExt;
 use masking::Secret;
 
@@ -118,3 +120,29 @@ pub fn get_verification_days_left(
     #[cfg(not(feature = "email"))]
     return Ok(None);
 }
+
+pub fn get_multiple_merchant_details_with_status(
+    user_roles: Vec<UserRole>,
+    merchant_accounts: Vec<MerchantAccount>,
+) -> UserResult<Vec<user_api::UserMerchantAccount>> {
+    let roles: HashMap<_, _> = user_roles
+        .into_iter()
+        .map(|user_role| (user_role.merchant_id.clone(), user_role))
+        .collect();
+
+    merchant_accounts
+        .into_iter()
+        .map(|merchant| {
+            let role = roles
+                .get(merchant.merchant_id.as_str())
+                .ok_or(UserErrors::InternalServerError.into())
+                .attach_printable("Merchant exists but user role doesn't")?;
+
+            Ok(user_api::UserMerchantAccount {
+                merchant_id: merchant.merchant_id.clone(),
+                merchant_name: merchant.merchant_name.clone(),
+                is_active: role.status == UserStatus::Active,
+            })
+        })
+        .collect()
+}
diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs
index 65ead92ad34..7ca06aeda0d 100644
--- a/crates/router/src/utils/user_role.rs
+++ b/crates/router/src/utils/user_role.rs
@@ -1,5 +1,5 @@
 use api_models::user_role as user_role_api;
-use diesel_models::enums::UserStatus;
+use diesel_models::{enums::UserStatus, user_role::UserRole};
 use error_stack::ResultExt;
 
 use crate::{
@@ -17,19 +17,17 @@ pub fn is_internal_role(role_id: &str) -> bool {
         || role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER
 }
 
-pub async fn get_merchant_ids_for_user(state: &AppState, user_id: &str) -> UserResult<Vec<String>> {
+pub async fn get_active_user_roles_for_user(
+    state: &AppState,
+    user_id: &str,
+) -> UserResult<Vec<UserRole>> {
     Ok(state
         .store
         .list_user_roles_by_user_id(user_id)
         .await
         .change_context(UserErrors::InternalServerError)?
         .into_iter()
-        .filter_map(|ele| {
-            if ele.status == UserStatus::Active {
-                return Some(ele.merchant_id);
-            }
-            None
-        })
+        .filter(|ele| ele.status == UserStatus::Active)
         .collect())
 }
 
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 998c52f2c13..0d5710820ee 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -267,6 +267,8 @@ pub enum Flow {
     UserSignUp,
     /// User Sign Up
     UserSignUpWithMerchantId,
+    /// User Sign In without invite checks
+    UserSignInWithoutInviteChecks,
     /// User Sign In
     UserSignIn,
     /// User connect account
@@ -333,6 +335,8 @@ pub enum Flow {
     SyncOnboardingStatus,
     /// Reset tracking id
     ResetTrackingId,
+    /// Verify email token without invite checks
+    VerifyEmailWithoutInviteChecks,
     /// Verify email Token
     VerifyEmail,
     /// Send verify email