Issue:
Bug: feat: Role information in list merchants API



- HCC FE is building a profile feature where they want to show all the merchants where the current user is part of.
- This also includes the role they were assigned in each merchant account.
- We have an API which lists merchant accounts but it doesn't send role they were assigned.
- So, we can change that API to also send the roles.

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 `role_id`, `role_name`, `org_id` fields in list merchants for user API.
- New route `/user/merchants/list` which points to the same function as `/user/switch/list`.
### 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
<!--
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).
-->
Closes #4325.

## 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)?
-->
Postman.
Both the following endpoints should produce the same results.
```
curl --location 'http://localhost:8080/user/merchants/list' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: login_token=JWT'
```
```
curl --location 'http://localhost:8080/user/switch/list' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: login_token=JWT'
```
Response: Should list all the merchant accounts that user is part of with the following structure.
```
[
    {
        "merchant_id": "merchant_1712559091",
        "merchant_name": null,
        "is_active": true,
        "role_id": "org_admin",
        "role_name": "organization_admin",
        "org_id": "org_NUwiY2HO9JRffXuYrJAx"
    },
    {
        "merchant_id": "merchant_1712559763",
        "merchant_name": null,
        "is_active": true,
        "role_id": "merchant_view_only",
        "role_name": "view_only",
        "org_id": "org_do8JuIQiH0tDQtWly4xo"
    }
]
```

## 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:

Postman.
Both the following endpoints should produce the same results.
```
curl --location 'http://localhost:8080/user/merchants/list' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: login_token=JWT'
```
```
curl --location 'http://localhost:8080/user/switch/list' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: login_token=JWT'
```
Response: Should list all the merchant accounts that user is part of with the following structure.
```
[
    {
        "merchant_id": "merchant_1712559091",
        "merchant_name": null,
        "is_active": true,
        "role_id": "org_admin",
        "role_name": "organization_admin",
        "org_id": "org_NUwiY2HO9JRffXuYrJAx"
    },
    {
        "merchant_id": "merchant_1712559763",
        "merchant_name": null,
        "is_active": true,
        "role_id": "merchant_view_only",
        "role_name": "view_only",
        "org_id": "org_do8JuIQiH0tDQtWly4xo"
    }
]
```

Patch:
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 9b193a9ba12..afe55afb276 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -190,6 +190,9 @@ pub struct UserMerchantAccount {
     pub merchant_id: String,
     pub merchant_name: OptionalEncryptableName,
     pub is_active: bool,
+    pub role_id: String,
+    pub role_name: String,
+    pub org_id: String,
 }
 
 #[cfg(feature = "recon")]
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 2702cec8eb5..50fd094d8d0 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1173,7 +1173,7 @@ pub async fn create_merchant_account(
     Ok(ApplicationResponse::StatusOk)
 }
 
