        json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
    ) -> impl Responder {
        // safety: This shouldn't panic owing to the data type
        #[allow(clippy::expect_used)]
        let payload = json_payload
            .into_inner()
            .to_vec()
            .pop()
            .expect("Couldn't get GetAuthEventMetricRequest");
        let flow = AnalyticsFlow::GetAuthMetrics;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            payload,
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };

                analytics::auth_events::get_metrics(&state.pool, &auth, req)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    /// # Panics
    ///
    /// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
    pub async fn get_profile_auth_event_metrics(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
    ) -> impl Responder {
        // safety: This shouldn't panic owing to the data type
        #[allow(clippy::expect_used)]
        let payload = json_payload
            .into_inner()
            .to_vec()
            .pop()
            .expect("Couldn't get GetAuthEventMetricRequest");
        let flow = AnalyticsFlow::GetAuthMetrics;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            payload,
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let auth: AuthInfo = AuthInfo::ProfileLevel {
                    org_id: org_id.clone(),
                    merchant_id: merchant_id.clone(),
                    profile_ids: vec![profile_id.clone()],
                };
                analytics::auth_events::get_metrics(&state.pool, &auth, req)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    /// # Panics
    ///
    /// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element.
    pub async fn get_org_auth_event_metrics(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<[GetAuthEventMetricRequest; 1]>,
    ) -> impl Responder {
        // safety: This shouldn't panic owing to the data type
        #[allow(clippy::expect_used)]
        let payload = json_payload
            .into_inner()
            .to_vec()
            .pop()
            .expect("Couldn't get GetAuthEventMetricRequest");
        let flow = AnalyticsFlow::GetAuthMetrics;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            payload,
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let auth: AuthInfo = AuthInfo::OrgLevel {
                    org_id: org_id.clone(),
                };
                analytics::auth_events::get_metrics(&state.pool, &auth, req)
                    .await
                    .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::OrganizationAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_merchant_payment_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetPaymentFiltersRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetPaymentFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };
                analytics::payments::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_merchant_auth_events_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetAuthEventFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetAuthEventFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();

                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };
                analytics::auth_events::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_org_auth_events_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetAuthEventFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetAuthEventFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let auth: AuthInfo = AuthInfo::OrgLevel {
                    org_id: org_id.clone(),
                };

                analytics::auth_events::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::OrganizationAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_profile_auth_events_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetAuthEventFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetAuthEventFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;

                let auth: AuthInfo = AuthInfo::ProfileLevel {
                    org_id: org_id.clone(),
                    merchant_id: merchant_id.clone(),
                    profile_ids: vec![profile_id.clone()],
                };
                analytics::auth_events::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_org_payment_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetPaymentFiltersRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetPaymentFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let auth: AuthInfo = AuthInfo::OrgLevel {
                    org_id: org_id.clone(),
                };
                analytics::payments::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::OrganizationAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_profile_payment_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetPaymentFiltersRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetPaymentFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let auth: AuthInfo = AuthInfo::ProfileLevel {
                    org_id: org_id.clone(),
                    merchant_id: merchant_id.clone(),
                    profile_ids: vec![profile_id.clone()],
                };
                analytics::payments::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_payment_intents_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetPaymentIntentFiltersRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetPaymentIntentFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                analytics::payment_intents::get_filters(
                    &state.pool,
                    req,
                    auth.merchant_account.get_id(),
                )
                .await
                .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::MerchantAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_merchant_refund_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetRefundFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetRefundFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req: GetRefundFilterRequest, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };
                analytics::refunds::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_org_refund_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetRefundFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetRefundFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req: GetRefundFilterRequest, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let auth: AuthInfo = AuthInfo::OrgLevel {
                    org_id: org_id.clone(),
                };
                analytics::refunds::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            auth::auth_type(
                &auth::PlatformOrgAdminAuth {
                    is_admin_auth_allowed: false,
                    organization_id: None,
                },
                &auth::JWTAuth {
                    permission: Permission::OrganizationAnalyticsRead,
                },
                req.headers(),
            ),
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_profile_refund_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetRefundFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetRefundFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req: GetRefundFilterRequest, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let auth: AuthInfo = AuthInfo::ProfileLevel {
                    org_id: org_id.clone(),
                    merchant_id: merchant_id.clone(),
                    profile_ids: vec![profile_id.clone()],
                };
                analytics::refunds::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_frm_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetFrmFilterRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetFrmFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req: GetFrmFilterRequest, _| async move {
                analytics::frm::get_filters(&state.pool, req, auth.merchant_account.get_id())
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_sdk_event_filters(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<GetSdkEventFiltersRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetSdkEventFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                analytics::sdk_events::get_filters(
                    &state.pool,
                    req,
                    &auth.merchant_account.publishable_key,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_profile_api_events(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Query<api_models::analytics::api_event::ApiLogsRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetApiEvents;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let payment_id = match req.query_param.clone() {
                    QueryType::Payment { payment_id } => payment_id,
                    QueryType::Refund { payment_id, .. } => payment_id,
                    QueryType::Dispute { payment_id, .. } => payment_id,
                };
                utils::check_if_profile_id_is_present_in_payment_intent(payment_id, &state, &auth)
                    .await
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                api_events_core(&state.pool, req, auth.merchant_account.get_id())
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    pub async fn get_profile_outgoing_webhook_events(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Query<
            api_models::analytics::outgoing_webhook_event::OutgoingWebhookLogsRequest,
        >,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetOutgoingWebhookEvents;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                utils::check_if_profile_id_is_present_in_payment_intent(
                    req.payment_id.clone(),
                    &state,
                    &auth,
                )
                .await
                .change_context(AnalyticsError::AccessForbiddenError)?;
                outgoing_webhook_events_core(&state.pool, req, auth.merchant_account.get_id())
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn get_profile_sdk_events(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<api_models::analytics::sdk_events::SdkEventsRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GetSdkEvents;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                utils::check_if_profile_id_is_present_in_payment_intent(
                    req.payment_id.clone(),
                    &state,
                    &auth,
                )
                .await
                .change_context(AnalyticsError::AccessForbiddenError)?;
                sdk_events_core(&state.pool, req, &auth.merchant_account.publishable_key)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_merchant_refund_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateRefundReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::MerchantLevel {
                        org_id: org_id.clone(),
                        merchant_ids: vec![merchant_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.refund_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_org_refund_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateRefundReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: None,
                    auth: AuthInfo::OrgLevel {
                        org_id: org_id.clone(),
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.refund_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::OrganizationReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_profile_refund_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateRefundReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::ProfileLevel {
                        org_id: org_id.clone(),
                        merchant_id: merchant_id.clone(),
                        profile_ids: vec![profile_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.refund_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }
    #[cfg(feature = "v1")]
    pub async fn generate_merchant_dispute_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateDisputeReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::MerchantLevel {
                        org_id: org_id.clone(),
                        merchant_ids: vec![merchant_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.dispute_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }
    #[cfg(feature = "v1")]
    pub async fn generate_org_dispute_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateDisputeReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: None,
                    auth: AuthInfo::OrgLevel {
                        org_id: org_id.clone(),
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.dispute_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::OrganizationReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_profile_dispute_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateDisputeReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::ProfileLevel {
                        org_id: org_id.clone(),
                        merchant_id: merchant_id.clone(),
                        profile_ids: vec![profile_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.dispute_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_merchant_payment_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GeneratePaymentReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::MerchantLevel {
                        org_id: org_id.clone(),
                        merchant_ids: vec![merchant_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.payment_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_org_payment_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GeneratePaymentReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();

                let org_id = auth.merchant_account.get_org_id();
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: None,
                    auth: AuthInfo::OrgLevel {
                        org_id: org_id.clone(),
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.payment_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::OrganizationReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_profile_payment_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GeneratePaymentReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),
            |state, (auth, user_id): auth::AuthenticationDataWithUserId, payload, _| async move {
                let user = UserInterface::find_user_by_id(&*state.global_store, &user_id)
                    .await
                    .change_context(AnalyticsError::UnknownError)?;

                let user_email = UserEmail::from_pii_email(user.email)
                    .change_context(AnalyticsError::UnknownError)?
                    .get_secret();
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;
                let lambda_req = GenerateReportRequest {
                    request: payload,
                    merchant_id: Some(merchant_id.clone()),
                    auth: AuthInfo::ProfileLevel {
                        org_id: org_id.clone(),
                        merchant_id: merchant_id.clone(),
                        profile_ids: vec![profile_id.clone()],
                    },
                    email: user_email,
                };

                let json_bytes =
                    serde_json::to_vec(&lambda_req).map_err(|_| AnalyticsError::UnknownError)?;
                invoke_lambda(
                    &state.conf.report_download_config.payment_function,
                    &state.conf.report_download_config.region,
                    &json_bytes,
                )
                .await
                .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileReportRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn generate_merchant_authentication_report(
        state: web::Data<AppState>,
        req: actix_web::HttpRequest,
        json_payload: web::Json<ReportRequest>,
    ) -> impl Responder {
        let flow = AnalyticsFlow::GenerateAuthenticationReport;
        Box::pin(api::server_wrap(
            flow,
            state.clone(),
            &req,
            json_payload.into_inner(),


pub async fn connector_list_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::MerchantConnectorsList;
    let merchant_id = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.to_owned(),
        |state, auth, merchant_id, _| {
            list_payment_connectors(
                state,
                merchant_id,
                auth.profile_id.map(|profile_id| vec![profile_id]),
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: Permission::ProfileConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}


pub async fn payments_complete_authorize_redirect(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    json_payload: Option<web::Form<serde_json::Value>>,
    path: web::Path<(
        common_utils::id_type::PaymentId,
        common_utils::id_type::MerchantId,
        String,
    )


pub fn record_value(&mut self, key: &'a str, value: serde_json::Value) {
        if super::formatter::IMPLICIT_KEYS.contains(key) {
            tracing::warn!(value =? value, "{} is a reserved entry. Skipping it.", key);
        } else {
            self.values.insert(key, value);
        }
    }


impl<K, V> Iterator for IntoIter<K, V>
where
    K: EntityId,
{
    type Item = (K, V);

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|(id, val)| (K::with_id(id), val))
    }
}


OpenAPI Block Path: components.schemas.ConnectorIntegrationStatus
{
  "type": "string",
  "description": "Connector Integration Status",
  "enum": [
    "live",
    "sandbox",
    "beta",
    "alpha"
  ]
}


impl api::ConnectorVerifyWebhookSource for Paypal {}

impl api::ConnectorVerifyWebhookSource for for Paypal


pub struct OnboardTransferMethodResponse {
    pub parent_id: Secret<String>,
    pub account_id: Secret<String>,
    pub sub_account_id: Secret<String>,
    pub id: String,
    pub status: String,
    pub created_on: String,
    pub last_updated: String,
    pub country_code: String,
    pub currency_code: Currency,
    pub display_name: String,
    #[serde(rename = "type")]
    pub transfer_method_type: String,
    pub profile: Profile,
    pub bank_account: BankAccount,
}


pub fn get_auth_event_dimensions() -> Vec<NameDescription> {
    vec![
        AuthEventDimensions::AuthenticationConnector,
        AuthEventDimensions::MessageVersion,
        AuthEventDimensions::AcsReferenceNumber,
        AuthEventDimensions::Platform,
        AuthEventDimensions::Mcc,
        AuthEventDimensions::Currency,
        AuthEventDimensions::MerchantCountry,
        AuthEventDimensions::BillingCountry,
        AuthEventDimensions::ShippingCountry,
        AuthEventDimensions::IssuerCountry,
        AuthEventDimensions::IssuerId,
        AuthEventDimensions::EarliestSupportedVersion,
        AuthEventDimensions::LatestSupportedVersion,
        AuthEventDimensions::WhitelistDecision,
        AuthEventDimensions::DeviceManufacturer,
        AuthEventDimensions::DeviceType,
        AuthEventDimensions::DeviceBrand,
        AuthEventDimensions::DeviceOs,
        AuthEventDimensions::DeviceDisplay,
        AuthEventDimensions::BrowserName,
        AuthEventDimensions::BrowserVersion,
        AuthEventDimensions::SchemeName,
        AuthEventDimensions::ExemptionRequested,
        AuthEventDimensions::ExemptionAccepted,
    ]
    .into_iter()
    .map(Into::into)
    .collect()
}


pub fn new() -> &'static Self {
        &Self {
            amount_convertor: &FloatMajorUnitForConnector,
        }
    }


impl ApiEventMetric for CardInfoCreateRequest {}

impl ApiEventMetric for for CardInfoCreateRequest


pub struct MollieErrorResponse {
    pub status: u16,
    pub title: Option<String>,
    pub detail: String,
    pub field: Option<String>,
}


pub struct ScoreDataEventResponse {
    pub score: f64,
    pub label: String,
    pub current_count: u64,
}


pub struct HubspotRequest {
    /// Indicates whether Hubspot should be used.
    #[serde(rename = "useHubspot")]
    pub use_hubspot: bool,

    /// The country of the user or company.
    pub country: String,

    /// The ID of the Hubspot form being submitted.
    #[serde(rename = "hubspotFormId")]
    pub hubspot_form_id: String,

    /// The first name of the user.
    pub firstname: Secret<String>,

    /// The last name of the user.
    pub lastname: Secret<String>,

    /// The email address of the user.
    pub email: Secret<String>,

    /// The name of the company.
    #[serde(rename = "companyName")]
    pub company_name: String,

    /// The source of the lead, typically set to "Hyperswitch Dashboard".
    pub lead_source: String,

    /// The website URL of the company.
    pub website: String,

    /// The phone number of the user.
    pub phone: Secret<String>,

    /// The role or designation of the user.
    pub role: String,

    /// The monthly GMV (Gross Merchandise Value) of the company.
    #[serde(rename = "monthlyGMV")]
    pub monthly_gmv: String,

    /// Notes from the business development team.
    pub bd_notes: String,

    /// Additional message or comments.
    pub message: String,
}


pub struct RefundResponse {
    #[serde(deserialize_with = "str_or_i32")]
    pub id: String,
    pub authorizing_merchant_id: i32,
    #[serde(deserialize_with = "str_or_i32")]
    pub approved: String,
    #[serde(deserialize_with = "str_or_i32")]
    pub message_id: String,
    pub message: String,
    pub auth_code: String,
    pub created: String,
    pub amount: FloatMajorUnit,
    pub order_number: String,
    #[serde(rename = "type")]
    pub payment_type: String,
    pub comments: Option<String>,
    pub batch_number: Option<String>,
    pub total_refunds: Option<f32>,
    pub total_completions: Option<f32>,
    pub payment_method: String,
    pub card: CardData,
    pub billing: Option<AddressData>,
    pub shipping: Option<AddressData>,
    pub custom: CustomData,
    pub adjusted_by: Option<Vec<AdjustedBy>>,
    pub links: Vec<Links>,
    pub risk_score: Option<f32>,
}


pub struct Extra {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub connector: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reason: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub connector_transaction_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fields: Option<String>,
}


File: crates/api_models/src/events/payouts.rs

use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::payouts::{
    PayoutActionRequest, PayoutCreateRequest, PayoutCreateResponse, PayoutLinkInitiateRequest,
    PayoutListConstraints, PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse,
    PayoutRetrieveRequest,
};

impl ApiEventMetric for PayoutRetrieveRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payout {
            payout_id: self.payout_id.to_owned(),
        })
    }
}

impl ApiEventMetric for PayoutCreateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        self.payout_id.as_ref().map(|id| ApiEventsType::Payout {
            payout_id: id.to_owned(),
        })
    }
}

impl ApiEventMetric for PayoutCreateResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payout {
            payout_id: self.payout_id.to_owned(),
        })
    }
}

impl ApiEventMetric for PayoutActionRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payout {
            payout_id: self.payout_id.to_owned(),
        })
    }
}

impl ApiEventMetric for PayoutListConstraints {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::ResourceListAPI)
    }
}

impl ApiEventMetric for PayoutListFilterConstraints {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::ResourceListAPI)
    }
}

impl ApiEventMetric for PayoutListResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::ResourceListAPI)
    }
}

impl ApiEventMetric for PayoutListFilters {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::ResourceListAPI)
    }
}

