Bug: feat(user): new invitations api



New api to list invitations for users (not just limited to merchant accounts).
Api should be compatibly with work with user jwt token and single purpose jwt token.
diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs
index 4718da28d27..e4819b74fbc 100644
--- a/crates/api_models/src/user_role.rs
+++ b/crates/api_models/src/user_role.rs
@@ -1,5 +1,6 @@
 use common_enums::PermissionGroup;
 use common_utils::pii;
+use masking::Secret;
 
 pub mod role;
 
@@ -138,3 +139,11 @@ pub struct ListUsersInEntityResponse {
     pub email: pii::Email,
     pub roles: Vec<role::MinimalRoleInfo>,
 }
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct ListInvitationForUserResponse {
+    pub entity_id: String,
+    pub entity_type: common_enums::EntityType,
+    pub entity_name: Option<Secret<String>>,
+    pub role_id: String,
+}
diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs
index fde146408a9..1cd25e54cb3 100644
--- a/crates/diesel_models/src/query/user_role.rs
+++ b/crates/diesel_models/src/query/user_role.rs
@@ -8,7 +8,11 @@ use error_stack::{report, ResultExt};
 use router_env::logger;
 
 use crate::{
-    enums::UserRoleVersion, errors, query::generics, schema::user_roles::dsl, user_role::*,
+    enums::{UserRoleVersion, UserStatus},
+    errors,
+    query::generics,
+    schema::user_roles::dsl,
+    user_role::*,
     PgPooledConn, StorageResult,
 };
 
@@ -201,6 +205,7 @@ impl UserRole {
             .await
     }
 
+    #[allow(clippy::too_many_arguments)]
     pub async fn generic_user_roles_list_for_user(
         conn: &PgPooledConn,
         user_id: String,
@@ -208,7 +213,9 @@ impl UserRole {
         merchant_id: Option<id_type::MerchantId>,
         profile_id: Option<id_type::ProfileId>,
         entity_id: Option<String>,
+        status: Option<UserStatus>,
         version: Option<UserRoleVersion>,
+        limit: Option<u32>,
     ) -> StorageResult<Vec<Self>> {
         let mut query = <Self as HasTable>::table()
             .filter(dsl::user_id.eq(user_id))
@@ -234,6 +241,14 @@ impl UserRole {
             query = query.filter(dsl::version.eq(version));
         }
 
+        if let Some(status) = status {
+            query = query.filter(dsl::status.eq(status));
+        }
+
+        if let Some(limit) = limit {
+            query = query.limit(limit.into());
+        }
+
         router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());
 
         match generics::db_metrics::track_database_call::<Self, _, _>(
diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs
index ac7def5ec2a..c454e8b58bc 100644
--- a/crates/diesel_models/src/user_role.rs
+++ b/crates/diesel_models/src/user_role.rs
@@ -26,7 +26,7 @@ pub struct UserRole {
     pub version: enums::UserRoleVersion,
 }
 
-fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
+pub fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
     match (user_role.version, user_role.role_id.as_str()) {
         (enums::UserRoleVersion::V1, consts::ROLE_ID_ORGANIZATION_ADMIN) => (
             user_role
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index f06e804cecc..8e99a202dfe 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -33,7 +33,10 @@ use crate::services::email::types as email_types;
 use crate::{
     consts,
     core::encryption::send_request_to_key_service_for_user,
-    db::domain::user_authentication_method::DEFAULT_USER_AUTH_METHOD,
+    db::{
+        domain::user_authentication_method::DEFAULT_USER_AUTH_METHOD,
+        user_role::ListUserRolesByUserIdPayload,
+    },
     routes::{app::ReqState, SessionState},
     services::{authentication as auth, authorization::roles, openidconnect, ApplicationResponse},
     types::{domain, transformers::ForeignInto},
@@ -2282,22 +2285,20 @@ pub async fn list_orgs_for_user(
 ) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
     let orgs = state
         .store
-        .list_user_roles_by_user_id(
-            user_from_token.user_id.as_str(),
-            None,
-            None,
-            None,
-            None,
-            None,
-        )
+        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+            user_id: user_from_token.user_id.as_str(),
+            org_id: None,
+            merchant_id: None,
+            profile_id: None,
+            entity_id: None,
+            version: None,
+            status: Some(UserStatus::Active),
+            limit: None,
+        })
         .await
         .change_context(UserErrors::InternalServerError)?
         .into_iter()
-        .filter_map(|user_role| {
-            (user_role.status == UserStatus::Active)
-                .then_some(user_role.org_id)
-                .flatten()
-        })
+        .filter_map(|user_role| user_role.org_id)
         .collect::<HashSet<_>>();
 
     let resp = futures::future::try_join_all(
@@ -2311,7 +2312,11 @@ pub async fn list_orgs_for_user(
         org_id: org.get_organization_id(),
         org_name: org.get_organization_name(),
     })
-    .collect();
+    .collect::<Vec<_>>();
+
+    if resp.is_empty() {
+        Err(UserErrors::InternalServerError).attach_printable("No orgs found for a user")?;
+    }
 
     Ok(ApplicationResponse::Json(resp))
 }
@@ -2344,26 +2349,24 @@ pub async fn list_merchants_for_user_in_org(
                     merchant_id: merchant_account.get_id().to_owned(),
                 },
             )
-            .collect()
+            .collect::<Vec<_>>()
     } else {
         let merchant_ids = state
             .store
-            .list_user_roles_by_user_id(
-                user_from_token.user_id.as_str(),
-                Some(&user_from_token.org_id),
-                None,
-                None,
-                None,
-                None,
-            )
+            .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                user_id: user_from_token.user_id.as_str(),
+                org_id: Some(&user_from_token.org_id),
+                merchant_id: None,
+                profile_id: None,
+                entity_id: None,
+                version: None,
+                status: Some(UserStatus::Active),
+                limit: None,
+            })
             .await
             .change_context(UserErrors::InternalServerError)?
             .into_iter()
