File: crates/router/src/core/user_role.rs
 Public functions: 9

use std::{
    collections::{HashMap, HashSet},
    sync::LazyLock,
};

use api_models::{
    user as user_api,
    user_role::{self as user_role_api, role as role_api},
};
use diesel_models::{
    enums::{UserRoleVersion, UserStatus},
    organization::OrganizationBridge,
    user_role::UserRoleUpdate,
};
use error_stack::{report, ResultExt};
use masking::Secret;

use crate::{
    core::errors::{StorageErrorExt, UserErrors, UserResponse},
    db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload},
    routes::{app::ReqState, SessionState},
    services::{
        authentication as auth,
        authorization::{
            info,
            permission_groups::{ParentGroupExt, PermissionGroupExt},
            roles,
        },
        ApplicationResponse,
    },
    types::domain,
    utils,
};
pub mod role;
use common_enums::{EntityType, ParentGroup, PermissionGroup};
use strum::IntoEnumIterator;

// TODO: To be deprecated
pub async fn get_authorization_info_with_groups(
    _state: SessionState,
) -> UserResponse<user_role_api::AuthorizationInfoResponse> {
    Ok(ApplicationResponse::Json(
        user_role_api::AuthorizationInfoResponse(
            info::get_group_authorization_info()
                .ok_or(UserErrors::InternalServerError)
                .attach_printable("No visible groups found")?
                .into_iter()
                .map(user_role_api::AuthorizationInfo::Group)
                .collect(),
        ),
    ))
}

pub async fn get_authorization_info_with_group_tag(
) -> UserResponse<user_role_api::AuthorizationInfoResponse> {
    static GROUPS_WITH_PARENT_TAGS: LazyLock<Vec<user_role_api::ParentInfo>> =
        LazyLock::new(|| {
            PermissionGroup::iter()
                .map(|group| (group.parent(), group))
                .fold(
                    HashMap::new(),
                    |mut acc: HashMap<ParentGroup, Vec<PermissionGroup>>, (key, value)| {
                        acc.entry(key).or_default().push(value);
                        acc
                    },
                )
                .into_iter()
                .filter_map(|(name, value)| {
                    Some(user_role_api::ParentInfo {
                        name: name.clone(),
                        description: info::get_parent_group_description(name)?,
                        groups: value,
                    })
                })
                .collect()
        });

    Ok(ApplicationResponse::Json(
        user_role_api::AuthorizationInfoResponse(
            GROUPS_WITH_PARENT_TAGS
                .iter()
                .cloned()
                .map(user_role_api::AuthorizationInfo::GroupWithTag)
                .collect(),
        ),
    ))
}

pub async fn get_parent_group_info(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: role_api::GetParentGroupsInfoQueryParams,
) -> UserResponse<Vec<role_api::ParentGroupInfo>> {
    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InvalidRoleId)?;

    let entity_type = request
        .entity_type
        .unwrap_or_else(|| role_info.get_entity_type());

    if role_info.get_entity_type() < entity_type {
        return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
            "Invalid operation, requestor entity type = {} cannot access entity type = {}",
            role_info.get_entity_type(),
            entity_type
        ));
    }

    let parent_groups =
        ParentGroup::get_descriptions_for_groups(entity_type, PermissionGroup::iter().collect())
            .unwrap_or_default()
            .into_iter()
            .map(|(parent_group, description)| role_api::ParentGroupInfo {
                name: parent_group.clone(),
                description,
                scopes: PermissionGroup::iter()
                    .filter_map(|group| (group.parent() == parent_group).then_some(group.scope()))
                    // TODO: Remove this hashset conversion when merchant access
                    // and organization access groups are removed
                    .collect::<HashSet<_>>()
                    .into_iter()
                    .collect(),
            })
            .collect::<Vec<_>>();

    Ok(ApplicationResponse::Json(parent_groups))
}

pub async fn update_user_role(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: user_role_api::UpdateUserRoleRequest,
    _req_state: ReqState,
) -> UserResponse<()> {
    let role_info = roles::RoleInfo::from_role_id_in_lineage(
        &state,
        &req.role_id,
        &user_from_token.merchant_id,
        &user_from_token.org_id,
        &user_from_token.profile_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InvalidRoleId)?;

    if !role_info.is_updatable() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable(format!("User role cannot be updated to {}", req.role_id));
    }

    let user_to_be_updated =
        utils::user::get_user_from_db_by_email(&state, domain::UserEmail::try_from(req.email)?)
            .await
            .to_not_found_response(UserErrors::InvalidRoleOperation)
            .attach_printable("User not found in our records".to_string())?;

    if user_from_token.user_id == user_to_be_updated.get_user_id() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User Changing their own role");
    }

    let updator_role = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let mut is_updated = false;

    let v2_user_role_to_be_updated = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_to_be_updated.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V2,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(user_role) = v2_user_role_to_be_updated {
        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
            &state,
            &user_role.role_id,
            &user_from_token.org_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !role_to_be_updated.is_updatable() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "User role cannot be updated from {}",
                role_to_be_updated.get_role_id()
            ));
        }

        if role_info.get_entity_type() != role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Upgrade and downgrade of roles is not allowed, user_entity_type = {} req_entity_type = {}",
                role_to_be_updated.get_entity_type(),
                role_info.get_entity_type(),
            ));
        }

        if updator_role.get_entity_type() < role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Invalid operation, update requestor = {} cannot update target = {}",
                updator_role.get_entity_type(),
                role_to_be_updated.get_entity_type()
            ));
        }

        state
            .global_store
            .update_user_role_by_user_id_and_lineage(
                user_to_be_updated.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                Some(&user_from_token.merchant_id),
                Some(&user_from_token.profile_id),
                UserRoleUpdate::UpdateRole {
                    role_id: req.role_id.clone(),
                    modified_by: user_from_token.user_id.clone(),
                },
                UserRoleVersion::V2,
            )
            .await
            .change_context(UserErrors::InternalServerError)?;

        is_updated = true;
    }

    let v1_user_role_to_be_updated = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_to_be_updated.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V1,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(user_role) = v1_user_role_to_be_updated {
        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
            &state,
            &user_role.role_id,
            &user_from_token.org_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !role_to_be_updated.is_updatable() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "User role cannot be updated from {}",
                role_to_be_updated.get_role_id()
            ));
        }

        if role_info.get_entity_type() != role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Upgrade and downgrade of roles is not allowed, user_entity_type = {} req_entity_type = {}",
                role_to_be_updated.get_entity_type(),
                role_info.get_entity_type(),
            ));
        }

        if updator_role.get_entity_type() < role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Invalid operation, update requestor = {} cannot update target = {}",
                updator_role.get_entity_type(),
                role_to_be_updated.get_entity_type()
            ));
        }

        state
            .global_store
            .update_user_role_by_user_id_and_lineage(
                user_to_be_updated.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                Some(&user_from_token.merchant_id),
                Some(&user_from_token.profile_id),
                UserRoleUpdate::UpdateRole {
                    role_id: req.role_id.clone(),
                    modified_by: user_from_token.user_id,
                },
                UserRoleVersion::V1,
            )
            .await
            .change_context(UserErrors::InternalServerError)?;

        is_updated = true;
    }

    if !is_updated {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User with given email is not found in the organization")?;
    }

    auth::blacklist::insert_user_in_blacklist(&state, user_to_be_updated.get_user_id()).await?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn accept_invitations_v2(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: user_role_api::AcceptInvitationsV2Request,
) -> UserResponse<()> {
    let lineages = futures::future::try_join_all(req.into_iter().map(|entity| {
        utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite(
            &state,
            &user_from_token.user_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            entity.entity_id,
            entity.entity_type,
        )
    }))
    .await?
    .into_iter()
    .flatten()
    .collect::<Vec<_>>();

    let update_results = futures::future::join_all(lineages.iter().map(
        |(org_id, merchant_id, profile_id)| async {
            let (update_v1_result, update_v2_result) =
                utils::user_role::update_v1_and_v2_user_roles_in_db(
                    &state,
                    user_from_token.user_id.as_str(),
                    user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id,
                    merchant_id.as_ref(),
                    profile_id.as_ref(),
                    UserRoleUpdate::UpdateStatus {
                        status: UserStatus::Active,
                        modified_by: user_from_token.user_id.clone(),
                    },
                )
                .await;

            if update_v1_result.is_err_and(|err| !err.current_context().is_db_not_found())
                || update_v2_result.is_err_and(|err| !err.current_context().is_db_not_found())
            {
                Err(report!(UserErrors::InternalServerError))
            } else {
                Ok(())
            }
        },
    ))
    .await;

    if update_results.is_empty() || update_results.iter().all(Result::is_err) {
        return Err(UserErrors::MerchantIdNotFound.into());
    }

    Ok(ApplicationResponse::StatusOk)
}