-pub async fn list_merchant_ids_for_user(
+pub async fn list_merchants_for_user(
     state: AppState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
@@ -1194,8 +1194,15 @@ pub async fn list_merchant_ids_for_user(
         .await
         .change_context(UserErrors::InternalServerError)?;
 
+    let roles =
+        utils::user_role::get_multiple_role_info_for_user_roles(&state, &user_roles).await?;
+
     Ok(ApplicationResponse::Json(
-        utils::user::get_multiple_merchant_details_with_status(user_roles, merchant_accounts)?,
+        utils::user::get_multiple_merchant_details_with_status(
+            user_roles,
+            merchant_accounts,
+            roles,
+        )?,
     ))
 }
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 41893b05445..e0e3045aa8a 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1135,7 +1135,9 @@ impl User {
                 web::resource("/create_merchant")
                     .route(web::post().to(user_merchant_account_create)),
             )
-            .service(web::resource("/switch/list").route(web::get().to(list_merchant_ids_for_user)))
+            // TODO: Remove this endpoint once migration to /merchants/list is done
+            .service(web::resource("/switch/list").route(web::get().to(list_merchants_for_user)))
+            .service(web::resource("/merchants/list").route(web::get().to(list_merchants_for_user)))
             .service(web::resource("/permission_info").route(web::get().to(get_authorization_info)))
             .service(web::resource("/update").route(web::post().to(update_user_account_details)))
             .service(
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index c296d3d93b3..3e5061b401d 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -297,17 +297,14 @@ pub async fn delete_sample_data(
     .await
 }
 
-pub async fn list_merchant_ids_for_user(
-    state: web::Data<AppState>,
-    req: HttpRequest,
-) -> HttpResponse {
+pub async fn list_merchants_for_user(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
     let flow = Flow::UserMerchantAccountList;
     Box::pin(api::server_wrap(
         flow,
         state,
         &req,
         (),
-        |state, user, _| user_core::list_merchant_ids_for_user(state, user),
+        |state, user, _| user_core::list_merchants_for_user(state, user),
         &auth::DashboardNoPermissionAuth,
         api_locking::LockAction::NotApplicable,
     ))
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index 93df373d21a..bc06ffbad7b 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -896,9 +896,14 @@ impl SignInWithMultipleRolesStrategy {
             .await
             .change_context(UserErrors::InternalServerError)?;
 
+        let roles =
+            utils::user_role::get_multiple_role_info_for_user_roles(state, &self.user_roles)
+                .await?;
+
         let merchant_details = utils::user::get_multiple_merchant_details_with_status(
             self.user_roles,
             merchant_accounts,
+            roles,
         )?;
 
         Ok(user_api::SignInResponse::MerchantSelect(
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index f8b7d86c74a..60b2ac48286 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
 use api_models::user as user_api;
 use common_utils::errors::CustomResult;
 use diesel_models::{enums::UserStatus, user_role::UserRole};
-use error_stack::{report, ResultExt};
+use error_stack::ResultExt;
 use masking::{ExposeInterface, Secret};
 
 use crate::{
@@ -137,24 +137,38 @@ pub fn get_verification_days_left(
 pub fn get_multiple_merchant_details_with_status(
     user_roles: Vec<UserRole>,
     merchant_accounts: Vec<MerchantAccount>,
+    roles: Vec<RoleInfo>,
 ) -> UserResult<Vec<user_api::UserMerchantAccount>> {
-    let roles: HashMap<_, _> = user_roles
+    let merchant_account_map = merchant_accounts
         .into_iter()
-        .map(|user_role| (user_role.merchant_id.clone(), user_role))
-        .collect();
+        .map(|merchant_account| (merchant_account.merchant_id.clone(), merchant_account))
+        .collect::<HashMap<_, _>>();
 
-    merchant_accounts
+    let role_map = roles
         .into_iter()
-        .map(|merchant| {
-            let role = roles
-                .get(merchant.merchant_id.as_str())
-                .ok_or(report!(UserErrors::InternalServerError))
-                .attach_printable("Merchant exists but user role doesn't")?;
+        .map(|role_info| (role_info.get_role_id().to_string(), role_info))
+        .collect::<HashMap<_, _>>();
+
+    user_roles
+        .into_iter()
+        .map(|user_role| {
+            let merchant_account = merchant_account_map
+                .get(&user_role.merchant_id)
+                .ok_or(UserErrors::InternalServerError)
+                .attach_printable("Merchant account for user role doesn't exist")?;
+
+            let role_info = role_map
+                .get(&user_role.role_id)
+                .ok_or(UserErrors::InternalServerError)
+                .attach_printable("Role info for user role doesn't exist")?;
 
             Ok(user_api::UserMerchantAccount {
-                merchant_id: merchant.merchant_id.clone(),
-                merchant_name: merchant.merchant_name.clone(),
-                is_active: role.status == UserStatus::Active,
+                merchant_id: user_role.merchant_id,
+                merchant_name: merchant_account.merchant_name.clone(),
+                is_active: user_role.status == UserStatus::Active,
+                role_id: user_role.role_id,
+                role_name: role_info.get_role_name().to_string(),
+                org_id: user_role.org_id,
             })
         })
         .collect()
diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs
index 1a52153b43d..ba4c2878667 100644
--- a/crates/router/src/utils/user_role.rs
+++ b/crates/router/src/utils/user_role.rs
@@ -8,7 +8,7 @@ use router_env::logger;
 
 use crate::{
     consts,
-    core::errors::{UserErrors, UserResult},
+    core::errors::{StorageErrorExt, UserErrors, UserResult},
     routes::AppState,
     services::authorization::{self as authz, permissions::Permission, roles},
     types::domain,
@@ -141,3 +141,22 @@ pub async fn set_role_permissions_in_cache_if_required(
     .change_context(UserErrors::InternalServerError)
     .attach_printable("Error setting permissions in redis")
 }
+
+pub async fn get_multiple_role_info_for_user_roles(
+    state: &AppState,
+    user_roles: &[UserRole],
+) -> UserResult<Vec<roles::RoleInfo>> {
+    futures::future::try_join_all(user_roles.iter().map(|user_role| async {
+        let role = roles::RoleInfo::from_role_id(
+            state,
+            &user_role.role_id,
+            &user_role.merchant_id,
+            &user_role.org_id,
+        )
+        .await
+        .to_not_found_response(UserErrors::InternalServerError)
+        .attach_printable("Role for user role doesn't exist")?;
+        Ok::<_, error_stack::Report<UserErrors>>(role)
+    }))
+    .await
+}