-            .filter_map(|user_role| {
-                (user_role.status == UserStatus::Active)
-                    .then_some(user_role.merchant_id)
-                    .flatten()
-            })
+            .filter_map(|user_role| user_role.merchant_id)
             .collect::<HashSet<_>>()
             .into_iter()
             .collect();
@@ -2379,9 +2382,13 @@ pub async fn list_merchants_for_user_in_org(
                     merchant_id: merchant_account.get_id().to_owned(),
                 },
             )
-            .collect()
+            .collect::<Vec<_>>()
     };
 
+    if merchant_accounts.is_empty() {
+        Err(UserErrors::InternalServerError).attach_printable("No merchant found for a user")?;
+    }
+
     Ok(ApplicationResponse::Json(merchant_accounts))
 }
 
@@ -2427,26 +2434,24 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
                         profile_name: profile.profile_name,
                     },
                 )
-                .collect()
+                .collect::<Vec<_>>()
         } else {
             let profile_ids = state
                 .store
-                .list_user_roles_by_user_id(
-                    user_from_token.user_id.as_str(),
-                    Some(&user_from_token.org_id),
-                    Some(&user_from_token.merchant_id),
-                    None,
-                    None,
-                    None,
-                )
+                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                    user_id: user_from_token.user_id.as_str(),
+                    org_id: Some(&user_from_token.org_id),
+                    merchant_id: Some(&user_from_token.merchant_id),
+                    profile_id: None,
+                    entity_id: None,
+                    version: None,
+                    status: Some(UserStatus::Active),
+                    limit: None,
+                })
                 .await
                 .change_context(UserErrors::InternalServerError)?
                 .into_iter()
-                .filter_map(|user_role| {
-                    (user_role.status == UserStatus::Active)
-                        .then_some(user_role.profile_id)
-                        .flatten()
-                })
+                .filter_map(|user_role| user_role.profile_id)
                 .collect::<HashSet<_>>();
 
             futures::future::try_join_all(profile_ids.iter().map(|profile_id| {
@@ -2465,9 +2470,13 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
                     profile_name: profile.profile_name,
                 },
             )
-            .collect()
+            .collect::<Vec<_>>()
         };
 
+    if profiles.is_empty() {
+        Err(UserErrors::InternalServerError).attach_printable("No profile found for a user")?;
+    }
+
     Ok(ApplicationResponse::Json(profiles))
 }
 