pub async fn accept_invitations_pre_auth(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    req: user_role_api::AcceptInvitationsPreAuthRequest,
) -> UserResponse<user_api::TokenResponse> {
    let lineages = futures::future::try_join_all(req.into_iter().map(|entity| {
        utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite(
            &state,
            &user_token.user_id,
            user_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            entity.entity_id,
            entity.entity_type,
        )
    }))
    .await?
    .into_iter()
    .flatten()
    .collect::<Vec<_>>();

    let update_results = futures::future::join_all(lineages.iter().map(
        |(org_id, merchant_id, profile_id)| async {
            let (update_v1_result, update_v2_result) =
                utils::user_role::update_v1_and_v2_user_roles_in_db(
                    &state,
                    user_token.user_id.as_str(),
                    user_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id,
                    merchant_id.as_ref(),
                    profile_id.as_ref(),
                    UserRoleUpdate::UpdateStatus {
                        status: UserStatus::Active,
                        modified_by: user_token.user_id.clone(),
                    },
                )
                .await;

            if update_v1_result.is_err_and(|err| !err.current_context().is_db_not_found())
                || update_v2_result.is_err_and(|err| !err.current_context().is_db_not_found())
            {
                Err(report!(UserErrors::InternalServerError))
            } else {
                Ok(())
            }
        },
    ))
    .await;

    if update_results.is_empty() || update_results.iter().all(Result::is_err) {
        return Err(UserErrors::MerchantIdNotFound.into());
    }

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_user_by_id(user_token.user_id.as_str())
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let current_flow =
        domain::CurrentFlow::new(user_token, domain::SPTFlow::MerchantSelect.into())?;
    let next_flow = current_flow.next(user_from_db.clone(), &state).await?;

    let token = next_flow.get_token(&state).await?;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };
    auth::cookies::set_cookie_response(response, token)
}

pub async fn delete_user_role(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: user_role_api::DeleteUserRoleRequest,
    _req_state: ReqState,
) -> UserResponse<()> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_user_by_email(&domain::UserEmail::from_pii_email(request.email)?)
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::InvalidRoleOperation)
                    .attach_printable("User not found in our records")
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })?
        .into();

    if user_from_db.get_user_id() == user_from_token.user_id {
        return Err(report!(UserErrors::InvalidDeleteOperation))
            .attach_printable("User deleting himself");
    }

    let deletion_requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let mut user_role_deleted_flag = false;

    // Find in V2
    let user_role_v2 = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_from_db.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V2,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(role_to_be_deleted) = user_role_v2 {
        let target_role_info = roles::RoleInfo::from_role_id_in_lineage(
            &state,
            &role_to_be_deleted.role_id,
            &user_from_token.merchant_id,
            &user_from_token.org_id,
            &user_from_token.profile_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !target_role_info.is_deletable() {
            return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
                "Invalid operation, role_id = {} is not deletable",
                role_to_be_deleted.role_id
            ));
        }

        if deletion_requestor_role_info.get_entity_type() < target_role_info.get_entity_type() {
            return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
                "Invalid operation, deletion requestor = {} cannot delete target = {}",
                deletion_requestor_role_info.get_entity_type(),
                target_role_info.get_entity_type()
            ));
        }

        user_role_deleted_flag = true;
        state
            .global_store
            .delete_user_role_by_user_id_and_lineage(
                user_from_db.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                &user_from_token.merchant_id,
                &user_from_token.profile_id,
                UserRoleVersion::V2,
            )
            .await
            .change_context(UserErrors::InternalServerError)
            .attach_printable("Error while deleting user role")?;
    }

    // Find in V1
    let user_role_v1 = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_from_db.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V1,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(role_to_be_deleted) = user_role_v1 {
        let target_role_info = roles::RoleInfo::from_role_id_in_lineage(
            &state,
            &role_to_be_deleted.role_id,
            &user_from_token.merchant_id,
            &user_from_token.org_id,
            &user_from_token.profile_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !target_role_info.is_deletable() {
            return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
                "Invalid operation, role_id = {} is not deletable",
                role_to_be_deleted.role_id
            ));
        }

        if deletion_requestor_role_info.get_entity_type() < target_role_info.get_entity_type() {
            return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
                "Invalid operation, deletion requestor = {} cannot delete target = {}",
                deletion_requestor_role_info.get_entity_type(),
                target_role_info.get_entity_type()
            ));
        }

        user_role_deleted_flag = true;
        state
            .global_store
            .delete_user_role_by_user_id_and_lineage(
                user_from_db.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                &user_from_token.merchant_id,
                &user_from_token.profile_id,
                UserRoleVersion::V1,
            )
            .await
            .change_context(UserErrors::InternalServerError)
            .attach_printable("Error while deleting user role")?;
    }

    if !user_role_deleted_flag {
        return Err(report!(UserErrors::InvalidDeleteOperation))
            .attach_printable("User is not associated with the merchant");
    }

    // Check if user has any more role associations
    let remaining_roles = state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: user_from_db.get_user_id(),
            tenant_id: user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),

            org_id: None,
            merchant_id: None,
            profile_id: None,
            entity_id: None,
            version: None,
            status: None,
            limit: None,
        })
        .await
        .change_context(UserErrors::InternalServerError)?;

    // If user has no more role associated with him then deleting user
    if remaining_roles.is_empty() {
        state
            .global_store
            .delete_user_by_user_id(user_from_db.get_user_id())
            .await
            .change_context(UserErrors::InternalServerError)
            .attach_printable("Error while deleting user entry")?;
    }

    auth::blacklist::insert_user_in_blacklist(&state, user_from_db.get_user_id()).await?;
    Ok(ApplicationResponse::StatusOk)
}

pub async fn list_users_in_lineage(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: user_role_api::ListUsersInEntityRequest,
) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
    let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let user_roles_set: HashSet<_> = match utils::user_role::get_min_entity(
        requestor_role_info.get_entity_type(),
        request.entity_type,
    )? {
        EntityType::Tenant => {
            let mut org_users = utils::user_role::fetch_user_roles_by_payload(
                &state,
                ListUserRolesByOrgIdPayload {
                    user_id: None,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: &user_from_token.org_id,
                    merchant_id: None,
                    profile_id: None,
                    version: None,
                    limit: None,
                },
                request.entity_type,
            )
            .await?;

            // Fetch tenant user
            let tenant_user = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: &user_from_token.user_id,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: None,
                    merchant_id: None,
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: None,
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?;

            org_users.extend(tenant_user);
            org_users
        }
        EntityType::Organization => {
            utils::user_role::fetch_user_roles_by_payload(
                &state,
                ListUserRolesByOrgIdPayload {
                    user_id: None,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: &user_from_token.org_id,
                    merchant_id: None,
                    profile_id: None,
                    version: None,
                    limit: None,
                },
                request.entity_type,
            )
            .await?
        }
        EntityType::Merchant => {
            utils::user_role::fetch_user_roles_by_payload(
                &state,
                ListUserRolesByOrgIdPayload {
                    user_id: None,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: &user_from_token.org_id,
                    merchant_id: Some(&user_from_token.merchant_id),
                    profile_id: None,
                    version: None,
                    limit: None,
                },
                request.entity_type,
            )
            .await?
        }
        EntityType::Profile => {
            utils::user_role::fetch_user_roles_by_payload(
                &state,
                ListUserRolesByOrgIdPayload {
                    user_id: None,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: &user_from_token.org_id,
                    merchant_id: Some(&user_from_token.merchant_id),
                    profile_id: Some(&user_from_token.profile_id),
                    version: None,
                    limit: None,
                },
                request.entity_type,
            )
            .await?
        }
    };

    // This filtering is needed because for org level users in V1, merchant_id is present.
    // Due to this, we get org level users in merchant level users list.
    let user_roles_set = user_roles_set
        .into_iter()
        .filter_map(|user_role| {
            let (_entity_id, entity_type) = user_role.get_entity_id_and_type()?;
            (entity_type <= requestor_role_info.get_entity_type()).then_some(user_role)
        })
        .collect::<HashSet<_>>();

    let mut email_map = state
        .global_store
        .find_users_by_user_ids(
            user_roles_set
                .iter()
                .map(|user_role| user_role.user_id.clone())
                .collect(),
        )
        .await
        .change_context(UserErrors::InternalServerError)?
        .into_iter()
        .map(|user| (user.user_id.clone(), user.email))
        .collect::<HashMap<_, _>>();

    let role_info_map =
        futures::future::try_join_all(user_roles_set.iter().map(|user_role| async {
            roles::RoleInfo::from_role_id_org_id_tenant_id(
                &state,
                &user_role.role_id,
                &user_from_token.org_id,
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
            )
            .await
            .map(|role_info| {
                (
                    user_role.role_id.clone(),
                    user_role_api::role::MinimalRoleInfo {
                        role_id: user_role.role_id.clone(),
                        role_name: role_info.get_role_name().to_string(),
                    },
                )
            })
        }))
        .await
        .change_context(UserErrors::InternalServerError)?
        .into_iter()
        .collect::<HashMap<_, _>>();

    let user_role_map = user_roles_set
        .into_iter()
        .fold(HashMap::new(), |mut map, user_role| {
            map.entry(user_role.user_id)
                .or_insert(Vec::with_capacity(1))
                .push(user_role.role_id);
            map
        });

    Ok(ApplicationResponse::Json(
        user_role_map
            .into_iter()
            .map(|(user_id, role_id_vec)| {
                Ok::<_, error_stack::Report<UserErrors>>(user_role_api::ListUsersInEntityResponse {
                    email: email_map
                        .remove(&user_id)
                        .ok_or(UserErrors::InternalServerError)?,
                    roles: role_id_vec
                        .into_iter()
                        .map(|role_id| {
                            role_info_map
                                .get(&role_id)
                                .cloned()
                                .ok_or(UserErrors::InternalServerError)
                        })
                        .collect::<Result<Vec<_>, _>>()?,
                })
            })
            .collect::<Result<Vec<_>, _>>()?,
    ))
}