impl ApiEventMetric for PayoutLinkInitiateRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payout {
            payout_id: self.payout_id.to_owned(),
        })
    }
}


impl RefundDistributionAccumulator for RefundReasonDistributionAccumulator {
    type DistributionOutput = Option<Vec<ReasonsResult>>;

    fn add_distribution_bucket(&mut self, distribution: &RefundDistributionRow) {
        self.refund_reason_vec.push(RefundReasonDistributionRow {
            count: distribution.count.unwrap_or_default(),
            total: distribution
                .total
                .clone()
                .map(|i| i.to_i64().unwrap_or_default())
                .unwrap_or_default(),
            refund_reason: distribution.refund_reason.clone().unwrap_or_default(),
        })
    }

    fn collect(mut self) -> Self::DistributionOutput {
        if self.refund_reason_vec.is_empty() {
            None
        } else {
            self.refund_reason_vec.sort_by(|a, b| b.count.cmp(&a.count));
            let mut res: Vec<ReasonsResult> = Vec::new();
            for val in self.refund_reason_vec.into_iter() {
                let perc = f64::from(u32::try_from(val.count).ok()?) * 100.0
                    / f64::from(u32::try_from(val.total).ok()?);

                res.push(ReasonsResult {
                    reason: val.refund_reason,
                    count: val.count,
                    percentage: (perc * 100.0).round() / 100.0,
                })
            }

            Some(res)
        }
    }
}