@@ -2503,23 +2512,23 @@ pub async fn switch_org_for_user(
 
     let user_role = state
         .store
-        .list_user_roles_by_user_id(
-            &user_from_token.user_id,
-            Some(&request.org_id),
-            None,
-            None,
-            None,
-            None,
-        )
+        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+            user_id: &user_from_token.user_id,
+            org_id: Some(&request.org_id),
+            merchant_id: None,
+            profile_id: None,
+            entity_id: None,
+            version: None,
+            status: Some(UserStatus::Active),
+            limit: Some(1),
+        })
         .await
         .change_context(UserErrors::InternalServerError)
         .attach_printable("Failed to list user roles by user_id and org_id")?
-        .into_iter()
-        .find(|role| role.status == UserStatus::Active)
+        .pop()
         .ok_or(UserErrors::InvalidRoleOperationWithMessage(
             "No user role found for the requested org_id".to_string(),
-        ))?
-        .to_owned();
+        ))?;
 
     let merchant_id = utils::user_role::get_single_merchant_id(&state, &user_role).await?;
 
@@ -2547,7 +2556,7 @@ pub async fn switch_org_for_user(
             .await
             .change_context(UserErrors::InternalServerError)
             .attach_printable("Failed to list business profiles by merchant_id")?
-            .first()
+            .pop()
             .ok_or(UserErrors::InternalServerError)
             .attach_printable("No business profile found for the merchant_id")?
             .get_id()
@@ -2635,7 +2644,7 @@ pub async fn switch_merchant_for_user_in_org(
                 .await
                 .change_context(UserErrors::InternalServerError)
                 .attach_printable("Failed to list business profiles by merchant_id")?
-                .first()
+                .pop()
                 .ok_or(UserErrors::InternalServerError)
                 .attach_printable("No business profile found for the given merchant_id")?
                 .get_id()
@@ -2688,12 +2697,11 @@ pub async fn switch_merchant_for_user_in_org(
                 .await
                 .change_context(UserErrors::InternalServerError)
                 .attach_printable("Failed to list business profiles by merchant_id")?
-                .first()
+                .pop()
                 .ok_or(UserErrors::InternalServerError)
                 .attach_printable("No business profile found for the merchant_id")?
                 .get_id()
                 .to_owned();
-
             (
                 user_from_token.org_id.clone(),
                 merchant_id,
@@ -2705,25 +2713,25 @@ pub async fn switch_merchant_for_user_in_org(
         EntityType::Merchant | EntityType::Profile => {
             let user_role = state
                 .store
-                .list_user_roles_by_user_id(
-                    &user_from_token.user_id,
-                    Some(&user_from_token.org_id),
-                    Some(&request.merchant_id),
-                    None,
-                    None,
-                    None,
-                )
+                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                    user_id: &user_from_token.user_id,
+                    org_id: Some(&user_from_token.org_id),
+                    merchant_id: Some(&request.merchant_id),
+                    profile_id: None,
+                    entity_id: None,
+                    version: None,
+                    status: Some(UserStatus::Active),
+                    limit: Some(1),
+                })
                 .await
                 .change_context(UserErrors::InternalServerError)
                 .attach_printable(
                     "Failed to list user roles for the given user_id, org_id and merchant_id",
                 )?
-                .into_iter()
-                .find(|role| role.status == UserStatus::Active)
+                .pop()
                 .ok_or(UserErrors::InvalidRoleOperationWithMessage(
                     "No user role associated with the requested merchant_id".to_string(),
-                ))?
-                .to_owned();
+                ))?;
 
             let profile_id = if let Some(profile_id) = &user_role.profile_id {
                 profile_id.clone()
@@ -2749,7 +2757,7 @@ pub async fn switch_merchant_for_user_in_org(
                     .await
                     .change_context(UserErrors::InternalServerError)
                     .attach_printable("Failed to list business profiles for the given merchant_id")?
-                    .first()
+                    .pop()
                     .ok_or(UserErrors::InternalServerError)
                     .attach_printable("No business profile found for the given merchant_id")?
                     .get_id()
@@ -2846,23 +2854,24 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
         EntityType::Profile => {
             let user_role = state
                 .store
-                .list_user_roles_by_user_id(
-                    &user_from_token.user_id,
-                    Some(&user_from_token.org_id),
-                    Some(&user_from_token.merchant_id),
-                    Some(&request.profile_id),
-                    None,
-                    None,
+                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload{
+                    user_id:&user_from_token.user_id,
+                    org_id: Some(&user_from_token.org_id),
+                    merchant_id: Some(&user_from_token.merchant_id),
+                    profile_id:Some(&request.profile_id),
+                    entity_id: None,
+                    version:None,
+                    status: Some(UserStatus::Active),
+                    limit: Some(1)
+                }
                 )
                 .await
                 .change_context(UserErrors::InternalServerError)
                 .attach_printable("Failed to list user roles for the given user_id, org_id, merchant_id and profile_id")?
-                .into_iter()
-                .find(|role| role.status == UserStatus::Active)
+                .pop()
                 .ok_or(UserErrors::InvalidRoleOperationWithMessage(
                     "No user role associated with the profile".to_string(),
-                ))?
-                .to_owned();
+                ))?;
 
             (request.profile_id, user_role.role_id)
         }
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index 7752ce06e9a..5ee71d0e509 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -3,14 +3,14 @@ use std::collections::{HashMap, HashSet};
 use api_models::{user as user_api, user_role as user_role_api};
 use diesel_models::{
     enums::{UserRoleVersion, UserStatus},
-    user_role::UserRoleUpdate,
+    user_role::{get_entity_id_and_type, UserRoleUpdate},
 };
 use error_stack::{report, ResultExt};
 use once_cell::sync::Lazy;
 
 use crate::{
     core::errors::{StorageErrorExt, UserErrors, UserResponse},
-    db::user_role::ListUserRolesByOrgIdPayload,
+    db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload},
     routes::{app::ReqState, SessionState},
     services::{
         authentication as auth,
@@ -687,3 +687,41 @@ pub async fn list_users_in_lineage(
             .collect::<Result<Vec<_>, _>>()?,
     ))
 }
+
+pub async fn list_invitations_for_user(
+    state: SessionState,
+    user_from_token: auth::UserIdFromAuth,
+) -> UserResponse<Vec<user_role_api::ListInvitationForUserResponse>> {
+    let invitations = state
+        .store
+        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+            user_id: &user_from_token.user_id,
+            org_id: None,
+            merchant_id: None,
+            profile_id: None,
+            entity_id: None,
+            version: None,
+            status: Some(UserStatus::InvitationSent),
+            limit: None,
+        })
+        .await
+        .change_context(UserErrors::InternalServerError)
+        .attach_printable("Failed to list user roles by user id and invitation sent")?
+        .into_iter()
+        .collect::<HashSet<_>>()
+        .into_iter()
+        .filter_map(|user_role| {
+            let (entity_id, entity_type) = get_entity_id_and_type(&user_role);
+            entity_id.zip(entity_type).map(|(entity_id, entity_type)| {
+                user_role_api::ListInvitationForUserResponse {
+                    entity_id,
+                    entity_type,
+                    entity_name: None,
+                    role_id: user_role.role_id,
+                }
+            })
+        })
+        .collect();
+
+    Ok(ApplicationResponse::Json(invitations))
+}
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index d8a6947a5c7..6b9caf90a91 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -35,7 +35,10 @@ use super::{
     user::{sample_data::BatchSampleDataInterface, UserInterface},
     user_authentication_method::UserAuthenticationMethodInterface,
     user_key_store::UserKeyStoreInterface,
-    user_role::{InsertUserRolePayload, ListUserRolesByOrgIdPayload, UserRoleInterface},
+    user_role::{
+        InsertUserRolePayload, ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload,
+        UserRoleInterface,
+    },
 };
 #[cfg(feature = "payouts")]
 use crate::services::kafka::payout::KafkaPayout;