pub async fn list_invitations_for_user(
    state: SessionState,
    user_from_token: auth::UserIdFromAuth,
) -> UserResponse<Vec<user_role_api::ListInvitationForUserResponse>> {
    let user_roles = state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: &user_from_token.user_id,
            tenant_id: user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_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<_>>();

    let (org_ids, merchant_ids, profile_ids_with_merchant_ids) = user_roles.iter().try_fold(
        (Vec::new(), Vec::new(), Vec::new()),
        |(mut org_ids, mut merchant_ids, mut profile_ids_with_merchant_ids), user_role| {
            let (_, entity_type) = user_role
                .get_entity_id_and_type()
                .ok_or(UserErrors::InternalServerError)
                .attach_printable("Failed to compute entity id and type")?;

            match entity_type {
                EntityType::Tenant => {
                    return Err(report!(UserErrors::InternalServerError))
                        .attach_printable("Tenant roles are not allowed for this operation");
                }
                EntityType::Organization => org_ids.push(
                    user_role
                        .org_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?,
                ),
                EntityType::Merchant => merchant_ids.push(
                    user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?,
                ),
                EntityType::Profile => profile_ids_with_merchant_ids.push((
                    user_role
                        .profile_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?,
                    user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?,
                )),
            }

            Ok::<_, error_stack::Report<UserErrors>>((
                org_ids,
                merchant_ids,
                profile_ids_with_merchant_ids,
            ))
        },
    )?;

    let org_name_map = futures::future::try_join_all(org_ids.into_iter().map(|org_id| async {
        let org_name = state
            .accounts_store
            .find_organization_by_org_id(&org_id)
            .await
            .change_context(UserErrors::InternalServerError)?
            .get_organization_name()
            .map(Secret::new);

        Ok::<_, error_stack::Report<UserErrors>>((org_id, org_name))
    }))
    .await?
    .into_iter()
    .collect::<HashMap<_, _>>();

    let key_manager_state = &(&state).into();

    let merchant_name_map = state
        .store
        .list_multiple_merchant_accounts(key_manager_state, merchant_ids)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into_iter()
        .map(|merchant| {
            (
                merchant.get_id().clone(),
                merchant
                    .merchant_name
                    .map(|encryptable_name| encryptable_name.into_inner()),
            )
        })
        .collect::<HashMap<_, _>>();

    let master_key = &state.store.get_master_key().to_vec().into();

    let profile_name_map = futures::future::try_join_all(profile_ids_with_merchant_ids.iter().map(
        |(profile_id, merchant_id)| async {
            let merchant_key_store = state
                .store
                .get_merchant_key_store_by_merchant_id(key_manager_state, merchant_id, master_key)
                .await
                .change_context(UserErrors::InternalServerError)?;

            let business_profile = state
                .store
                .find_business_profile_by_profile_id(
                    key_manager_state,
                    &merchant_key_store,
                    profile_id,
                )
                .await
                .change_context(UserErrors::InternalServerError)?;

            Ok::<_, error_stack::Report<UserErrors>>((
                profile_id.clone(),
                Secret::new(business_profile.profile_name),
            ))
        },
    ))
    .await?
    .into_iter()
    .collect::<HashMap<_, _>>();

    user_roles
        .into_iter()
        .map(|user_role| {
            let (entity_id, entity_type) = user_role
                .get_entity_id_and_type()
                .ok_or(UserErrors::InternalServerError)
                .attach_printable("Failed to compute entity id and type")?;

            let entity_name = match entity_type {
                EntityType::Tenant => {
                    return Err(report!(UserErrors::InternalServerError))
                        .attach_printable("Tenant roles are not allowed for this operation");
                }
                EntityType::Organization => user_role
                    .org_id
                    .as_ref()
                    .and_then(|org_id| org_name_map.get(org_id).cloned())
                    .ok_or(UserErrors::InternalServerError)?,
                EntityType::Merchant => user_role
                    .merchant_id
                    .as_ref()
                    .and_then(|merchant_id| merchant_name_map.get(merchant_id).cloned())
                    .ok_or(UserErrors::InternalServerError)?,
                EntityType::Profile => user_role
                    .profile_id
                    .as_ref()
                    .map(|profile_id| profile_name_map.get(profile_id).cloned())
                    .ok_or(UserErrors::InternalServerError)?,
            };

            Ok(user_role_api::ListInvitationForUserResponse {
                entity_id,
                entity_type,
                entity_name,
                role_id: user_role.role_id,
            })
        })
        .collect::<Result<Vec<_>, _>>()
        .map(ApplicationResponse::Json)
}


pub fn get_error_response(
    pix_data: Box<SantanderPixQRCodePaymentsResponse>,
    status_code: u16,
    attempt_status: AttemptStatus,
) -> ErrorResponse {
    ErrorResponse {
        code: NO_ERROR_CODE.to_string(),
        message: NO_ERROR_MESSAGE.to_string(),
        reason: None,
        status_code,
        attempt_status: Some(attempt_status),
        connector_transaction_id: Some(pix_data.transaction_id.clone()),
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
        connector_metadata: None,
    }
}


pub struct PaymentInput {
    payment_method_id: Secret<String>,
    transaction: TransactionBody,
}


pub struct PaymentOrder {
    id: String,
}


pub struct ErrorDetails {
    pub code: Option<String>,
    pub message: String,
    pub details: Option<Vec<ErrorDetailItem>>,
}


pub struct UpiIntentData {}


impl RoutingType {
    pub fn is_dynamic_routing(self) -> bool {
        self == Self::Dynamic
    }
}


File: crates/drainer/src/metrics.rs

use router_env::{counter_metric, global_meter, histogram_metric_f64, histogram_metric_u64};

global_meter!(DRAINER_METER, "DRAINER");

counter_metric!(JOBS_PICKED_PER_STREAM, DRAINER_METER);
counter_metric!(CYCLES_COMPLETED_SUCCESSFULLY, DRAINER_METER);
counter_metric!(CYCLES_COMPLETED_UNSUCCESSFULLY, DRAINER_METER);
counter_metric!(ERRORS_WHILE_QUERY_EXECUTION, DRAINER_METER);
counter_metric!(SUCCESSFUL_QUERY_EXECUTION, DRAINER_METER);
counter_metric!(SHUTDOWN_SIGNAL_RECEIVED, DRAINER_METER);
counter_metric!(SUCCESSFUL_SHUTDOWN, DRAINER_METER);
counter_metric!(STREAM_EMPTY, DRAINER_METER);
counter_metric!(STREAM_PARSE_FAIL, DRAINER_METER);
counter_metric!(DRAINER_HEALTH, DRAINER_METER);

histogram_metric_f64!(QUERY_EXECUTION_TIME, DRAINER_METER); // Time in (ms) milliseconds
histogram_metric_f64!(REDIS_STREAM_READ_TIME, DRAINER_METER); // Time in (ms) milliseconds
histogram_metric_f64!(REDIS_STREAM_TRIM_TIME, DRAINER_METER); // Time in (ms) milliseconds
histogram_metric_f64!(CLEANUP_TIME, DRAINER_METER); // Time in (ms) milliseconds
histogram_metric_u64!(DRAINER_DELAY_SECONDS, DRAINER_METER); // Time in (s) seconds
histogram_metric_f64!(REDIS_STREAM_DEL_TIME, DRAINER_METER); // Time in (ms) milliseconds


Web Documentation: Reconciliation | Hyperswitch
# Type: Web Doc

Note
:
For Reconciliation V1, please check
here
.
We will continue to support Reconciliation V1 for all existing merchants. With the launch of V2, reconciliation is now a configurable experience — merchants will have the choice to continue with V1 or opt into V2 directly from the dashboard
All your existing configurations and setups will seamlessly carry over to V2 with no additional effort required on your end. We recommend beginning the transition to Reconciliation V2 to take advantage of the latest features.
Are you truly confident that every customer payment lands in your bank account? For most businesses, the path from transaction to settlement is a complex maze of different providers, inconsistent data, and manual checks—a system ripe for revenue leakage, reporting errors, and compliance risks
The reconciliation problem is further compounded by following factors:
Different acquirers provide files in inconsistent formats, with uneven settlement schedules, varied fee structures, and unpredictable reversals
Refunds, disputes, and partial settlements often follow fragmented workflows, while providers use different terminology for the same status, making it hard to maintain a clear audit trail
Banks further complicate matters with lump-sum deposits, posting delays, hidden charges, and partial or reversed credits
Our Reconciliation Module is built on a powerful, accounting-grade ledger, applying the precision of double-entry bookkeeping to your entire payment flow
Jump to:
Architecture
Example: E Commerce
Roadmap
Reconciliation V1
\
29 days ago
Was this helpful?

Reconciliation | Hyperswitch