impl RefundDistributionAccumulator for for RefundReasonDistributionAccumulator


pub struct NmiWebhookObjectReference {
    pub event_body: NmiReferenceBody,
}


File: crates/analytics/src/active_payments/core.rs
 Public functions: 1

use std::collections::HashMap;

use api_models::analytics::{
    active_payments::{
        ActivePaymentsMetrics, ActivePaymentsMetricsBucketIdentifier, MetricsBucketResponse,
    },
    AnalyticsMetadata, GetActivePaymentsMetricRequest, MetricsResponse,
};
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};

use super::ActivePaymentsMetricsAccumulator;
use crate::{
    active_payments::ActivePaymentsMetricAccumulator,
    errors::{AnalyticsError, AnalyticsResult},
    AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn get_metrics(
    pool: &AnalyticsProvider,
    publishable_key: &String,
    merchant_id: &common_utils::id_type::MerchantId,
    req: GetActivePaymentsMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<
        ActivePaymentsMetricsBucketIdentifier,
        ActivePaymentsMetricsAccumulator,
    > = HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let publishable_key_scoped = publishable_key.to_owned();
        let merchant_id_scoped = merchant_id.to_owned();
        let pool = pool.clone();
        set.spawn(async move {
            let data = pool
                .get_active_payments_metrics(
                    &metric_type,
                    &merchant_id_scoped,
                    &publishable_key_scoped,
                    &req.time_range,
                )
                .await
                .change_context(AnalyticsError::UnknownError);
            (metric_type, data)
        });
    }

    while let Some((metric, data)) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        logger::info!("Logging metric: {metric} Result: {:?}", data);
        for (id, value) in data? {
            let metrics_builder = metrics_accumulator.entry(id).or_default();
            match metric {
                ActivePaymentsMetrics::ActivePayments => {
                    metrics_builder.active_payments.add_metrics_bucket(&value)
                }
            }
        }

        logger::debug!(
            "Analytics Accumulated Results: metric: {}, results: {:#?}",
            metric,
            metrics_accumulator
        );
    }

    let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| MetricsBucketResponse {
            values: val.collect(),
            dimensions: id,
        })
        .collect();

    Ok(MetricsResponse {
        query_data,
        meta_data: [AnalyticsMetadata {
            current_time_range: req.time_range,
        }],
    })
}