@@ -2872,25 +2875,11 @@ impl UserRoleInterface for KafkaStore {
             .await
     }
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id<'a>(
         &self,
-        user_id: &str,
-        org_id: Option<&id_type::OrganizationId>,
-        merchant_id: Option<&id_type::MerchantId>,
-        profile_id: Option<&id_type::ProfileId>,
-        entity_id: Option<&String>,
-        version: Option<enums::UserRoleVersion>,
+        payload: ListUserRolesByUserIdPayload<'a>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
-        self.diesel_store
-            .list_user_roles_by_user_id(
-                user_id,
-                org_id,
-                merchant_id,
-                profile_id,
-                entity_id,
-                version,
-            )
-            .await
+        self.diesel_store.list_user_roles_by_user_id(payload).await
     }
 
     async fn list_user_roles_by_org_id<'a>(
diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs
index eadd1ef4a5b..d511010e6b5 100644
--- a/crates/router/src/db/user_role.rs
+++ b/crates/router/src/db/user_role.rs
@@ -1,5 +1,8 @@
 use common_utils::id_type;
-use diesel_models::{enums, user_role as storage};
+use diesel_models::{
+    enums::{self, UserStatus},
+    user_role as storage,
+};
 use error_stack::{report, ResultExt};
 use router_env::{instrument, tracing};
 