webbworks.com
webcity.ca
webdream.com
webemaillist.com
webindia123.com
webinfo.fi
webjump.com
webl-3.br.inter.net
webmail.co.yu
webmail.co.za
webmails.com
webmailv.com
webpim.cc
webspawner.com
webstation.com
websurfer.co.za
webtopmail.com
webtribe.net
webtv.net
weedmail.com
weekonline.com
weirdness.com
westvirginia.usa.com
whale-mail.com
whipmail.com
who.net
whoever.com
wildcats.com
wildmail.com
williams.net.ar
winning.com
winningteam.com
winwinhosting.com
wisconsin.usa.com
witelcom.com
witty.com
wolverines.com
wooow.it
workmail.co.za
worldcrossing.com
worldemail.com
worldmedic.com
worldonline.de
wowmail.com
wp.pl
wprost.pl
wrongmail.com
wtonetwork.com
wurtele.net
www.com
www.consulcredit.it
wyoming.usa.com
x-mail.net
xasa.com
xfreehosting.com
xmail.net
xmsg.com
xnmsn.cn
xoom.com
xtra.co.nz
xuite.net
xpectmore.com
xrea.com
xsmail.com
xzapmail.com
y7mail.com
yahala.co.il
yaho.com
yalla.com.lb
ya.com
yeah.net
ya.ru
yahoomail.com
yam.com
yamal.info
yapost.com
yawmail.com
yebox.com
yehey.com
yellow-jackets.com
yellowstone.net
yenimail.com
yepmail.net
yifan.net
yopmail.com
your-mail.com
yours.com
yourwap.com
yyhmail.com
z11.com
z6.com
zednet.co.uk
zeeman.nl
ziplip.com
zipmail.com.br
zipmax.com
zmail.pt
zmail.ru
zona-andina.net
zonai.com
zoneview.net
zonnet.nl
zoho.com
zoomshare.com
zoznam.sk
zubee.com
zuvio.com
zwallet.com
zworg.com
zybermail.com
zzn.com
126.com
139.com
163.com
188.com
189.cn
263.net
9.cn
vip.126.com
vip.163.com
vip.188.com
vip.sina.com
vip.sohu.com
vip.sohu.net
vip.tom.com
vip.qq.com
vipsohu.net
clovermail.net
mail-on.us
chewiemail.com
offcolormail.com
powdermail.com
tightmail.com
toothandmail.com
tushmail.com
openmail.cc
expressmail.dk
4xn.de
5x2.de
5x2.me
aufdrogen.de
auf-steroide.de
besser-als-du.de
brainsurfer.de
chillaxer.de
cyberkriminell.de
danneben.so
freemailen.de
freemailn.de
ist-der-mann.de
ist-der-wahnsinn.de
ist-echt.so
istecht.so
ist-genialer.de
ist-schlauer.de
ist-supersexy.de
kann.so
mag-spam.net
mega-schlau.de
muss.so
nerd4life.de
ohne-drogen-gehts.net
on-steroids.de
scheint.so
staatsterrorist.de
super-gerissen.de
unendlich-schlau.de
vip-client.de
will-keinen-spam.de
zu-geil.de
rbox.me
rbox.co
tunome.com
acatperson.com
adogperson.com
all4theskins.com
allsportsrock.com
alwaysgrilling.com
alwaysinthekitchen.com
alwayswatchingmovies.com
alwayswatchingtv.com
asylum.com
basketball-email.com
beabookworm.com
beagolfer.com
beahealthnut.com
believeinliberty.com
bestcoolcars.com
bestjobcandidate.com
besure2vote.com
bigtimecatperson.com
bigtimedogperson.com
bigtimereader.com
bigtimesportsfan.com
blackvoices.com
capsfanatic.com
capshockeyfan.com
capsred.com
car-nut.net
cat-person.com
catpeoplerule.com
chat-with-me.com
cheatasrule.com
crazy4baseball.com
crazy4homeimprovement.com
crazy4mail.com
crazyaboutfilms.net
crazycarfan.com
crazyforemail.com
crazymoviefan.com
descriptivemail.com
differentmail.com
dog-person.com
dogpeoplerule.com
easydoesit.com
expertrenovator.com
expressivemail.com
fanaticos.com
fanofbooks.com
fanofcomputers.com
fanofcooking.com
fanoftheweb.com
fieldmail.com
fleetmail.com
focusedonprofits.com
focusedonreturns.com
futboladdict.com
games.com
getintobooks.com
hail2theskins.com
hitthepuck.com
i-dig-movies.com
i-love-restaurants.com
idigcomputers.com
idigelectronics.com
idigvideos.com
ilike2helpothers.com
ilike2invest.com
ilike2workout.com
ilikeelectronics.com
ilikeworkingout.com
ilovehomeprojects.com
iloveourteam.com
iloveworkingout.com
in2autos.net
interestedinthejob.com
intomotors.com
iwatchrealitytv.com
lemondrop.com
love2exercise.com
love2workout.com
lovefantasysports.com
lovetoexercise.com
luvfishing.com
luvgolfing.com
luvsoccer.com
mail4me.com
majorgolfer.com
majorshopaholic.com
majortechie.com
mcom.com
motor-nut.com
moviefan.com
mycapitalsmail.com
mycatiscool.com
myfantasyteamrules.com
myteamisbest.com
netbusiness.com
news-fanatic.com
newspaperfan.com
onlinevideosrock.com
realbookfan.com
realhealthnut.com
realitytvaddict.net
realitytvnut.com
reallyintomusic.com
realtravelfan.com
redskinscheer.com
redskinsfamily.com
redskinsfancentral.com
redskinshog.com
redskinsrule.com
redskinsspecialteams.com
redskinsultimatefan.com
scoutmail.com
skins4life.com
stargate2.com
stargateatlantis.com
stargatefanclub.com
stargatesg1.com
stargateu.com
switched.com
t-online.de
thegamefanatic.com
total-techie.com
totalfoodnut.com
totally-into-cooking.com
totallyintobaseball.com
totallyintobasketball.com
totallyintocooking.com
totallyintofootball.com
totallyintogolf.com
totallyintohockey.com
totallyintomusic.com
totallyintoreading.com
totallyintosports.com
totallyintotravel.com
totalmoviefan.com
travel2newplaces.com
tvchannelsurfer.com
ultimateredskinsfan.com
videogamesrock.com
volunteeringisawesome.com
wayintocomputers.com
whatmail.com
when.com
wild4music.com
wildaboutelectronics.com
workingaroundthehouse.com
workingonthehouse.com
writesoon.com
xmasmail.com
arab.ir
denmark.ir
egypt.ir
icq.ir
ir.ae
iraq.ir
ire.ir
ireland.ir
irr.ir
jpg.ir
ksa.ir
kuwait.ir
london.ir
paltalk.ir
spain.ir
sweden.ir
tokyo.ir
111mail.com
123iran.com
37.com
420email.com
4degreez.com
4-music-today.com
actingbiz.com
allhiphop.com
anatomicrock.com
animeone.com
asiancutes.com
a-teens.net
ausi.com
autoindia.com
autopm.com
barriolife.com
b-boy.com
beautifulboy.com
bgay.com
bicycledata.com
bicycling.com
bigheavyworld.com
bigmailbox.net
bikerheaven.net
bikermail.com
billssite.com
blackandchristian.com
blackcity.net
blackvault.com
bmxtrix.com
boarderzone.com
boatnerd.com
bolbox.com
bongmail.com
bowl.com
butch-femme.org
byke.com
calle22.com
cannabismail.com
catlovers.com
certifiedbitches.com
championboxing.com
chatway.com
chillymail.com
classprod.com
classycouples.com
congiu.net
coolshit.com
corpusmail.com
cyberunlimited.org
cycledata.com
darkfear.com
darkforces.com
dirtythird.com
dopefiends.com
draac.com
drakmail.net
dr-dre.com
dreamstop.com
egypt.net
emailfast.com
envirocitizen.com
escapeartist.com
ezsweeps.com
famous.as
farts.com
feelingnaughty.com
firemyst.com
freeonline.com
fudge.com
funkytimes.com
gamerssolution.com
gazabo.net
glittergrrrls.com
goatrance.com
goddess.com
gohip.com
gospelcity.com
gothicgirl.com
grapemail.net
greatautos.org
guy.com
haitisurf.com
happyhippo.com
hateinthebox.com
houseofhorrors.com
hugkiss.com
hullnumber.com
idunno4recipes.com
ihatenetscape.com
intimatefire.com
irow.com
jazzemail.com
juanitabynum.com
kanoodle.com
kickboxing.com
kidrock.com
kinkyemail.com
kool-things.com
latinabarbie.com
latinogreeks.com
leesville.com
loveemail.com
lowrider.com
lucky7lotto.net
madeniggaz.net
mailbomb.com
marillion.net
megarave.com
mofa.com
motley.com
music.com
musician.net
musicsites.com
netbroadcaster.com
netfingers.com
net-surf.com
nocharge.com
operationivy.com
paidoffers.net
pcbee.com
persian.com
petrofind.com
phunkybitches.com
pikaguam.com
pinkcity.net
pitbullmail.com
planetsmeg.com
poop.com
poormail.com
potsmokersnet.com
primetap.com
project420.com
prolife.net
puertoricowow.com
puppetweb.com
rapstar.com
rapworld.com
rastamall.com
ratedx.net
ravermail.com
relapsecult.com
remixer.com
rockeros.com
romance106fm.com
singalongcenter.com
sketchyfriends.com
slayerized.com
smartstocks.com
soulja-beatz.org
specialoperations.com
speedymail.net
spells.com
superbikeclub.com
superintendents.net
surfguiden.com
sweetwishes.com
tattoodesign.com
teamster.net
teenchatnow.com
the5thquarter.com
theblackmarket.com
tombstone.ws
troamail.org
u2tours.com
vitalogy.org
whatisthis.com
wrestlezone.com
abha.cc
agadir.cc
ahsa.ws
ajman.cc
ajman.us
ajman.ws
albaha.cc
algerie.cc
alriyadh.cc
amman.cc
aqaba.cc
arar.ws
aswan.cc
baalbeck.cc
bahraini.cc
banha.cc
bizerte.cc
blida.info
buraydah.cc
cameroon.cc
dhahran.cc
dhofar.cc
djibouti.cc
dominican.cc
eritrea.cc
falasteen.cc
fujairah.cc
fujairah.us
fujairah.ws
gabes.cc
gafsa.cc
giza.cc
guinea.cc
hamra.cc
hasakah.com
hebron.tv
homs.cc
ibra.cc
irbid.ws
ismailia.cc
jadida.cc
jadida.org
jerash.cc
jizan.cc
jouf.cc
kairouan.cc
karak.cc
khaimah.cc
khartoum.cc
khobar.cc
kuwaiti.tv
kyrgyzstan.cc
latakia.cc
lebanese.cc
lubnan.cc
lubnan.ws
madinah.cc
maghreb.cc
manama.cc
mansoura.tv
marrakesh.cc
mascara.ws
meknes.cc
muscat.tv
muscat.ws
nabeul.cc
nabeul.info
nablus.cc
nador.cc
najaf.cc
omani.ws
omdurman.cc
oran.cc
oued.info
oued.org
oujda.biz
oujda.cc
pakistani.ws
palmyra.cc
palmyra.ws
portsaid.cc
qassem.cc
quds.cc
rabat.cc
rafah.cc
ramallah.cc
safat.biz
safat.info
safat.us
safat.ws
salalah.cc
salmiya.biz
sanaa.cc
seeb.cc
sfax.ws
sharm.cc
sinai.cc
siria.cc
sousse.cc
sudanese.cc
suez.cc
tabouk.cc
tajikistan.cc
tangiers.cc
tanta.cc
tayef.cc
tetouan.cc
timor.cc
tunisian.cc
urdun.cc
yanbo.cc
yemeni.cc
yunus.cc
zagazig.cc
zambia.cc
5005.lv
a.org.ua
bmx.lv
company.org.ua
coolmail.ru
dino.lv
eclub.lv
e-mail.am
fit.lv
hacker.am
human.lv
iphon.biz
latchess.com
loveis.lv
lv-inter.net
pookmail.com
sexriga.lv