File: crates/common_utils/tests/percentage.rs

#![allow(clippy::panic_in_result_fn)]
use common_utils::{errors::PercentageError, types::Percentage};
const PRECISION_2: u8 = 2;
const PRECISION_0: u8 = 0;

#[test]
fn invalid_range_more_than_100() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("100.01".to_string());
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(
            *err.current_context(),
            PercentageError::InvalidPercentageValue
        )
    }
    Ok(())
}
#[test]
fn invalid_range_less_than_0() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("-0.01".to_string());
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(
            *err.current_context(),
            PercentageError::InvalidPercentageValue
        )
    }
    Ok(())
}

#[test]
fn invalid_string() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("-0.01ed".to_string());
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(
            *err.current_context(),
            PercentageError::InvalidPercentageValue
        )
    }
    Ok(())
}

#[test]
fn valid_range() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("2.22".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 2.22)
    }

    let percentage = Percentage::<PRECISION_2>::from_string("0.05".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 0.05)
    }

    let percentage = Percentage::<PRECISION_2>::from_string("100.0".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 100.0)
    }
    Ok(())
}
#[test]
fn valid_precision() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("2.2".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 2.2)
    }

    let percentage = Percentage::<PRECISION_2>::from_string("2.20000".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 2.2)
    }

    let percentage = Percentage::<PRECISION_0>::from_string("2.0".to_string());
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 2.0)
    }

    Ok(())
}

#[test]
fn invalid_precision() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let percentage = Percentage::<PRECISION_2>::from_string("2.221".to_string());
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(
            *err.current_context(),
            PercentageError::InvalidPercentageValue
        )
    }
    Ok(())
}

#[test]
fn deserialization_test_ok() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut decimal = 0;
    let mut integer = 0;
    // check for all percentage values from 0 to 100
    while integer <= 100 {
        let json_string = format!(
            r#"
            {{
                "percentage" : {integer}.{decimal}
            }}
        "#,
        );
        let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(&json_string);
        assert!(percentage.is_ok());
        if let Ok(percentage) = percentage {
            assert_eq!(
                percentage.get_percentage(),
                format!("{integer}.{decimal}")
                    .parse::<f32>()
                    .unwrap_or_default()
            )
        }
        if integer == 100 {
            break;
        }
        decimal += 1;
        if decimal == 100 {
            decimal = 0;
            integer += 1;
        }
    }

    let json_string = r#"
        {
            "percentage" : 18.7
        }
    "#;
    let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 18.7)
    }

    let json_string = r#"
        {
            "percentage" : 12.0
        }
    "#;
    let percentage = serde_json::from_str::<Percentage<PRECISION_0>>(json_string);
    assert!(percentage.is_ok());
    if let Ok(percentage) = percentage {
        assert_eq!(percentage.get_percentage(), 12.0)
    }
    Ok(())
}

#[test]
fn deserialization_test_err() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // invalid percentage precision
    let json_string = r#"
        {
            "percentage" : 12.4
        }
    "#;
    let percentage = serde_json::from_str::<Percentage<PRECISION_0>>(json_string);
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(err.to_string(), "invalid value: percentage value 12.4, expected value should be a float between 0 to 100 and precise to only upto 0 decimal digits at line 4 column 9".to_string())
    }

    // invalid percentage value
    let json_string = r#"
        {
            "percentage" : 123.42
        }
    "#;
    let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(err.to_string(), "invalid value: percentage value 123.42, expected value should be a float between 0 to 100 and precise to only upto 2 decimal digits at line 4 column 9".to_string())
    }

    // missing percentage field
    let json_string = r#"
        {
            "percent": 22.0
        }
    "#;
    let percentage = serde_json::from_str::<Percentage<PRECISION_2>>(json_string);
    assert!(percentage.is_err());
    if let Err(err) = percentage {
        assert_eq!(
            err.to_string(),
            "missing field `percentage` at line 4 column 9".to_string()
        )
    }
    Ok(())
}