@@ -33,6 +36,17 @@ pub struct ListUserRolesByOrgIdPayload<'a> {
     pub version: Option<enums::UserRoleVersion>,
 }
 
+pub struct ListUserRolesByUserIdPayload<'a> {
+    pub user_id: &'a str,
+    pub org_id: Option<&'a id_type::OrganizationId>,
+    pub merchant_id: Option<&'a id_type::MerchantId>,
+    pub profile_id: Option<&'a id_type::ProfileId>,
+    pub entity_id: Option<&'a String>,
+    pub version: Option<enums::UserRoleVersion>,
+    pub status: Option<UserStatus>,
+    pub limit: Option<u32>,
+}
+
 #[async_trait::async_trait]
 pub trait UserRoleInterface {
     async fn insert_user_role(
@@ -93,14 +107,9 @@ pub trait UserRoleInterface {
         version: enums::UserRoleVersion,
     ) -> CustomResult<storage::UserRole, errors::StorageError>;
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id<'a>(
         &self,
-        user_id: &str,
-        org_id: Option<&id_type::OrganizationId>,
-        merchant_id: Option<&id_type::MerchantId>,
-        profile_id: Option<&id_type::ProfileId>,
-        entity_id: Option<&String>,
-        version: Option<enums::UserRoleVersion>,
+        payload: ListUserRolesByUserIdPayload<'a>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
 
     async fn list_user_roles_by_org_id<'a>(
@@ -244,24 +253,21 @@ impl UserRoleInterface for Store {
         .map_err(|error| report!(errors::StorageError::from(error)))
     }
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id<'a>(
         &self,
-        user_id: &str,
-        org_id: Option<&id_type::OrganizationId>,
-        merchant_id: Option<&id_type::MerchantId>,
-        profile_id: Option<&id_type::ProfileId>,
-        entity_id: Option<&String>,
-        version: Option<enums::UserRoleVersion>,
+        payload: ListUserRolesByUserIdPayload<'a>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
         storage::UserRole::generic_user_roles_list_for_user(
             &conn,
-            user_id.to_owned(),
-            org_id.cloned(),
-            merchant_id.cloned(),
-            profile_id.cloned(),
-            entity_id.cloned(),
-            version,
+            payload.user_id.to_owned(),
+            payload.org_id.cloned(),
+            payload.merchant_id.cloned(),
+            payload.profile_id.cloned(),
+            payload.entity_id.cloned(),
+            payload.status,
+            payload.version,
+            payload.limit,
         )
         .await
         .map_err(|error| report!(errors::StorageError::from(error)))
@@ -552,50 +558,52 @@ impl UserRoleInterface for MockDb {
         }
     }
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id<'a>(
         &self,
-        user_id: &str,
-        org_id: Option<&id_type::OrganizationId>,
-        merchant_id: Option<&id_type::MerchantId>,
-        profile_id: Option<&id_type::ProfileId>,
-        entity_id: Option<&String>,
-        version: Option<enums::UserRoleVersion>,
+        payload: ListUserRolesByUserIdPayload<'a>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
         let user_roles = self.user_roles.lock().await;
 
-        let filtered_roles: Vec<_> = user_roles
+        let mut filtered_roles: Vec<_> = user_roles
             .iter()
             .filter_map(|role| {
-                let mut filter_condition = role.user_id == user_id;
+                let mut filter_condition = role.user_id == payload.user_id;
 
                 role.org_id
                     .as_ref()
-                    .zip(org_id)
+                    .zip(payload.org_id)
                     .inspect(|(role_org_id, org_id)| {
                         filter_condition = filter_condition && role_org_id == org_id
                     });
-                role.merchant_id.as_ref().zip(merchant_id).inspect(
+                role.merchant_id.as_ref().zip(payload.merchant_id).inspect(
                     |(role_merchant_id, merchant_id)| {
                         filter_condition = filter_condition && role_merchant_id == merchant_id
                     },
                 );
-                role.profile_id.as_ref().zip(profile_id).inspect(
+                role.profile_id.as_ref().zip(payload.profile_id).inspect(
                     |(role_profile_id, profile_id)| {
                         filter_condition = filter_condition && role_profile_id == profile_id
                     },
                 );
-                role.entity_id
-                    .as_ref()
-                    .zip(entity_id)
-                    .inspect(|(role_entity_id, entity_id)| {
+                role.entity_id.as_ref().zip(payload.entity_id).inspect(
+                    |(role_entity_id, entity_id)| {
                         filter_condition = filter_condition && role_entity_id == entity_id
-                    });
-                version.inspect(|ver| filter_condition = filter_condition && ver == &role.version);
+                    },
+                );
+                payload
+                    .version
+                    .inspect(|ver| filter_condition = filter_condition && ver == &role.version);
+                payload.status.inspect(|status| {
+                    filter_condition = filter_condition && status == &role.status
+                });
 
                 filter_condition.then(|| role.to_owned())
             })
             .collect();
 
+        if let Some(Ok(limit)) = payload.limit.map(|val| val.try_into()) {
+            filtered_roles = filtered_roles.into_iter().take(limit).collect();
+        }
         Ok(filtered_roles)
     }
 
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 9b35e3ca475..4b1f1bd8b14 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1725,6 +1725,9 @@ impl User {
                 .service(
                     web::resource("/profile")
                         .route(web::get().to(list_profiles_for_user_in_org_and_merchant)),
+                )
+                .service(
+                    web::resource("/invitation").route(web::get().to(list_invitations_for_user)),
                 ),
         );
 
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index 8f61d78c4a1..c6160497918 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -250,6 +250,7 @@ impl From<Flow> for ApiIdentifier {
             | Flow::ListOrgForUser
             | Flow::ListMerchantsForUserInOrg
             | Flow::ListProfileForUserInOrgAndMerchant
+            | Flow::ListInvitationsForUser
             | Flow::AuthSelect => Self::User,
 
             Flow::ListRoles
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index 539409ab75f..61250774c10 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -310,3 +310,23 @@ pub async fn list_updatable_roles_at_entity_level(
     ))
     .await
 }
+
+pub async fn list_invitations_for_user(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+) -> HttpResponse {
+    let flow = Flow::ListInvitationsForUser;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        (),
+        |state, user_id_from_token, _, _| {
+            user_role_core::list_invitations_for_user(state, user_id_from_token)
+        },
+        &auth::SinglePurposeOrLoginTokenAuth(TokenPurpose::AcceptInvite),
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 51bb4b64730..3aae72d8aa7 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -474,6 +474,8 @@ pub enum Flow {
     ListProfileForUserInOrgAndMerchant,
     /// List Users in Org
     ListUsersInLineage,
+    /// List invitations for user
+    ListInvitationsForUser,
     /// List initial webhook delivery attempts
     WebhookEventInitialDeliveryAttemptList,
     /// List delivery attempts for a webhook event

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

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

## Description
New APIs to list of invitations for a user.
<!-- 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
To show user list of invitations.
<!--
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?
```sh
curl --location 'localhost:8080/user/list/invitation' \
--header 'Authorization: Bearer <JWT>' \
```

Response 
```json
[
    {
        "entity_id": "juspay2",
        "entity_type": "merchant",
        "entity_name": null,
        "role_id": "merchant_view_only"
    }
]
```
<!--
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