File: crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs
 Public structs: 18

use common_enums::enums;
use common_utils::types::MinorUnit;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{AccessToken, ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefreshTokenRouterData, RefundsResponseRouterData, ResponseRouterData},
    utils::{CardData as _, PaymentsAuthorizeRequestData, RouterData as OtherRouterData},
};

const CLIENT_CREDENTIALS: &str = "client_credentials";

pub struct MonerisRouterData<T> {
    pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> From<(MinorUnit, T)> for MonerisRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

pub mod auth_headers {
    pub const X_MERCHANT_ID: &str = "X-Merchant-Id";
    pub const API_VERSION: &str = "Api-Version";
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsRequest {
    idempotency_key: String,
    amount: Amount,
    payment_method: PaymentMethod,
    automatic_capture: bool,
}
#[derive(Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    currency: enums::Currency,
    amount: MinorUnit,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum PaymentMethod {
    Card(PaymentMethodCard),
    PaymentMethodId(PaymentMethodId),
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentMethodCard {
    payment_method_source: PaymentMethodSource,
    card: MonerisCard,
    store_payment_method: StorePaymentMethod,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PaymentMethodId {
    payment_method_source: PaymentMethodSource,
    payment_method_id: Secret<String>,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentMethodSource {
    Card,
    PaymentMethodId,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisCard {
    card_number: cards::CardNumber,
    expiry_month: Secret<i64>,
    expiry_year: Secret<i64>,
    card_security_code: Secret<String>,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StorePaymentMethod {
    DoNotStore,
    CardholderInitiated,
    MerchantInitiated,
}

impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &MonerisRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if item.router_data.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Card 3DS".to_string(),
                connector: "Moneris",
            })?
        };
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(ref req_card) => {
                let idempotency_key = uuid::Uuid::new_v4().to_string();
                let amount = Amount {
                    currency: item.router_data.request.currency,
                    amount: item.amount,
                };
                let payment_method = PaymentMethod::Card(PaymentMethodCard {
                    payment_method_source: PaymentMethodSource::Card,
                    card: MonerisCard {
                        card_number: req_card.card_number.clone(),
                        expiry_month: Secret::new(
                            req_card
                                .card_exp_month
                                .peek()
                                .parse::<i64>()
                                .change_context(errors::ConnectorError::ParsingFailed)?,
                        ),
                        expiry_year: Secret::new(
                            req_card
                                .get_expiry_year_4_digit()
                                .peek()
                                .parse::<i64>()
                                .change_context(errors::ConnectorError::ParsingFailed)?,
                        ),
                        card_security_code: req_card.card_cvc.clone(),
                    },
                    store_payment_method: if item
                        .router_data
                        .request
                        .is_customer_initiated_mandate_payment()
                    {
                        StorePaymentMethod::CardholderInitiated
                    } else {
                        StorePaymentMethod::DoNotStore
                    },
                });
                let automatic_capture = item.router_data.request.is_auto_capture()?;

                Ok(Self {
                    idempotency_key,
                    amount,
                    payment_method,
                    automatic_capture,
                })
            }
            PaymentMethodData::MandatePayment => {
                let idempotency_key = uuid::Uuid::new_v4().to_string();
                let amount = Amount {
                    currency: item.router_data.request.currency,
                    amount: item.amount,
                };
                let automatic_capture = item.router_data.request.is_auto_capture()?;
                let payment_method = PaymentMethod::PaymentMethodId(PaymentMethodId {
                    payment_method_source: PaymentMethodSource::PaymentMethodId,
                    payment_method_id: item
                        .router_data
                        .request
                        .connector_mandate_id()
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "connector_mandate_id",
                        })?
                        .into(),
                });
                Ok(Self {
                    idempotency_key,
                    amount,
                    payment_method,
                    automatic_capture,
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

pub struct MonerisAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
    pub(super) merchant_id: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for MonerisAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Ok(Self {
                client_id: key1.to_owned(),
                client_secret: api_key.to_owned(),
                merchant_id: api_secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct MonerisAuthRequest {
    client_id: Secret<String>,
    client_secret: Secret<String>,
    grant_type: String,
}

impl TryFrom<&RefreshTokenRouterData> for MonerisAuthRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefreshTokenRouterData) -> Result<Self, Self::Error> {
        let auth = MonerisAuthType::try_from(&item.connector_auth_type)?;
        Ok(Self {
            client_id: auth.client_id.clone(),
            client_secret: auth.client_secret.clone(),
            grant_type: CLIENT_CREDENTIALS.to_string(),
        })
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MonerisAuthResponse {
    access_token: Secret<String>,
    token_type: String,
    expires_in: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, MonerisAuthResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, MonerisAuthResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(AccessToken {
                token: item.response.access_token,
                expires: item
                    .response
                    .expires_in
                    .parse::<i64>()
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MonerisPaymentStatus {
    Succeeded,
    #[default]
    Processing,
    Canceled,
    Declined,
    DeclinedRetry,
    Authorized,
}

impl From<MonerisPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: MonerisPaymentStatus) -> Self {
        match item {
            MonerisPaymentStatus::Succeeded => Self::Charged,
            MonerisPaymentStatus::Authorized => Self::Authorized,
            MonerisPaymentStatus::Canceled => Self::Voided,
            MonerisPaymentStatus::Declined | MonerisPaymentStatus::DeclinedRetry => Self::Failure,
            MonerisPaymentStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsResponse {
    payment_status: MonerisPaymentStatus,
    payment_id: String,
    payment_method: MonerisPaymentMethodData,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentMethodData {
    payment_method_id: Secret<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, MonerisPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, MonerisPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.payment_status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payment_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(Some(MandateReference {
                    connector_mandate_id: Some(
                        item.response
                            .payment_method
                            .payment_method_id
                            .peek()
                            .to_string(),
                    ),
                    payment_method_id: None,
                    mandate_metadata: None,
                    connector_mandate_request_reference_id: None,
                })),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.payment_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MonerisPaymentsCaptureRequest {
    amount: Amount,
    idempotency_key: String,
}

impl TryFrom<&MonerisRouterData<&PaymentsCaptureRouterData>> for MonerisPaymentsCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &MonerisRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let amount = Amount {
            currency: item.router_data.request.currency,
            amount: item.amount,
        };
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        Ok(Self {
            amount,
            idempotency_key,
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MonerisCancelRequest {
    idempotency_key: String,
    reason: Option<String>,
}

impl TryFrom<&PaymentsCancelRouterData> for MonerisCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        let reason = item.request.cancellation_reason.clone();
        Ok(Self {
            idempotency_key,
            reason,
        })
    }
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MonerisRefundRequest {
    pub refund_amount: Amount,
    pub idempotency_key: String,
    pub reason: Option<String>,
    pub payment_id: String,
}

impl<F> TryFrom<&MonerisRouterData<&RefundsRouterData<F>>> for MonerisRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &MonerisRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let refund_amount = Amount {
            currency: item.router_data.request.currency,
            amount: item.amount,
        };
        let idempotency_key = uuid::Uuid::new_v4().to_string();
        let reason = item.router_data.request.reason.clone();
        let payment_id = item.router_data.request.connector_transaction_id.clone();
        Ok(Self {
            refund_amount,
            idempotency_key,
            reason,
            payment_id,
        })
    }
}

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
    Succeeded,
    #[default]
    Processing,
    Declined,
    DeclinedRetry,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Declined | RefundStatus::DeclinedRetry => Self::Failure,
            RefundStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    refund_id: String,
    refund_status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.refund_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.refund_status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.refund_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.refund_status),
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisErrorResponse {
    pub status: u16,
    pub category: String,
    pub title: String,
    pub errors: Option<Vec<MonerisError>>,
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MonerisError {
    pub reason_code: String,
    pub parameter_name: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct MonerisAuthErrorResponse {
    pub error: String,
    pub error_description: Option<String>,
}


pub struct LastPaymentError {
    charge: Option<String>,
    code: Option<String>,
    decline_code: Option<String>,
    message: String,
    param: Option<String>,
    payment_method: StripePaymentMethod,
    #[serde(rename = "type")]
    error_type: String,
}


impl Customer {
    /// Get the unique identifier of Customer
    #[cfg(feature = "v1")]
    pub fn get_id(&self) -> &id_type::CustomerId {
        &self.customer_id
    }

    /// Get the global identifier of Customer
    #[cfg(feature = "v2")]
    pub fn get_id(&self) -> &id_type::GlobalCustomerId {
        &self.id
    }

    /// Get the connector customer ID for the specified connector label, if present
    #[cfg(feature = "v1")]
    pub fn get_connector_customer_id(&self, connector_label: &str) -> Option<&str> {
        use masking::PeekInterface;

        self.connector_customer
            .as_ref()
            .and_then(|connector_customer_value| {
                connector_customer_value.peek().get(connector_label)
            })
            .and_then(|connector_customer| connector_customer.as_str())
    }

    /// Get the connector customer ID for the specified merchant connector account ID, if present
    #[cfg(feature = "v2")]
    pub fn get_connector_customer_id(
        &self,
        merchant_connector_account: &MerchantConnectorAccountTypeDetails,
    ) -> Option<&str> {
        match merchant_connector_account {
            MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(account) => {
                let connector_account_id = account.get_id();
                self.connector_customer
                    .as_ref()?
                    .get(&connector_account_id)
                    .map(|connector_customer_id| connector_customer_id.as_str())
            }
            MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None,
        }
    }
}


pub struct NoKey {}


pub struct TotalPaymentMethodCountResponse {
    /// total count of payment methods under the merchant
    pub total_count: i64,
}


pub(crate) async fn get_webhook_delivery_retry_schedule_time(
    db: &dyn StorageInterface,
    merchant_id: &id_type::MerchantId,
    retry_count: i32,
) -> Option<time::PrimitiveDateTime> {
    let key = "pt_mapping_outgoing_webhooks";

    let result = db
        .find_config_by_key(key)
        .await
        .map(|value| value.config)
        .and_then(|config| {
            config
                .parse_struct("OutgoingWebhookRetryProcessTrackerMapping")
                .change_context(errors::StorageError::DeserializationFailed)
        });
    let mapping = result.map_or_else(
        |error| {
            if error.current_context().is_db_not_found() {
                logger::debug!("Outgoing webhooks retry config `{key}` not found, ignoring");
            } else {
                logger::error!(
                    ?error,
                    "Failed to read outgoing webhooks retry config `{key}`"
                );
            }
            process_data::OutgoingWebhookRetryProcessTrackerMapping::default()
        },
        |mapping| {
            logger::debug!(?mapping, "Using custom outgoing webhooks retry config");
            mapping
        },
    );

    let time_delta = scheduler_utils::get_outgoing_webhook_retry_schedule_time(
        mapping,
        merchant_id,
        retry_count,
    );

    scheduler_utils::get_time_from_delta(time_delta)
}


pub fn validate_for_valid_refunds(
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    connector: api_models::enums::Connector,
) -> RouterResult<()> {
    let payment_method = payment_attempt
        .payment_method
        .as_ref()
        .get_required_value("payment_method")?;

    match payment_method {
        diesel_models::enums::PaymentMethod::PayLater
        | diesel_models::enums::PaymentMethod::Wallet => {
            let payment_method_type = payment_attempt
                .payment_method_type
                .get_required_value("payment_method_type")?;

            utils::when(
                matches!(
                    (connector, payment_method_type),
                    (
                        api_models::enums::Connector::Braintree,
                        diesel_models::enums::PaymentMethodType::Paypal,
                    )
                ),
                || {
                    Err(errors::ApiErrorResponse::RefundNotPossible {
                        connector: connector.to_string(),
                    }
                    .into())
                },
            )
        }
        _ => Ok(()),
    }
}


pub struct AuthorizedPaymentMethod {
    #[serde(rename = "type")]
    payment_type: String,
}


pub struct Blocklist;


File: crates/external_services/src/aws_kms/implementers.rs

//! Trait implementations for aws kms client

use common_utils::errors::CustomResult;
use error_stack::ResultExt;
use hyperswitch_interfaces::{
    encryption_interface::{EncryptionError, EncryptionManagementInterface},
    secrets_interface::{SecretManagementInterface, SecretsManagementError},
};
use masking::{PeekInterface, Secret};

use crate::aws_kms::core::AwsKmsClient;

#[async_trait::async_trait]
impl EncryptionManagementInterface for AwsKmsClient {
    async fn encrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
        self.encrypt(input)
            .await
            .change_context(EncryptionError::EncryptionFailed)
            .map(|val| val.into_bytes())
    }

    async fn decrypt(&self, input: &[u8]) -> CustomResult<Vec<u8>, EncryptionError> {
        self.decrypt(input)
            .await
            .change_context(EncryptionError::DecryptionFailed)
            .map(|val| val.into_bytes())
    }
}

#[async_trait::async_trait]
impl SecretManagementInterface for AwsKmsClient {
    async fn get_secret(
        &self,
        input: Secret<String>,
    ) -> CustomResult<Secret<String>, SecretsManagementError> {
        self.decrypt(input.peek())
            .await
            .change_context(SecretsManagementError::FetchSecretFailed)
            .map(Into::into)
    }
}


pub async fn update(
        conn: &PgPooledConn,
        user_id: Option<String>,
        merchant_id: id_type::MerchantId,
        org_id: id_type::OrganizationId,
        data_key: enums::DashboardMetadata,
        dashboard_metadata_update: DashboardMetadataUpdate,
    ) -> StorageResult<Self> {
        let predicate = dsl::merchant_id
            .eq(merchant_id.to_owned())
            .and(dsl::org_id.eq(org_id.to_owned()))
            .and(dsl::data_key.eq(data_key.to_owned()));

        if let Some(uid) = user_id {
            generics::generic_update_with_unique_predicate_get_result::<
                <Self as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                predicate.and(dsl::user_id.eq(uid)),
                DashboardMetadataUpdateInternal::from(dashboard_metadata_update),
            )
            .await
        } else {
            generics::generic_update_with_unique_predicate_get_result::<
                <Self as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                predicate.and(dsl::user_id.is_null()),
                DashboardMetadataUpdateInternal::from(dashboard_metadata_update),
            )
            .await
        }
    }


impl RoutableConnectorChoiceWithStatus {
    pub fn new(routable_connector_choice: RoutableConnectorChoice, status: bool) -> Self {
        Self {
            routable_connector_choice,
            status,
        }
    }
}


OpenAPI Block Path: components.schemas.PaymentChannel
{
  "oneOf": [
    {
      "type": "string",
      "enum": [
        "ecommerce"
      ]
    },
    {
      "type": "string",
      "enum": [
        "mail_order"
      ]
    },
    {
      "type": "string",
      "enum": [
        "telephone_order"
      ]
    },
    {
      "type": "object",
      "required": [
        "other"
      ],
      "properties": {
        "other": {
          "type": "string"
        }
      }
    }
  ],
  "description": "Describes the channel through which the payment was initiated."
}


pub struct DataPaymentCaptureRequest {
    pub amount: MinorUnit,
    pub currency: enums::Currency,
    pub refno: String,
}


pub struct AdminApiAuth;


OpenAPI Block Path: paths."/payments/{payment_id}/cancel"
{
  "post": {
    "tags": [
      "Payments"
    ],
    "summary": "Payments - Cancel",
    "description": "A Payment could can be cancelled when it is in one of these statuses: `requires_payment_method`, `requires_capture`, `requires_confirmation`, `requires_customer_action`.",
    "operationId": "Cancel a Payment",
    "parameters": [
      {
        "name": "payment_id",
        "in": "path",
        "description": "The identifier for payment",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    ],
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PaymentsCancelRequest"
          },
          "examples": {
            "Cancel the payment with cancellation reason": {
              "value": {
                "cancellation_reason": "requested_by_customer"
              }
            },
            "Cancel the payment with minimal fields": {
              "value": {}
            }
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Payment canceled"
      },
      "400": {
        "description": "Missing mandatory fields",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/GenericErrorResponseOpenApi"
            }
          }
        }
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  }
}


pub struct GetSdkEventMetricRequest {
    pub time_series: Option<TimeSeries>,
    pub time_range: TimeRange,
    #[serde(default)]
    pub group_by_names: Vec<SdkEventDimensions>,
    #[serde(default)]
    pub filters: sdk_events::SdkEventFilters,
    pub metrics: HashSet<SdkEventMetrics>,
    #[serde(default)]
    pub delta: bool,
}


pub struct TrustlyInfo {}


            card_type: None,
            card_issuing_country: None,
            bank_code: None,
            nick_name: None,
            eci: None,
        }
    }
}

impl ForeignFrom<common_types::three_ds_decision_rule_engine::ThreeDSDecision>
    for common_enums::AuthenticationType
{
    fn foreign_from(
        three_ds_decision: common_types::three_ds_decision_rule_engine::ThreeDSDecision,
    ) -> Self {
        match three_ds_decision {
            common_types::three_ds_decision_rule_engine::ThreeDSDecision::NoThreeDs => Self::NoThreeDs,
            common_types::three_ds_decision_rule_engine::ThreeDSDecision::ChallengeRequested
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::ChallengePreferred
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::ThreeDsExemptionRequestedTra
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::ThreeDsExemptionRequestedLowValue
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::IssuerThreeDsExemptionRequested => Self::ThreeDs,
        }
    }
}

impl ForeignFrom<common_types::three_ds_decision_rule_engine::ThreeDSDecision>
    for Option<common_enums::ScaExemptionType>
{
    fn foreign_from(
        three_ds_decision: common_types::three_ds_decision_rule_engine::ThreeDSDecision,
    ) -> Self {
        match three_ds_decision {
            common_types::three_ds_decision_rule_engine::ThreeDSDecision::ThreeDsExemptionRequestedTra => {
                Some(common_enums::ScaExemptionType::TransactionRiskAnalysis)
            }
            common_types::three_ds_decision_rule_engine::ThreeDSDecision::ThreeDsExemptionRequestedLowValue => {
                Some(common_enums::ScaExemptionType::LowValue)
            }
            common_types::three_ds_decision_rule_engine::ThreeDSDecision::NoThreeDs
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::ChallengeRequested
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::ChallengePreferred
            | common_types::three_ds_decision_rule_engine::ThreeDSDecision::IssuerThreeDsExemptionRequested => {
                None
            }
        }
    }
}


pub struct PaystackErrorResponse {
    pub status: bool,
    pub message: String,
    pub data: Option<serde_json::Value>,
    pub meta: serde_json::Value,
    pub code: String,
}


pub async fn get_sync_process_schedule_time(
    db: &dyn StorageInterface,
    connector: &str,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_count: i32,
) -> Result<Option<time::PrimitiveDateTime>, errors::ProcessTrackerError> {
    let mapping: common_utils::errors::CustomResult<
        process_data::ConnectorPTMapping,
        errors::StorageError,
    > = db
        .find_config_by_key(&format!("pt_mapping_{connector}"))
        .await
        .map(|value| value.config)
        .and_then(|config| {
            config
                .parse_struct("ConnectorPTMapping")
                .change_context(errors::StorageError::DeserializationFailed)
        });
    let mapping = match mapping {
        Ok(x) => x,
        Err(error) => {
            logger::info!(?error, "Redis Mapping Error");
            process_data::ConnectorPTMapping::default()
        }
    };
    let time_delta = scheduler_utils::get_schedule_time(mapping, merchant_id, retry_count);

    Ok(scheduler_utils::get_time_from_delta(time_delta))
}


impl api::MandateSetup for Katapult {}


pub struct Tokenization {
    pub id: id_type::GlobalTokenId,
    pub merchant_id: id_type::MerchantId,
    pub customer_id: id_type::GlobalCustomerId,
    pub created_at: PrimitiveDateTime,
    pub updated_at: PrimitiveDateTime,
    pub locker_id: String,
    pub flag: common_enums::enums::TokenizationFlag,
    pub version: common_enums::enums::ApiVersion,
}


File: crates/hyperswitch_domain_models/src/router_response_types/disputes.rs
 Public structs: 5

#[derive(Default, Clone, Debug)]
pub struct AcceptDisputeResponse {
    pub dispute_status: api_models::enums::DisputeStatus,
    pub connector_status: Option<String>,
}

#[derive(Default, Clone, Debug)]
pub struct SubmitEvidenceResponse {
    pub dispute_status: api_models::enums::DisputeStatus,
    pub connector_status: Option<String>,
}

#[derive(Default, Debug, Clone)]
pub struct DefendDisputeResponse {
    pub dispute_status: api_models::enums::DisputeStatus,
    pub connector_status: Option<String>,
}

pub struct FileInfo {
    pub file_data: Option<Vec<u8>>,
    pub provider_file_id: Option<String>,
    pub file_type: Option<String>,
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct DisputeSyncResponse {
    pub object_reference_id: api_models::webhooks::ObjectReferenceId,
    pub amount: common_utils::types::StringMinorUnit,
    pub currency: common_enums::enums::Currency,
    pub dispute_stage: common_enums::enums::DisputeStage,
    pub dispute_status: api_models::enums::DisputeStatus,
    pub connector_status: String,
    pub connector_dispute_id: String,
    pub connector_reason: Option<String>,
    pub connector_reason_code: Option<String>,
    pub challenge_required_by: Option<time::PrimitiveDateTime>,
    pub created_at: Option<time::PrimitiveDateTime>,
    pub updated_at: Option<time::PrimitiveDateTime>,
}

pub type FetchDisputesResponse = Vec<DisputeSyncResponse>;


File: crates/analytics/src/payment_intents/filters.rs
 Public functions: 1
 Public structs: 1

use api_models::analytics::{payment_intents::PaymentIntentDimensions, Granularity, TimeRange};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums::{AuthenticationType, Currency, IntentStatus};
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
    types::{
        AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, FiltersError, FiltersResult,
        LoadRow,
    },
};

pub trait PaymentIntentFilterAnalytics: LoadRow<PaymentIntentFilterRow> {}

pub async fn get_payment_intent_filter_for_dimension<T>(
    dimension: PaymentIntentDimensions,
    merchant_id: &common_utils::id_type::MerchantId,
    time_range: &TimeRange,
    pool: &T,
) -> FiltersResult<Vec<PaymentIntentFilterRow>>
where
    T: AnalyticsDataSource + PaymentIntentFilterAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::PaymentIntent);

    query_builder.add_select_column(dimension).switch()?;
    time_range
        .set_filter_clause(&mut query_builder)
        .attach_printable("Error filtering time range")
        .switch()?;

    query_builder
        .add_filter_clause("merchant_id", merchant_id)
        .switch()?;

    query_builder.set_distinct();

    query_builder
        .execute_query::<PaymentIntentFilterRow, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}

#[derive(Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
pub struct PaymentIntentFilterRow {
    pub status: Option<DBEnumWrapper<IntentStatus>>,
    pub currency: Option<DBEnumWrapper<Currency>>,
    pub profile_id: Option<String>,
    pub connector: Option<String>,
    pub authentication_type: Option<DBEnumWrapper<AuthenticationType>>,
    pub payment_method: Option<String>,
    pub payment_method_type: Option<String>,
    pub card_network: Option<String>,
    pub merchant_id: Option<String>,
    pub card_last_4: Option<String>,
    pub card_issuer: Option<String>,
    pub error_reason: Option<String>,
    pub customer_id: Option<String>,
}


impl api::PaymentVoid for Paytm {}


impl HubspotRequest {
    pub fn new(
        country: String,
        hubspot_form_id: String,
        firstname: Secret<String>,
        email: Secret<String>,
        company_name: String,
        website: String,
    ) -> Self {
        Self {
            use_hubspot: true,
            country,
            hubspot_form_id,
            firstname,
            email,
            company_name,
            lead_source: HUBSPOT_LEAD_SOURCE.to_string(),
            website,
            ..Default::default()
        }
    }
}


pub async fn update_role(
    state: SessionState,
    user_from_token: UserFromToken,
    req: role_api::UpdateRoleRequest,
    role_id: &str,
) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
    let role_name = req.role_name.map(RoleName::new).transpose()?;

    let role_info = roles::RoleInfo::from_role_id_in_lineage(
        &state,
        role_id,
        &user_from_token.merchant_id,
        &user_from_token.org_id,
        &user_from_token.profile_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InvalidRoleOperation)?;

    let user_role_info = user_from_token.get_role_info_from_db(&state).await?;

    let requested_entity_from_role_scope = EntityType::from(role_info.get_scope());
    let requested_role_entity_type = role_info.get_entity_type();
    let max_from_scope_and_entity =
        cmp::max(requested_entity_from_role_scope, requested_role_entity_type);

    if user_role_info.get_entity_type() < max_from_scope_and_entity {
        return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
            "{} is trying to update of scope {} and of type {}",
            user_role_info.get_entity_type(),
            requested_entity_from_role_scope,
            requested_role_entity_type
        ));
    }