pub struct ChargesPaymentRequest {
    source: Source,
    transaction_interaction: Option<TransactionInteraction>,
    transaction_details: TransactionDetails,
}


pub struct ProcessorResponse {
    name: Option<String>,
}


impl RoleInfo {
    pub fn get_role_id(&self) -> &str {
        &self.role_id
    }

    pub fn get_role_name(&self) -> &str {
        &self.role_name
    }

    pub fn get_permission_groups(&self) -> Vec<PermissionGroup> {
        self.groups
            .iter()
            .flat_map(|group| group.accessible_groups())
            .collect::<HashSet<_>>()
            .into_iter()
            .collect()
    }

    pub fn get_scope(&self) -> RoleScope {
        self.scope
    }

    pub fn get_entity_type(&self) -> EntityType {
        self.entity_type
    }

    pub fn is_invitable(&self) -> bool {
        self.is_invitable
    }

    pub fn is_deletable(&self) -> bool {
        self.is_deletable
    }

    pub fn is_internal(&self) -> bool {
        self.is_internal
    }

    pub fn is_updatable(&self) -> bool {
        self.is_updatable
    }

    pub fn get_resources_set(&self) -> HashSet<Resource> {
        self.get_permission_groups()
            .iter()
            .flat_map(|group| group.resources())
            .collect()
    }

    pub fn check_permission_exists(&self, required_permission: Permission) -> bool {
        required_permission.entity_type() <= self.entity_type
            && self.get_permission_groups().iter().any(|group| {
                required_permission.scope() <= group.scope()
                    && group.resources().contains(&required_permission.resource())
            })
    }

    #[cfg(feature = "recon")]
    pub fn get_recon_acl(&self) -> HashMap<Resource, ReconPermissionScope> {
        let mut acl: HashMap<Resource, ReconPermissionScope> = HashMap::new();
        let mut recon_resources = RECON_OPS.to_vec();
        recon_resources.extend(RECON_REPORTS);
        let recon_internal_resources = [Resource::ReconToken];
        self.get_permission_groups()
            .iter()
            .for_each(|permission_group| {
                permission_group.resources().iter().for_each(|resource| {
                    if recon_resources.contains(resource)
                        && !recon_internal_resources.contains(resource)
                    {
                        let scope = match resource {
                            Resource::ReconAndSettlementAnalytics => ReconPermissionScope::Read,
                            _ => ReconPermissionScope::from(permission_group.scope()),
                        };
                        acl.entry(*resource)
                            .and_modify(|curr_scope| {
                                *curr_scope = if (*curr_scope) < scope {
                                    scope
                                } else {
                                    *curr_scope
                                }
                            })
                            .or_insert(scope);
                    }
                })
            });
        acl
    }

    pub fn from_predefined_roles(role_id: &str) -> Option<Self> {
        predefined_roles::PREDEFINED_ROLES.get(role_id).cloned()
    }

    pub async fn from_role_id_in_lineage(
        state: &SessionState,
        role_id: &str,
        merchant_id: &id_type::MerchantId,
        org_id: &id_type::OrganizationId,
        profile_id: &id_type::ProfileId,
        tenant_id: &id_type::TenantId,
    ) -> CustomResult<Self, errors::StorageError> {
        if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
            Ok(role.clone())
        } else {
            state
                .global_store
                .find_role_by_role_id_in_lineage(
                    role_id,
                    merchant_id,
                    org_id,
                    profile_id,
                    tenant_id,
                )
                .await
                .map(Self::from)
        }
    }

    // TODO: To evaluate whether we can omit org_id and tenant_id for this function
    pub async fn from_role_id_org_id_tenant_id(
        state: &SessionState,
        role_id: &str,
        org_id: &id_type::OrganizationId,
        tenant_id: &id_type::TenantId,
    ) -> CustomResult<Self, errors::StorageError> {
        if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
            Ok(role.clone())
        } else {
            state
                .global_store
                .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id)
                .await
                .map(Self::from)
        }
    }
}


pub struct ConnectAccountRequest {
    pub email: pii::Email,
}


pub struct PaymentCancel;


impl api::PaymentVoid for Paysafe {}


File: crates/scheduler/src/consumer/types/process_data.rs
 Public structs: 5

use std::collections::HashMap;

use diesel_models::enums;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RetryMapping {
    pub start_after: i32,
    pub frequencies: Vec<(i32, i32)>, // (frequency, count)
}

#[derive(Serialize, Deserialize)]
pub struct ConnectorPTMapping {
    pub default_mapping: RetryMapping,
    pub custom_merchant_mapping: HashMap<common_utils::id_type::MerchantId, RetryMapping>,
    pub max_retries_count: i32,
}

impl Default for ConnectorPTMapping {
    fn default() -> Self {
        Self {
            custom_merchant_mapping: HashMap::new(),
            default_mapping: RetryMapping {
                start_after: 60,
                frequencies: vec![(300, 5)],
            },
            max_retries_count: 5,
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct PaymentMethodsPTMapping {
    pub default_mapping: RetryMapping,
    pub custom_pm_mapping: HashMap<enums::PaymentMethod, RetryMapping>,
    pub max_retries_count: i32,
}

impl Default for PaymentMethodsPTMapping {
    fn default() -> Self {
        Self {
            custom_pm_mapping: HashMap::new(),
            default_mapping: RetryMapping {
                start_after: 900,
                frequencies: vec![(300, 5)],
            },
            max_retries_count: 5,
        }
    }
}

/// Configuration for outgoing webhook retries.
#[derive(Debug, Serialize, Deserialize)]
pub struct OutgoingWebhookRetryProcessTrackerMapping {
    /// Default (fallback) retry configuration used when no merchant-specific retry configuration
    /// exists.
    pub default_mapping: RetryMapping,

    /// Merchant-specific retry configuration.
    pub custom_merchant_mapping: HashMap<common_utils::id_type::MerchantId, RetryMapping>,
}

impl Default for OutgoingWebhookRetryProcessTrackerMapping {
    fn default() -> Self {
        Self {
            default_mapping: RetryMapping {
                // 1st attempt happens after 1 minute
                start_after: 60,

                frequencies: vec![
                    // 2nd and 3rd attempts happen at intervals of 5 minutes each
                    (60 * 5, 2),
                    // 4th, 5th, 6th, 7th and 8th attempts happen at intervals of 10 minutes each
                    (60 * 10, 5),
                    // 9th, 10th, 11th, 12th and 13th attempts happen at intervals of 1 hour each
                    (60 * 60, 5),
                    // 14th, 15th and 16th attempts happen at intervals of 6 hours each
                    (60 * 60 * 6, 3),
                ],
            },
            custom_merchant_mapping: HashMap::new(),
        }
    }
}

/// Configuration for outgoing webhook retries.
#[derive(Debug, Serialize, Deserialize)]
pub struct RevenueRecoveryPaymentProcessTrackerMapping {
    /// Default (fallback) retry configuration used when no merchant-specific retry configuration
    /// exists.
    pub default_mapping: RetryMapping,

    /// Merchant-specific retry configuration.
    pub custom_merchant_mapping: HashMap<common_utils::id_type::MerchantId, RetryMapping>,
}

impl Default for RevenueRecoveryPaymentProcessTrackerMapping {
    fn default() -> Self {
        Self {
            default_mapping: RetryMapping {
                // 1st attempt happens after 1 minute of it being
                start_after: 60,

                frequencies: vec![
                    // 2nd and 3rd attempts happen at intervals of 3 hours each
                    (60 * 60 * 3, 2),
                    // 4th, 5th, 6th attempts happen at intervals of 6 hours each
                    (60 * 60 * 6, 3),
                    // 7th, 8th, 9th attempts happen at intervals of 9 hour each
                    (60 * 60 * 9, 3),
                    // 10th, 11th and 12th attempts happen at intervals of 12 hours each
                    (60 * 60 * 12, 3),
                    // 13th, 14th and 15th attempts happen at intervals of 18 hours each
                    (60 * 60 * 18, 3),
                ],
            },
            custom_merchant_mapping: HashMap::new(),
        }
    }
}


impl api::ConnectorAccessToken for Zsl {}

impl api::ConnectorAccessToken for for Zsl


File: crates/storage_impl/src/metrics.rs

use router_env::{counter_metric, gauge_metric, global_meter};

global_meter!(GLOBAL_METER, "ROUTER_API");

counter_metric!(KV_MISS, GLOBAL_METER); // No. of KV misses

// Metrics for KV
counter_metric!(KV_OPERATION_SUCCESSFUL, GLOBAL_METER);
counter_metric!(KV_OPERATION_FAILED, GLOBAL_METER);
counter_metric!(KV_PUSHED_TO_DRAINER, GLOBAL_METER);
counter_metric!(KV_FAILED_TO_PUSH_TO_DRAINER, GLOBAL_METER);
counter_metric!(KV_SOFT_KILL_ACTIVE_UPDATE, GLOBAL_METER);

// Metrics for In-memory cache
gauge_metric!(IN_MEMORY_CACHE_ENTRY_COUNT, GLOBAL_METER);
counter_metric!(IN_MEMORY_CACHE_HIT, GLOBAL_METER);
counter_metric!(IN_MEMORY_CACHE_MISS, GLOBAL_METER);
counter_metric!(IN_MEMORY_CACHE_EVICTION_COUNT, GLOBAL_METER);


    let html = match payment_response.status {
            IntentStatus::RequiresCustomerAction => {
                // Request poll id sent to client for retrieve_poll_status api
                let req_poll_id = get_external_authentication_request_poll_id(&payment_id);
                let poll_frequency = poll_config.frequency;
                let poll_delay_in_secs = poll_config.delay_in_secs;
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    let poll_status_data = {{
                                        'poll_id': '{req_poll_id}',
                                        'frequency': '{poll_frequency}',
                                        'delay_in_secs': '{poll_delay_in_secs}',
                                        'return_url_with_query_params': return_url
                                    }};
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
            _ => {
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
        };
    Ok(html)
}

#[cfg(feature = "v1")]
pub fn get_html_redirect_response_for_external_modular_authentication(
    return_url_with_query_params: String,
    authentication_response: &api_models::authentication::AuthenticationResponse,
    authentication_id: common_utils::id_type::AuthenticationId,
    poll_config: &PollConfig,
) -> RouterResult<String> {
    // if authentication_status is pending then set poll_id, fetch poll config and do a poll_status post message, else do open_url post message to redirect to return_url
    let html = match authentication_response.status {
            common_enums::AuthenticationStatus::Pending => {
                // Request poll id sent to client for retrieve_poll_status api
                let req_poll_id = get_modular_authentication_request_poll_id(&authentication_id);
                let poll_frequency = poll_config.frequency;
                let poll_delay_in_secs = poll_config.delay_in_secs;
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    let poll_status_data = {{
                                        'poll_id': '{req_poll_id}',
                                        'frequency': '{poll_frequency}',
                                        'delay_in_secs': '{poll_delay_in_secs}',
                                        'return_url_with_query_params': return_url
                                    }};
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
            _ => {
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
        };
    Ok(html)
}

#[inline]
pub fn get_flow_name<F>() -> RouterResult<String> {
    Ok(std::any::type_name::<F>()
        .to_string()
        .rsplit("::")
        .next()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Flow stringify failed")?
        .to_string())
}

pub fn get_request_incremental_authorization_value(
    request_incremental_authorization: Option<bool>,
    capture_method: Option<common_enums::CaptureMethod>,
) -> RouterResult<Option<RequestIncrementalAuthorization>> {
    Some(request_incremental_authorization
        .map(|request_incremental_authorization| {
            if request_incremental_authorization {
                if matches!(
                    capture_method,
                    Some(common_enums::CaptureMethod::Automatic) | Some(common_enums::CaptureMethod::SequentialAutomatic)
                ) {
                    Err(errors::ApiErrorResponse::NotSupported { message: "incremental authorization is not supported when capture_method is automatic".to_owned() })?
                }
                Ok(RequestIncrementalAuthorization::True)
            } else {
                Ok(RequestIncrementalAuthorization::False)
            }
        })
        .unwrap_or(Ok(RequestIncrementalAuthorization::default()))).transpose()
}

pub fn get_incremental_authorization_allowed_value(
    incremental_authorization_allowed: Option<bool>,
    request_incremental_authorization: Option<RequestIncrementalAuthorization>,
) -> Option<bool> {
    if request_incremental_authorization == Some(RequestIncrementalAuthorization::False) {
        Some(false)
    } else {
        incremental_authorization_allowed
    }
}

pub(crate) trait GetProfileId {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>;
}

impl GetProfileId for MerchantConnectorAccount {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for storage::PaymentIntent {
    #[cfg(feature = "v1")]
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }

    // TODO: handle this in a better way for v2
    #[cfg(feature = "v2")]
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl<A> GetProfileId for (storage::PaymentIntent, A) {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.0.get_profile_id()
    }
}

impl GetProfileId for diesel_models::Dispute {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

impl GetProfileId for diesel_models::Refund {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

#[cfg(feature = "v1")]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

#[cfg(feature = "v2")]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for api_models::routing::RoutingRetrieveLinkQuery {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

impl GetProfileId for diesel_models::routing_algorithm::RoutingProfileMetadata {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for domain::Profile {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(self.get_id())
    }
}

#[cfg(feature = "payouts")]
impl GetProfileId for storage::Payouts {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}
#[cfg(feature = "payouts")]
impl<T, F, R> GetProfileId for (storage::Payouts, T, F, R) {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.0.get_profile_id()
    }
}

/// Filter Objects based on profile ids
pub(super) fn filter_objects_based_on_profile_id_list<
    T: GetProfileId,
    U: IntoIterator<Item = T> + FromIterator<T>,
>(
    profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>,
    object_list: U,
) -> U {
    if let Some(profile_id_list) = profile_id_list_auth_layer {
        let profile_ids_to_filter: HashSet<_> = profile_id_list.iter().collect();
        object_list
            .into_iter()
            .filter_map(|item| {
                if item
                    .get_profile_id()
                    .is_some_and(|profile_id| profile_ids_to_filter.contains(profile_id))
                {
                    Some(item)
                } else {
                    None
                }
            })
            .collect()
    } else {
        object_list
    }
}

pub(crate) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>(
    profile_id_auth_layer: Option<common_utils::id_type::ProfileId>,
    object: &T,
) -> RouterResult<()> {
    match (profile_id_auth_layer, object.get_profile_id()) {
        (Some(auth_profile_id), Some(object_profile_id)) => {
            auth_profile_id.eq(object_profile_id).then_some(()).ok_or(
                errors::ApiErrorResponse::PreconditionFailed {
                    message: "Profile id authentication failed. Please use the correct JWT token"
                        .to_string(),
                }
                .into(),
            )
        }
        (Some(_auth_profile_id), None) => RouterResult::Err(
            errors::ApiErrorResponse::PreconditionFailed {
                message: "Couldn't find profile_id in record for authentication".to_string(),
            }
            .into(),
        )
        .attach_printable(format!("Couldn't find profile_id in entity {object:?}")),
        (None, None) | (None, Some(_)) => Ok(()),
    }
}

#[cfg(feature = "v2")]
pub async fn construct_vault_router_data<F>(
    state: &SessionState,
    merchant_account: &domain::MerchantAccount,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    payment_method_vaulting_data: Option<domain::PaymentMethodVaultingData>,
    connector_vault_id: Option<String>,
    connector_customer_id: Option<String>,
) -> RouterResult<VaultRouterDataV2<F>> {
    let connector_name = merchant_connector_account
        .get_connector_name()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Connector name not present for external vault")?; // always get the connector name from the merchant_connector_account
    let connector_auth_type = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let resource_common_data = VaultConnectorFlowData {
        merchant_id: merchant_account.get_id().to_owned(),
    };

    let router_data = types::RouterDataV2 {
        flow: PhantomData,
        resource_common_data,
        tenant_id: state.tenant.tenant_id.clone(),
        connector_auth_type,
        request: types::VaultRequestData {
            payment_method_vaulting_data,
            connector_vault_id,
            connector_customer_id,
        },
        response: Ok(types::VaultResponseData::default()),
    };

    Ok(router_data)
}

pub fn validate_bank_account_data(data: &types::MerchantAccountData) -> RouterResult<()> {
    match data {
        types::MerchantAccountData::Iban { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::Sepa { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::SepaInstant { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::Bacs {
            account_number,
            sort_code,
            ..
        } => validate_uk_account(account_number, sort_code),

        types::MerchantAccountData::FasterPayments {
            account_number,
            sort_code,
            ..
        } => validate_uk_account(account_number, sort_code),

        types::MerchantAccountData::Elixir { iban, .. } => validate_elixir_account(iban),

        types::MerchantAccountData::Bankgiro { number, .. } => validate_bankgiro_number(number),

        types::MerchantAccountData::Plusgiro { number, .. } => validate_plusgiro_number(number),
    }
}

fn validate_iban(iban: &Secret<String>) -> RouterResult<()> {
    let iban_str = iban.peek();

    if iban_str.len() > consts::IBAN_MAX_LENGTH || iban_str.len() < consts::IBAN_MIN_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "IBAN length must be between {} and {} characters",
                consts::IBAN_MIN_LENGTH,
                consts::IBAN_MAX_LENGTH
            ),
        }
        .into());
    }

    if iban.peek().len() > consts::IBAN_MAX_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "IBAN length must be up to 34 characters".to_string(),
        }
        .into());
    }
    let pattern = Regex::new(r"^[A-Z0-9]*$")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to create regex pattern")?;

    let mut iban = iban.peek().to_string();

    if !pattern.is_match(iban.as_str()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "IBAN data must be alphanumeric".to_string(),
        }
        .into());
    }

    // MOD check
    let first_4 = iban.chars().take(4).collect::<String>();
    iban.push_str(first_4.as_str());
    let len = iban.len();

    let rearranged_iban = iban
        .chars()
        .rev()
        .take(len - 4)
        .collect::<String>()
        .chars()
        .rev()
        .collect::<String>();

    let mut result = String::new();

    rearranged_iban.chars().for_each(|c| {
        if c.is_ascii_uppercase() {
            let digit = (u32::from(c) - u32::from('A')) + 10;
            result.push_str(&format!("{digit:02}"));
        } else {
            result.push(c);
        }
    });

    let num = result
        .parse::<u128>()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to validate IBAN")?;

    if num % 97 != 1 {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid IBAN".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_uk_account(
    account_number: &Secret<String>,
    sort_code: &Secret<String>,
) -> RouterResult<()> {
    if account_number.peek().len() > consts::BACS_MAX_ACCOUNT_NUMBER_LENGTH
        || sort_code.peek().len() != consts::BACS_SORT_CODE_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid BACS numbers".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_elixir_account(iban: &Secret<String>) -> RouterResult<()> {
    let iban_str = iban.peek();

    // Validate IBAN first
    validate_iban(iban)?;

    // Check if IBAN is Polish
    if !iban_str.starts_with("PL") {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Elixir IBAN must be Polish (PL)".to_string(),
        }
        .into());
    }

    // Validate IBAN length for Poland
    if iban_str.len() != consts::ELIXIR_IBAN_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Polish IBAN must be 28 characters".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_bankgiro_number(number: &Secret<String>) -> RouterResult<()> {
    let num_str = number.peek();
    let clean_number = num_str.replace("-", "").replace(" ", "");

    // Length validation
    if clean_number.len() < consts::BANKGIRO_MIN_LENGTH
        || clean_number.len() > consts::BANKGIRO_MAX_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "Bankgiro number must be between {} and {} digits",
                consts::BANKGIRO_MIN_LENGTH,
                consts::BANKGIRO_MAX_LENGTH
            ),
        }
        .into());
    }

    // Must be numeric
    if !clean_number.chars().all(|c| c.is_ascii_digit()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Bankgiro number must be numeric".to_string(),
        }
        .into());
    }
    Ok(())
}

fn validate_plusgiro_number(number: &Secret<String>) -> RouterResult<()> {
    let num_str = number.peek();
    let clean_number = num_str.replace("-", "").replace(" ", "");

    // Length validation
    if clean_number.len() < consts::PLUSGIRO_MIN_LENGTH
        || clean_number.len() > consts::PLUSGIRO_MAX_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "Plusgiro number must be between {} and {} digits",
                consts::PLUSGIRO_MIN_LENGTH,
                consts::PLUSGIRO_MAX_LENGTH
            ),
        }
        .into());
    }

    // Must be numeric
    if !clean_number.chars().all(|c| c.is_ascii_digit()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Plusgiro number must be numeric".to_string(),
        }
        .into());
    }
    Ok(())
}