    if let Some(ref role_name) = role_name {
        utils::user_role::validate_role_name(
            &state,
            role_name,
            &user_from_token.merchant_id,
            &user_from_token.org_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.profile_id,
            &role_info.get_entity_type(),
        )
        .await?;
    }

    if let Some(ref groups) = req.groups {
        utils::user_role::validate_role_groups(groups)?;
    }

    let updated_role = state
        .global_store
        .update_role_by_role_id(
            role_id,
            RoleUpdate::UpdateDetails {
                groups: req.groups,
                role_name: role_name.map(RoleName::get_role_name),
                last_modified_at: common_utils::date_time::now(),
                last_modified_by: user_from_token.user_id,
            },
        )
        .await
        .to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;

    blacklist::insert_role_in_blacklist(&state, role_id).await?;

    Ok(ApplicationResponse::Json(
        role_api::RoleInfoWithGroupsResponse {
            groups: updated_role.groups,
            role_id: updated_role.role_id,
            role_name: updated_role.role_name,
            role_scope: updated_role.scope,
            entity_type: updated_role.entity_type,
        },
    ))
}


pub trait CustomerAddress {
    async fn get_address_update(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
        merchant_id: id_type::MerchantId,
    ) -> CustomResult<storage::AddressUpdate, common_utils::errors::CryptoError>;

    async fn get_domain_address(
        &self,
        state: &SessionState,
        address_details: payments::AddressDetails,
        merchant_id: &id_type::MerchantId,
        customer_id: &id_type::CustomerId,
        key: &[u8],
        storage_scheme: storage::enums::MerchantStorageScheme,
    ) -> CustomResult<domain::CustomerAddress, common_utils::errors::CryptoError>;
}


pub struct AmazonpayFinalizeRequest {
    charge_amount: ChargeAmount,
    shipping_address: AddressDetails,
    payment_intent: PaymentIntent,
}


pub async fn make_payment_attempt(
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
        organization_id: &common_utils::id_type::OrganizationId,
        money: (api::Amount, enums::Currency)


pub fn get_valid_connectors_for_rule(rule: JsValue) -> JsResult {
    let seed_data = SEED_DATA.get().ok_or("Data not seeded").err_to_js()?;

    let rule: ast::Rule<ConnectorSelection> = serde_wasm_bindgen::from_value(rule)?;
    let dir_rule = ast::lowering::lower_rule(rule).err_to_js()?;
    let mut valid_connectors: Vec<(ast::ConnectorChoice, dir::DirValue)> = seed_data
        .connectors
        .iter()
        .cloned()
        .map(|choice| (choice.clone(), dir::DirValue::Connector(Box::new(choice))))
        .collect();
    let mut invalid_connectors: HashSet<ast::ConnectorChoice> = HashSet::new();

    let mut ctx_manager = state_machine::RuleContextManager::new(&dir_rule, &[]);

    let dummy_meta = HashMap::new();

    // For every conjunctive context in the Rule, verify validity of all still-valid connectors
    // using the knowledge graph
    while let Some(ctx) = ctx_manager.advance_mut().err_to_js()? {
        // Standalone conjunctive context analysis to ensure the context itself is valid before
        // checking it against merchant's connectors
        seed_data
            .cgraph
            .perform_context_analysis(
                ctx,
                &mut hyperswitch_constraint_graph::Memoization::new(),
                None,
            )
            .err_to_js()?;

        // Update conjunctive context and run analysis on all of merchant's connectors.
        for (conn, choice) in &valid_connectors {
            if invalid_connectors.contains(conn) {
                continue;
            }

            let ctx_val = dssa::types::ContextValue::assertion(choice, &dummy_meta);
            ctx.push(ctx_val);
            let analysis_result = seed_data.cgraph.perform_context_analysis(
                ctx,
                &mut hyperswitch_constraint_graph::Memoization::new(),
                None,
            );
            if analysis_result.is_err() {
                invalid_connectors.insert(conn.clone());
            }
            ctx.pop();
        }
    }

    valid_connectors.retain(|(k, _)| !invalid_connectors.contains(k));

    let valid_connectors: Vec<ast::ConnectorChoice> =
        valid_connectors.into_iter().map(|c| c.0).collect();

    Ok(serde_wasm_bindgen::to_value(&valid_connectors)?)
}


pub struct PaymentRevenueRecoveryMetadata {
    /// Total number of billing connector + recovery retries for a payment intent.
    pub total_retry_count: u16,
    /// Flag for the payment connector's call
    pub payment_connector_transmission: PaymentConnectorTransmission,
    /// Billing Connector Id to update the invoices
    pub billing_connector_id: id_type::MerchantConnectorAccountId,
    /// Payment Connector Id to retry the payments
    pub active_attempt_payment_connector_id: id_type::MerchantConnectorAccountId,
    /// Billing Connector Payment Details
    pub billing_connector_payment_details: BillingConnectorPaymentDetails,
    ///Payment Method Type
    pub payment_method_type: common_enums::enums::PaymentMethod,
    /// PaymentMethod Subtype
    pub payment_method_subtype: common_enums::enums::PaymentMethodType,
    /// The name of the payment connector through which the payment attempt was made.
    pub connector: common_enums::connector_enums::Connector,
    /// Time at which next invoice will be created
    pub invoice_next_billing_time: Option<time::PrimitiveDateTime>,
    /// Time at which invoice started
    pub invoice_billing_started_at_time: Option<time::PrimitiveDateTime>,
    /// Extra Payment Method Details that are needed to be stored
    pub billing_connector_payment_method_details: Option<BillingConnectorPaymentMethodDetails>,
    /// First Payment Attempt Payment Gateway Error Code
    pub first_payment_attempt_pg_error_code: Option<String>,
    /// First Payment Attempt Network Error Code
    pub first_payment_attempt_network_decline_code: Option<String>,
    /// First Payment Attempt Network Advice Code
    pub first_payment_attempt_network_advice_code: Option<String>,
}


impl Engine for Kv2 {
    type ReturnType<'b, T: 'b> =
        Pin<Box<dyn Future<Output = error_stack::Result<T, HashiCorpError>> + Send + 'b>>;
    fn read(client: &HashiCorpVault, location: String) -> Self::ReturnType<'_, String> {
        Box::pin(async move {
            let mut split = location.split(':');
            let mount = split.next().ok_or(HashiCorpError::IncompleteData)?;
            let path = split.next().ok_or(HashiCorpError::IncompleteData)?;
            let key = split.next().unwrap_or("value");

            let mut output =
                vaultrs::kv2::read::<HashMap<String, String>>(&client.client, mount, path)
                    .await
                    .map_err(Into::<Report<_>>::into)
                    .change_context(HashiCorpError::FetchFailed)?;

            Ok(output.remove(key).ok_or(HashiCorpError::ParseError)?)
        })
    }
}