pub fn should_add_dispute_sync_task_to_pt(state: &SessionState, connector_name: Connector) -> bool {
    let list_dispute_supported_connectors = state
        .conf
        .list_dispute_supported_connectors
        .connector_list
        .clone();
    list_dispute_supported_connectors.contains(&connector_name)
}

pub fn should_proceed_with_submit_evidence(
    dispute_stage: DisputeStage,
    dispute_status: DisputeStatus,
) -> bool {
    matches!(
        dispute_stage,
        DisputeStage::PreDispute
            | DisputeStage::Dispute
            | DisputeStage::PreArbitration
            | DisputeStage::Arbitration
    ) && matches!(
        dispute_status,
        DisputeStatus::DisputeOpened | DisputeStatus::DisputeChallenged
    )
}

pub fn should_proceed_with_accept_dispute(
    dispute_stage: DisputeStage,
    dispute_status: DisputeStatus,
) -> bool {
    matches!(
        dispute_stage,
        DisputeStage::PreDispute | DisputeStage::Dispute | DisputeStage::PreArbitration
    ) && matches!(
        dispute_status,
        DisputeStatus::DisputeChallenged | DisputeStatus::DisputeOpened
    )
}


pub struct CardPaymentInformation {
    card: Card,
}


pub struct FailureReasonsDistributionAccumulator {
    pub count: u64,
    pub count_without_retries: u64,
}


pub struct FiuuPaymentSyncRequest {
    amount: StringMajorUnit,
    #[serde(rename = "txID")]
    tx_id: String,
    domain: String,
    skey: Secret<String>,
}


pub struct AuthEventMetricRow {
    pub count: Option<i64>,
    pub authentication_status: Option<DBEnumWrapper<storage_enums::AuthenticationStatus>>,
    pub trans_status: Option<DBEnumWrapper<storage_enums::TransactionStatus>>,
    pub authentication_type: Option<DBEnumWrapper<storage_enums::DecoupledAuthenticationType>>,
    pub error_message: Option<String>,
    pub authentication_connector: Option<DBEnumWrapper<storage_enums::AuthenticationConnectors>>,
    pub message_version: Option<String>,
    pub acs_reference_number: Option<String>,
    pub platform: Option<String>,
    pub mcc: Option<String>,
    pub currency: Option<DBEnumWrapper<storage_enums::Currency>>,
    pub merchant_country: Option<String>,
    pub billing_country: Option<String>,
    pub shipping_country: Option<String>,
    pub issuer_country: Option<String>,
    pub earliest_supported_version: Option<String>,
    pub latest_supported_version: Option<String>,
    pub whitelist_decision: Option<bool>,
    pub device_manufacturer: Option<String>,
    pub device_type: Option<String>,
    pub device_brand: Option<String>,
    pub device_os: Option<String>,
    pub device_display: Option<String>,
    pub browser_name: Option<String>,
    pub browser_version: Option<String>,
    pub issuer_id: Option<String>,
    pub scheme_name: Option<String>,
    pub exemption_requested: Option<bool>,
    pub exemption_accepted: Option<bool>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}


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