pub struct ReconToken {
    pub user_id: String,
    pub merchant_id: id_type::MerchantId,
    pub role_id: String,
    pub exp: u64,
    pub org_id: id_type::OrganizationId,
    pub profile_id: id_type::ProfileId,
    pub tenant_id: Option<id_type::TenantId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acl: Option<String>,
}


pub fn parse_routing_algorithm(&self) -> CustomResult<(), errors::ParsingError> {
        match self.routing_algorithm {
            Some(ref routing_algorithm) => {
                let _: routing::StaticRoutingAlgorithm = routing_algorithm
                    .clone()
                    .parse_value("StaticRoutingAlgorithm")?;
                Ok(())
            }
            None => Ok(()),
        }
    }


pub struct Vault;


impl OrganizationNew {
    pub fn new(
        id: id_type::OrganizationId,
        organization_type: common_enums::OrganizationType,
        organization_name: Option<String>,
    ) -> Self {
        Self {
            org_id: id.clone(),
            org_name: organization_name.clone(),
            id,
            organization_name,
            organization_details: None,
            metadata: None,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            version: common_types::consts::API_VERSION,
            organization_type,
            platform_merchant_id: None,
        }
    }
}


pub fn set_payment_method_response(
        self,
        payment_method: &'a domain::PaymentMethod,
    ) -> NetworkTokenizationBuilder<'a, PaymentMethodCreated> {
        let card_detail_from_locker = self.card.as_ref().map(|card| api::CardDetailFromLocker {
            scheme: None,
            issuer_country: card.card_issuing_country.clone(),
            last4_digits: Some(card.card_number.clone().get_last4()),
            card_number: None,
            expiry_month: Some(card.card_exp_month.clone().clone()),
            expiry_year: Some(card.card_exp_year.clone().clone()),
            card_token: None,
            card_holder_name: card.card_holder_name.clone(),
            card_fingerprint: None,
            nick_name: card.nick_name.clone(),
            card_network: card.card_network.clone(),
            card_isin: Some(card.card_number.clone().get_card_isin()),
            card_issuer: card.card_issuer.clone(),
            card_type: card.card_type.clone(),
            saved_to_locker: true,
        });
        let payment_method_response = api::PaymentMethodResponse {
            merchant_id: payment_method.merchant_id.clone(),
            customer_id: Some(payment_method.customer_id.clone()),
            payment_method_id: payment_method.payment_method_id.clone(),
            payment_method: payment_method.payment_method,
            payment_method_type: payment_method.payment_method_type,
            card: card_detail_from_locker,
            recurring_enabled: Some(true),
            installment_payment_enabled: Some(false),
            metadata: payment_method.metadata.clone(),
            created: Some(payment_method.created_at),
            last_used_at: Some(payment_method.last_used_at),
            client_secret: payment_method.client_secret.clone(),
            bank_transfer: None,
            payment_experience: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            payment_method_response: Some(payment_method_response),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }