// File: crates/router/src/core/unified_authentication_service.rs
// Module: router
// Public functions: 6

pub mod types;
use std::str::FromStr;

pub mod utils;
#[cfg(feature = "v1")]
use api_models::authentication::{
    AuthenticationEligibilityRequest, AuthenticationEligibilityResponse,
    AuthenticationSyncPostUpdateRequest, AuthenticationSyncRequest, AuthenticationSyncResponse,
};
use api_models::{
    authentication::{
        AcquirerDetails, AuthenticationAuthenticateRequest, AuthenticationAuthenticateResponse,
        AuthenticationCreateRequest, AuthenticationResponse,
    },
    payments,
};
#[cfg(feature = "v1")]
use common_utils::{ext_traits::ValueExt, types::keymanager::ToEncryptable};
use diesel_models::authentication::{Authentication, AuthenticationNew};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    errors::api_error_response::ApiErrorResponse,
    payment_method_data,
    router_request_types::{
        authentication::{MessageCategory, PreAuthenticationData},
        unified_authentication_service::{
            AuthenticationInfo, PaymentDetails, ServiceSessionIds, ThreeDsMetaData,
            TransactionDetails, UasAuthenticationRequestData, UasConfirmationRequestData,
            UasPostAuthenticationRequestData, UasPreAuthenticationRequestData,
        },
        BrowserInformation,
    },
    types::{
        UasAuthenticationRouterData, UasPostAuthenticationRouterData,
        UasPreAuthenticationRouterData,
    },
};
use masking::{ExposeInterface, PeekInterface};

use super::{
    errors::{RouterResponse, RouterResult},
    payments::helpers::MerchantConnectorAccountType,
};
use crate::{
    consts,
    core::{
        authentication::utils as auth_utils,
        errors::utils::StorageErrorExt,
        payments::helpers,
        unified_authentication_service::types::{
            ClickToPay, ExternalAuthentication, UnifiedAuthenticationService,
            UNIFIED_AUTHENTICATION_SERVICE,
        },
        utils as core_utils,
    },
    db::domain,
    routes::SessionState,
    services::AuthFlow,
    types::{domain::types::AsyncLift, transformers::ForeignTryFrom},
};
#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl UnifiedAuthenticationService for ClickToPay {
    fn get_pre_authentication_request_data(
        _payment_method_data: Option<&domain::PaymentMethodData>,
        service_details: Option<payments::CtpServiceDetails>,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRequestData> {
        let domain_service_details = hyperswitch_domain_models::router_request_types::unified_authentication_service::CtpServiceDetails {
            service_session_ids: Some(ServiceSessionIds {
                merchant_transaction_id: service_details
                    .as_ref()
                    .and_then(|details| details.merchant_transaction_id.clone()),
                correlation_id: service_details
                    .as_ref()
                    .and_then(|details| details.correlation_id.clone()),
                x_src_flow_id: service_details
                    .as_ref()
                    .and_then(|details| details.x_src_flow_id.clone()),
            }),
            payment_details: None,
        };

        let transaction_details = TransactionDetails {
            amount: Some(amount),
            currency,
            device_channel: None,
            message_category: None,
        };

        let authentication_info = Some(AuthenticationInfo {
            authentication_type: None,
            authentication_reasons: None,
            consent_received: false, // This is not relevant in this flow so keeping it as false
            is_authenticated: false, // This is not relevant in this flow so keeping it as false
            locale: None,
            supported_card_brands: None,
            encrypted_payload: service_details
                .as_ref()
                .and_then(|details| details.encrypted_payload.clone()),
        });
        Ok(UasPreAuthenticationRequestData {
            service_details: Some(domain_service_details),
            transaction_details: Some(transaction_details),
            payment_details: None,
            authentication_info,
            merchant_details: merchant_details.cloned(),
            billing_address: billing_address.cloned(),
            acquirer_bin,
            acquirer_merchant_id,
        })
    }

    async fn pre_authentication(
        state: &SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        payment_method_data: Option<&domain::PaymentMethodData>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRouterData> {
        let pre_authentication_data = Self::get_pre_authentication_request_data(
            payment_method_data,
            service_details,
            amount,
            currency,
            merchant_details,
            billing_address,
            acquirer_bin,
            acquirer_merchant_id,
        )?;

        let pre_auth_router_data: UasPreAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                pre_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            pre_auth_router_data,
        ))
        .await
    }

    async fn post_authentication(
        state: &SessionState,
        _business_profile: &domain::Profile,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        merchant_id: &common_utils::id_type::MerchantId,
        _authentication: Option<&Authentication>,
    ) -> RouterResult<UasPostAuthenticationRouterData> {
        let post_authentication_data = UasPostAuthenticationRequestData {
            threeds_server_transaction_id: None,
        };

        let post_auth_router_data: UasPostAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                post_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            post_auth_router_data,
        )
        .await
    }

    async fn confirmation(
        state: &SessionState,
        authentication_id: Option<&common_utils::id_type::AuthenticationId>,
        currency: Option<common_enums::Currency>,
        status: common_enums::AttemptStatus,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        payment_method: common_enums::PaymentMethod,
        net_amount: common_utils::types::MinorUnit,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> RouterResult<()> {
        let authentication_id = authentication_id
            .ok_or(ApiErrorResponse::InternalServerError)
            .attach_printable("Missing authentication id in tracker")?;

        let currency = currency.ok_or(ApiErrorResponse::MissingRequiredField {
            field_name: "currency",
        })?;

        let current_time = common_utils::date_time::date_as_yyyymmddthhmmssmmmz()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get current time")?;

        let payment_attempt_status = status;

        let (checkout_event_status, confirmation_reason) =
            utils::get_checkout_event_status_and_reason(payment_attempt_status);

        let click_to_pay_details = service_details.clone();

        let authentication_confirmation_data = UasConfirmationRequestData {
            x_src_flow_id: click_to_pay_details
                .as_ref()
                .and_then(|details| details.x_src_flow_id.clone()),
            transaction_amount: net_amount,
            transaction_currency: currency,
            checkout_event_type: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented
            checkout_event_status: checkout_event_status.clone(),
            confirmation_status: checkout_event_status.clone(),
            confirmation_reason,
            confirmation_timestamp: Some(current_time),
            network_authorization_code: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented
            network_transaction_identifier: Some("mastercard".to_string()), // hardcoded to 'mastercard' since only mastercard has confirmation flow requirement
            correlation_id: click_to_pay_details
                .clone()
                .and_then(|details| details.correlation_id),
            merchant_transaction_id: click_to_pay_details
                .and_then(|details| details.merchant_transaction_id),
        };

        let authentication_confirmation_router_data : hyperswitch_domain_models::types::UasAuthenticationConfirmationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method,
            merchant_id.clone(),
            None,
            authentication_confirmation_data,
            merchant_connector_account,
            Some(authentication_id.to_owned()),
            payment_id.cloned(),
        )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            authentication_confirmation_router_data,
        )
        .await
        .ok(); // marking this as .ok() since this is not a required step at our end for completing the transaction

        Ok(())
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl UnifiedAuthenticationService for ExternalAuthentication {
    fn get_pre_authentication_request_data(
        payment_method_data: Option<&domain::PaymentMethodData>,
        _service_details: Option<payments::CtpServiceDetails>,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRequestData> {
        let payment_method_data = payment_method_data
            .ok_or(ApiErrorResponse::InternalServerError)
            .attach_printable("payment_method_data is missing")?;
        let payment_details =
            if let payment_method_data::PaymentMethodData::Card(card) = payment_method_data {
                Some(PaymentDetails {
                    pan: card.card_number.clone(),
                    digital_card_id: None,
                    payment_data_type: None,
                    encrypted_src_card_details: None,
                    card_expiry_month: card.card_exp_month.clone(),
                    card_expiry_year: card.card_exp_year.clone(),
                    cardholder_name: card.card_holder_name.clone(),
                    card_token_number: None,
                    account_type: None,
                    card_cvc: Some(card.card_cvc.clone()),
                })
            } else {
                None
            };
        let transaction_details = TransactionDetails {
            amount: Some(amount),
            currency,
            device_channel: None,
            message_category: None,
        };
        Ok(UasPreAuthenticationRequestData {
            service_details: None,
            transaction_details: Some(transaction_details),
            payment_details,
            authentication_info: None,
            merchant_details: merchant_details.cloned(),
            billing_address: billing_address.cloned(),
            acquirer_bin,
            acquirer_merchant_id,
        })
    }

    #[allow(clippy::too_many_arguments)]
    async fn pre_authentication(
        state: &SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        payment_method_data: Option<&domain::PaymentMethodData>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        amount: common_utils::types::MinorUnit,
        currency: Option<common_enums::Currency>,
        service_details: Option<payments::CtpServiceDetails>,
        merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        billing_address: Option<&hyperswitch_domain_models::address::Address>,
        acquirer_bin: Option<String>,
        acquirer_merchant_id: Option<String>,
    ) -> RouterResult<UasPreAuthenticationRouterData> {
        let pre_authentication_data = Self::get_pre_authentication_request_data(
            payment_method_data,
            service_details,
            amount,
            currency,
            merchant_details,
            billing_address,
            acquirer_bin,
            acquirer_merchant_id,
        )?;

        let pre_auth_router_data: UasPreAuthenticationRouterData =
            utils::construct_uas_router_data(
                state,
                connector_name.to_string(),
                payment_method,
                merchant_id.clone(),
                None,
                pre_authentication_data,
                merchant_connector_account,
                Some(authentication_id.to_owned()),
                payment_id.cloned(),
            )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            pre_auth_router_data,
        ))
        .await
    }

    fn get_authentication_request_data(
        browser_details: Option<BrowserInformation>,
        amount: Option<common_utils::types::MinorUnit>,
        currency: Option<common_enums::Currency>,
        message_category: MessageCategory,
        device_channel: payments::DeviceChannel,
        authentication: Authentication,
        return_url: Option<String>,
        sdk_information: Option<payments::SdkInformation>,
        threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        email: Option<common_utils::pii::Email>,
        webhook_url: String,
    ) -> RouterResult<UasAuthenticationRequestData> {
        Ok(UasAuthenticationRequestData {
            browser_details,
            transaction_details: TransactionDetails {
                amount,
                currency,
                device_channel: Some(device_channel),
                message_category: Some(message_category),
            },
            pre_authentication_data: PreAuthenticationData {
                threeds_server_transaction_id: authentication.threeds_server_transaction_id.ok_or(
                    ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.threeds_server_transaction_id",
                    },
                )?,
                message_version: authentication.message_version.ok_or(
                    ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.message_version",
                    },
                )?,
                acquirer_bin: authentication.acquirer_bin,
                acquirer_merchant_id: authentication.acquirer_merchant_id,
                acquirer_country_code: authentication.acquirer_country_code,
                connector_metadata: authentication.connector_metadata,
            },
            return_url,
            sdk_information,
            email,
            threeds_method_comp_ind,
            webhook_url,
        })
    }

    #[allow(clippy::too_many_arguments)]
    async fn authentication(
        state: &SessionState,
        business_profile: &domain::Profile,
        payment_method: &common_enums::PaymentMethod,
        browser_details: Option<BrowserInformation>,
        amount: Option<common_utils::types::MinorUnit>,
        currency: Option<common_enums::Currency>,
        message_category: MessageCategory,
        device_channel: payments::DeviceChannel,
        authentication: Authentication,
        return_url: Option<String>,
        sdk_information: Option<payments::SdkInformation>,
        threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        email: Option<common_utils::pii::Email>,
        webhook_url: String,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        payment_id: Option<common_utils::id_type::PaymentId>,
    ) -> RouterResult<UasAuthenticationRouterData> {
        let authentication_data =
            <Self as UnifiedAuthenticationService>::get_authentication_request_data(
                browser_details,
                amount,
                currency,
                message_category,
                device_channel,
                authentication.clone(),
                return_url,
                sdk_information,
                threeds_method_comp_ind,
                email,
                webhook_url,
            )?;
        let auth_router_data: UasAuthenticationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method.to_owned(),
            business_profile.merchant_id.clone(),
            None,
            authentication_data,
            merchant_connector_account,
            Some(authentication.authentication_id.to_owned()),
            payment_id,
        )?;

        Box::pin(utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            auth_router_data,
        ))
        .await
    }

    fn get_post_authentication_request_data(
        authentication: Option<Authentication>,
    ) -> RouterResult<UasPostAuthenticationRequestData> {
        Ok(UasPostAuthenticationRequestData {
            // authentication.threeds_server_transaction_id is mandatory for post-authentication in ExternalAuthentication
            threeds_server_transaction_id: Some(
                authentication
                    .and_then(|auth| auth.threeds_server_transaction_id)
                    .ok_or(ApiErrorResponse::MissingRequiredField {
                        field_name: "authentication.threeds_server_transaction_id",
                    })?,
            ),
        })
    }

    async fn post_authentication(
        state: &SessionState,
        business_profile: &domain::Profile,
        payment_id: Option<&common_utils::id_type::PaymentId>,
        merchant_connector_account: &MerchantConnectorAccountType,
        connector_name: &str,
        authentication_id: &common_utils::id_type::AuthenticationId,
        payment_method: common_enums::PaymentMethod,
        _merchant_id: &common_utils::id_type::MerchantId,
        authentication: Option<&Authentication>,
    ) -> RouterResult<UasPostAuthenticationRouterData> {
        let authentication_data =
            <Self as UnifiedAuthenticationService>::get_post_authentication_request_data(
                authentication.cloned(),
            )?;
        let auth_router_data: UasPostAuthenticationRouterData = utils::construct_uas_router_data(
            state,
            connector_name.to_string(),
            payment_method,
            business_profile.merchant_id.clone(),
            None,
            authentication_data,
            merchant_connector_account,
            Some(authentication_id.clone()),
            payment_id.cloned(),
        )?;

        utils::do_auth_connector_call(
            state,
            UNIFIED_AUTHENTICATION_SERVICE.to_string(),
            auth_router_data,
        )
        .await
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn create_new_authentication(
    state: &SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    authentication_connector: Option<String>,
    profile_id: common_utils::id_type::ProfileId,
    payment_id: Option<common_utils::id_type::PaymentId>,
    merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    authentication_id: &common_utils::id_type::AuthenticationId,
    service_details: Option<payments::CtpServiceDetails>,
    authentication_status: common_enums::AuthenticationStatus,
    network_token: Option<payment_method_data::NetworkTokenData>,
    organization_id: common_utils::id_type::OrganizationId,
    force_3ds_challenge: Option<bool>,
    psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    acquirer_bin: Option<String>,
    acquirer_merchant_id: Option<String>,
    acquirer_country_code: Option<String>,
    amount: Option<common_utils::types::MinorUnit>,
    currency: Option<common_enums::Currency>,
    return_url: Option<String>,
    profile_acquirer_id: Option<common_utils::id_type::ProfileAcquirerId>,
) -> RouterResult<Authentication> {
    let service_details_value = service_details
        .map(serde_json::to_value)
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable(
            "unable to parse service details into json value while inserting to DB",
        )?;
    let authentication_client_secret = Some(common_utils::generate_id_with_default_len(&format!(
        "{}_secret",
        authentication_id.get_string_repr()
    )));
    let new_authorization = AuthenticationNew {
        authentication_id: authentication_id.to_owned(),
        merchant_id,
        authentication_connector,
        connector_authentication_id: None,
        payment_method_id: "".to_string(),
        authentication_type: None,
        authentication_status,
        authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus::Unused,
        error_message: None,
        error_code: None,
        connector_metadata: None,
        maximum_supported_version: None,
        threeds_server_transaction_id: None,
        cavv: None,
        authentication_flow_type: None,
        message_version: None,
        eci: network_token.and_then(|data| data.eci),
        trans_status: None,
        acquirer_bin,
        acquirer_merchant_id,
        three_ds_method_data: None,
        three_ds_method_url: None,
        acs_url: None,
        challenge_request: None,
        challenge_request_key: None,
        acs_reference_number: None,
        acs_trans_id: None,
        acs_signed_content: None,
        profile_id,
        payment_id,
        merchant_connector_id,
        ds_trans_id: None,
        directory_server_id: None,
        acquirer_country_code,
        service_details: service_details_value,
        organization_id,
        authentication_client_secret,
        force_3ds_challenge,
        psd2_sca_exemption_type,
        return_url,
        amount,
        currency,
        billing_address: None,
        shipping_address: None,
        browser_info: None,
        email: None,
        profile_acquirer_id,
        challenge_code: None,
        challenge_cancel: None,
        challenge_code_reason: None,
        message_extension: None,
    };
    state
        .store
        .insert_authentication(new_authorization)
        .await
        .to_duplicate_response(ApiErrorResponse::GenericDuplicateError {
            message: format!(
                "Authentication with authentication_id {} already exists",
                authentication_id.get_string_repr()
            ),
        })
}

// Modular authentication
#[cfg(feature = "v1")]
pub async fn authentication_create_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationCreateRequest,
) -> RouterResponse<AuthenticationResponse> {
    let db = &*state.store;
    let merchant_account = merchant_context.get_merchant_account();
    let merchant_id = merchant_account.get_id();
    let key_manager_state = (&state).into();
    let profile_id = core_utils::get_profile_id_from_business_details(
        &key_manager_state,
        None,
        None,
        &merchant_context,
        req.profile_id.as_ref(),
        db,
        true,
    )
    .await?;

    let business_profile = db
        .find_business_profile_by_profile_id(
            &key_manager_state,
            merchant_context.get_merchant_key_store(),
            &profile_id,
        )
        .await
        .to_not_found_response(ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;
    let organization_id = merchant_account.organization_id.clone();
    let authentication_id = common_utils::id_type::AuthenticationId::generate_authentication_id(
        consts::AUTHENTICATION_ID_PREFIX,
    );

    let force_3ds_challenge = Some(
        req.force_3ds_challenge
            .unwrap_or(business_profile.force_3ds_challenge),
    );

    // Priority logic: First check req.acquirer_details, then fallback to profile_acquirer_id lookup
    let (acquirer_bin, acquirer_merchant_id, acquirer_country_code) =
        if let Some(acquirer_details) = &req.acquirer_details {
            // Priority 1: Use acquirer_details from request if present
            (
                acquirer_details.acquirer_bin.clone(),
                acquirer_details.acquirer_merchant_id.clone(),
                acquirer_details.merchant_country_code.clone(),
            )
        } else {
            // Priority 2: Fallback to profile_acquirer_id lookup
            let acquirer_details = req.profile_acquirer_id.clone().and_then(|acquirer_id| {
                business_profile
                    .acquirer_config_map
                    .and_then(|acquirer_config_map| {
                        acquirer_config_map.0.get(&acquirer_id).cloned()
                    })
            });

            acquirer_details
                .as_ref()
                .map(|details| {
                    (
                        Some(details.acquirer_bin.clone()),
                        Some(details.acquirer_assigned_merchant_id.clone()),
                        business_profile
                            .merchant_country_code
                            .map(|code| code.get_country_code().to_owned()),
                    )
                })
                .unwrap_or((None, None, None))
        };

    let new_authentication = create_new_authentication(
        &state,
        merchant_id.clone(),
        req.authentication_connector
            .map(|connector| connector.to_string()),
        profile_id.clone(),
        None,
        None,
        &authentication_id,
        None,
        common_enums::AuthenticationStatus::Started,
        None,
        organization_id,
        force_3ds_challenge,
        req.psd2_sca_exemption_type,
        acquirer_bin,
        acquirer_merchant_id,
        acquirer_country_code,
        Some(req.amount),
        Some(req.currency),
        req.return_url,
        req.profile_acquirer_id.clone(),
    )
    .await?;

    let acquirer_details = Some(AcquirerDetails {
        acquirer_bin: new_authentication.acquirer_bin.clone(),
        acquirer_merchant_id: new_authentication.acquirer_merchant_id.clone(),
        merchant_country_code: new_authentication.acquirer_country_code.clone(),
    });

    let amount = new_authentication
        .amount
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("amount failed to get amount from authentication table")?;
    let currency = new_authentication
        .currency
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("currency failed to get currency from authentication table")?;

    let response = AuthenticationResponse::foreign_try_from((
        new_authentication.clone(),
        amount,
        currency,
        profile_id,
        acquirer_details,
        new_authentication.profile_acquirer_id,
    ))?;

    Ok(hyperswitch_domain_models::api::ApplicationResponse::Json(
        response,
    ))
}

impl
    ForeignTryFrom<(
        Authentication,
        common_utils::types::MinorUnit,
        common_enums::Currency,
        common_utils::id_type::ProfileId,
        Option<AcquirerDetails>,
        Option<common_utils::id_type::ProfileAcquirerId>,
    )> for AuthenticationResponse
{
    type Error = error_stack::Report<ApiErrorResponse>;
    fn foreign_try_from(
        (authentication, amount, currency, profile_id, acquirer_details, profile_acquirer_id): (
            Authentication,
            common_utils::types::MinorUnit,
            common_enums::Currency,
            common_utils::id_type::ProfileId,
            Option<AcquirerDetails>,
            Option<common_utils::id_type::ProfileAcquirerId>,
        ),
    ) -> Result<Self, Self::Error> {
        let authentication_connector = authentication
            .authentication_connector
            .map(|connector| common_enums::AuthenticationConnectors::from_str(&connector))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Incorrect authentication connector stored in table")?;
        Ok(Self {
            authentication_id: authentication.authentication_id,
            client_secret: authentication
                .authentication_client_secret
                .map(masking::Secret::new),
            amount,
            currency,
            force_3ds_challenge: authentication.force_3ds_challenge,
            merchant_id: authentication.merchant_id,
            status: authentication.authentication_status,
            authentication_connector,
            return_url: authentication.return_url,
            created_at: Some(authentication.created_at),
            error_code: authentication.error_code,
            error_message: authentication.error_message,
            profile_id: Some(profile_id),
            psd2_sca_exemption_type: authentication.psd2_sca_exemption_type,
            acquirer_details,
            profile_acquirer_id,
        })
    }
}

#[cfg(feature = "v1")]
impl
    ForeignTryFrom<(
        Authentication,
        api_models::authentication::NextAction,
        common_utils::id_type::ProfileId,
        Option<payments::Address>,
        Option<payments::Address>,
        Option<payments::BrowserInformation>,
        common_utils::crypto::OptionalEncryptableEmail,
    )> for AuthenticationEligibilityResponse
{
    type Error = error_stack::Report<ApiErrorResponse>;
    fn foreign_try_from(
        (authentication, next_action, profile_id, billing, shipping, browser_information, email): (
            Authentication,
            api_models::authentication::NextAction,
            common_utils::id_type::ProfileId,
            Option<payments::Address>,
            Option<payments::Address>,
            Option<payments::BrowserInformation>,
            common_utils::crypto::OptionalEncryptableEmail,
        ),
    ) -> Result<Self, Self::Error> {
        let authentication_connector = authentication
            .authentication_connector
            .map(|connector| common_enums::AuthenticationConnectors::from_str(&connector))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Incorrect authentication connector stored in table")?;
        let three_ds_method_url = authentication
            .three_ds_method_url
            .map(|url| url::Url::parse(&url))
            .transpose()
            .map_err(error_stack::Report::from)
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse three_ds_method_url")?;

        let three_ds_data = Some(api_models::authentication::ThreeDsData {
            three_ds_server_transaction_id: authentication.threeds_server_transaction_id,
            maximum_supported_3ds_version: authentication.maximum_supported_version,
            connector_authentication_id: authentication.connector_authentication_id,
            three_ds_method_data: authentication.three_ds_method_data,
            three_ds_method_url,
            message_version: authentication.message_version,
            directory_server_id: authentication.directory_server_id,
        });
        let acquirer_details = AcquirerDetails {
            acquirer_bin: authentication.acquirer_bin,
            acquirer_merchant_id: authentication.acquirer_merchant_id,
            merchant_country_code: authentication.acquirer_country_code,
        };
        Ok(Self {
            authentication_id: authentication.authentication_id,
            next_action,
            status: authentication.authentication_status,
            eligibility_response_params: three_ds_data
                .map(api_models::authentication::EligibilityResponseParams::ThreeDsData),
            connector_metadata: authentication.connector_metadata,
            profile_id,
            error_message: authentication.error_message,
            error_code: authentication.error_code,
            billing,
            shipping,
            authentication_connector,
            browser_information,
            email,
            acquirer_details: Some(acquirer_details),
        })
    }
}

#[cfg(feature = "v1")]
pub async fn authentication_eligibility_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: AuthenticationEligibilityRequest,
    authentication_id: common_utils::id_type::AuthenticationId,
) -> RouterResponse<AuthenticationEligibilityResponse> {
    let merchant_account = merchant_context.get_merchant_account();
    let merchant_id = merchant_account.get_id();
    let db = &*state.store;
    let authentication = db
        .find_authentication_by_merchant_id_authentication_id(merchant_id, &authentication_id)
        .await
        .to_not_found_response(ApiErrorResponse::AuthenticationNotFound {
            id: authentication_id.get_string_repr().to_owned(),
        })?;

    req.client_secret
        .clone()
        .map(|client_secret| {
            utils::authenticate_authentication_client_secret_and_check_expiry(
                client_secret.peek(),
                &authentication,
            )
        })
        .transpose()?;

    ensure_not_terminal_status(authentication.trans_status.clone())?;

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

    let profile_id = core_utils::get_profile_id_from_business_details(
        &key_manager_state,
        None,
        None,
        &merchant_context,
        req.profile_id.as_ref(),
        db,
        true,
    )
    .await?;

    let business_profile = db
        .find_business_profile_by_profile_id(
            &key_manager_state,
            merchant_context.get_merchant_key_store(),
            &profile_id,
        )
        .await
        .to_not_found_response(ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let (authentication_connector, three_ds_connector_account) =
        auth_utils::get_authentication_connector_data(
            &state,
            merchant_context.get_merchant_key_store(),
            &business_profile,
            authentication.authentication_connector.clone(),
        )
        .await?;

    let notification_url = match authentication_connector {
        common_enums::AuthenticationConnectors::Juspaythreedsserver => {
            Some(url::Url::parse(&format!(
                "{base_url}/authentication/{merchant_id}/{authentication_id}/redirect",
                base_url = state.base_url,
                merchant_id = merchant_id.get_string_repr(),
                authentication_id = authentication_id.get_string_repr()
            )))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse notification url")?
        }
        _ => authentication
            .return_url
            .as_ref()
            .map(|url| url::Url::parse(url))
            .transpose()
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse return url")?,
    };

    let authentication_connector_name = authentication_connector.to_string();

    let payment_method_data = domain::PaymentMethodData::from(req.payment_method_data.clone());

    let amount = authentication
        .amount
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("no amount found in authentication table")?;

    let acquirer_details = authentication
        .profile_acquirer_id
        .clone()
        .and_then(|acquirer_id| {
            business_profile
                .acquirer_config_map
                .and_then(|acquirer_config_map| acquirer_config_map.0.get(&acquirer_id).cloned())
        });

    let metadata: Option<ThreeDsMetaData> = three_ds_connector_account
        .get_metadata()
        .map(|metadata| {
            metadata.expose().parse_value("ThreeDsMetaData").inspect_err(|err| {
            router_env::logger::warn!(parsing_error=?err,"Error while parsing ThreeDsMetaData");
        })
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)?;

    let merchant_country_code = authentication.acquirer_country_code.clone();

    let merchant_details = Some(hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails {
        merchant_id: Some(authentication.merchant_id.get_string_repr().to_string()),
        merchant_name: acquirer_details.clone().map(|detail| detail.merchant_name.clone()).or(metadata.clone().and_then(|metadata| metadata.merchant_name)),
        merchant_category_code: business_profile.merchant_category_code.or(metadata.clone().and_then(|metadata| metadata.merchant_category_code)),
        endpoint_prefix: metadata.clone().map(|metadata| metadata.endpoint_prefix),
        three_ds_requestor_url: business_profile.authentication_connector_details.map(|details| details.three_ds_requestor_url),
        three_ds_requestor_id: metadata.clone().and_then(|metadata| metadata.three_ds_requestor_id),
        three_ds_requestor_name: metadata.clone().and_then(|metadata| metadata.three_ds_requestor_name),
        merchant_country_code: merchant_country_code.map(common_types::payments::MerchantCountryCode::new),
        notification_url,
    });

    let domain_address = req
        .billing
        .clone()
        .map(hyperswitch_domain_models::address::Address::from);

    let pre_auth_response =
        <ExternalAuthentication as UnifiedAuthenticationService>::pre_authentication(
            &state,
            merchant_id,
            None,
            Some(&payment_method_data),
            &three_ds_connector_account,
            &authentication_connector_name,
            &authentication_id,
            req.payment_method,
            amount,
            authentication.currency,
            None,
            merchant_details.as_ref(),
            domain_address.as_ref(),
            authentication.acquirer_bin.clone(),
            authentication.acquirer_merchant_id.clone(),
        )
        .await?;

    let billing_details_encoded = req
        .billing
        .clone()
        .map(|billing| {
            common_utils::ext_traits::Encode::encode_to_value(&billing)
                .map(masking::Secret::<serde_json::Value>::new)
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encode billing details to serde_json::Value")?;

    let shipping_details_encoded = req
        .shipping
        .clone()
        .map(|shipping| {
            common_utils::ext_traits::Encode::encode_to_value(&shipping)
                .map(masking::Secret::<serde_json::Value>::new)
        })
        .transpose()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encode shipping details to serde_json::Value")?;

    let encrypted_data = domain::types::crypto_operation(
        &key_manager_state,
        common_utils::type_name!(hyperswitch_domain_models::authentication::Authentication),
        domain::types::CryptoOperation::BatchEncrypt(
            hyperswitch_domain_models::authentication::UpdateEncryptableAuthentication::to_encryptable(
                hyperswitch_domain_models::authentication::UpdateEncryptableAuthentication {
                    billing_address: billing_details_encoded,
                    shipping_address: shipping_details_encoded,
                },
            ),
        ),
        common_utils::types::keymanager::Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        ),
        merchant_context.get_merchant_key_store().key.peek(),
    )
    .await
    .and_then(|val| val.try_into_batchoperation())
    .change_context(ApiErrorResponse::InternalServerError)
    .attach_printable("Unable to encrypt authentication data".to_string())?;

    let encrypted_data = hyperswitch_domain_models::authentication::FromRequestEncryptableAuthentication::from_encryptable(encrypted_data)
        .change_context(ApiErrorResponse::InternalServerError)


// Implementation: impl api::PaymentAuthorize for for Paypal
// File: crates/hyperswitch_connectors/src/connectors/paypal.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentAuthorize for for Paypal


// Implementation: impl ConnectorSpecifications for for Gpayments
// File: crates/hyperswitch_connectors/src/connectors/gpayments.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl ConnectorSpecifications for for Gpayments


// Function: into_inner
// File: crates/diesel_models/src/lib.rs
// Module: diesel_models

// Documentation: Extracts the inner value from the wrapper

pub fn into_inner(self) -> T


// Struct: AchBankDebitAdditionalData
// File: crates/api_models/src/payments/additional_info.rs
// Module: api_models
// Implementations: 0

pub struct AchBankDebitAdditionalData


// Implementation: impl KafkaAuthenticationEvent
// File: crates/router/src/services/kafka/authentication_event.rs
// Module: router
// Methods: 1 total (1 public)

impl KafkaAuthenticationEvent


// Function: get_step_up_enabled_key
// File: crates/common_utils/src/id_type/merchant.rs
// Module: common_utils

// Documentation: get step up enabled key

pub fn get_step_up_enabled_key(&self) -> String


// Implementation: impl UserFromStorage
// File: crates/router/src/types/domain/user.rs
// Module: router
// Methods: 11 total (9 public)

impl UserFromStorage


// Struct: Merchant
// File: crates/hyperswitch_connectors/src/connectors/affirm/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Merchant


// Implementation: impl Organization
// File: crates/router/src/routes/app.rs
// Module: router
// Methods: 1 total (1 public)

impl Organization


// File: crates/hyperswitch_connectors/src/connectors/netcetera.rs
// Module: hyperswitch_connectors
// Public structs: 1

pub mod netcetera_types;
pub mod transformers;

use std::fmt::Debug;

use api_models::webhooks::{AuthenticationIdType, IncomingWebhookEvent, ObjectReferenceId};
use common_utils::{
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    request::{Method, Request, RequestBuilder, RequestContent},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        authentication::{
            Authentication, PostAuthentication, PreAuthentication, PreAuthenticationVersionCall,
        },
        AccessTokenAuth, Authorize, Capture, Execute, PSync, PaymentMethodToken, RSync, Session,
        SetupMandate, Void,
    },
    router_request_types::{
        authentication::{
            ConnectorAuthenticationRequestData, ConnectorPostAuthenticationRequestData,
            PreAuthNRequestData,
        },
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        AuthenticationResponseData, ConnectorInfo, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods,
    },
};
use hyperswitch_interfaces::{
    api::{
        authentication::{
            ConnectorAuthentication, ConnectorPostAuthentication, ConnectorPreAuthentication,
            ConnectorPreAuthenticationVersionCall, ExternalAuthentication,
        },
        *,
    },
    authentication::ExternalAuthenticationPayload,
    configs::Connectors,
    errors::ConnectorError,
    events::connector_api_logs::ConnectorEvent,
    types::Response,
    webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
};
use masking::Maskable;
use transformers as netcetera;

use crate::{
    constants::headers,
    types::{
        ConnectorAuthenticationRouterData, ConnectorAuthenticationType,
        ConnectorPreAuthenticationType, PreAuthNRouterData, ResponseRouterData,
    },
};

#[derive(Debug, Clone)]
pub struct Netcetera;

impl Payment for Netcetera {}
impl PaymentSession for Netcetera {}
impl ConnectorAccessToken for Netcetera {}
impl MandateSetup for Netcetera {}
impl PaymentAuthorize for Netcetera {}
impl PaymentSync for Netcetera {}
impl PaymentCapture for Netcetera {}
impl PaymentVoid for Netcetera {}
impl Refund for Netcetera {}
impl RefundExecute for Netcetera {}
impl RefundSync for Netcetera {}
impl PaymentToken for Netcetera {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Netcetera
{
    // Not Implemented (R)
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Netcetera
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }
}

impl ConnectorCommon for Netcetera {
    fn id(&self) -> &'static str {
        "netcetera"
    }

    fn get_currency_unit(&self) -> CurrencyUnit {
        CurrencyUnit::Minor
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.netcetera.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        _auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        Ok(vec![])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        let response: netcetera::NetceteraErrorResponse = res
            .response
            .parse_struct("NetceteraErrorResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.error_details.error_code,
            message: response.error_details.error_description,
            reason: response.error_details.error_detail,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Netcetera {}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Netcetera {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Netcetera {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
    for Netcetera
{
}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Netcetera {}

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Netcetera {}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Netcetera {}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Netcetera {}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Netcetera {}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Netcetera {}

#[async_trait::async_trait]
impl IncomingWebhook for Netcetera {
    fn get_webhook_object_reference_id(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<ObjectReferenceId, ConnectorError> {
        let webhook_body: netcetera::ResultsResponseData = request
            .body
            .parse_struct("netcetera ResultsResponseData")
            .change_context(ConnectorError::WebhookBodyDecodingFailed)?;
        Ok(ObjectReferenceId::ExternalAuthenticationID(
            AuthenticationIdType::ConnectorAuthenticationId(webhook_body.three_ds_server_trans_id),
        ))
    }

    fn get_webhook_event_type(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, ConnectorError> {
        Ok(IncomingWebhookEvent::ExternalAuthenticationARes)
    }

    fn get_webhook_resource_object(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, ConnectorError> {
        let webhook_body_value: netcetera::ResultsResponseData = request
            .body
            .parse_struct("netcetera ResultsResponseDatae")
            .change_context(ConnectorError::WebhookBodyDecodingFailed)?;
        Ok(Box::new(webhook_body_value))
    }

    fn get_external_authentication_details(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<ExternalAuthenticationPayload, ConnectorError> {
        let webhook_body: netcetera::ResultsResponseData = request
            .body
            .parse_struct("netcetera ResultsResponseData")
            .change_context(ConnectorError::WebhookBodyDecodingFailed)?;

        let challenge_cancel = webhook_body
            .results_request
            .as_ref()
            .and_then(|v| v.get("challengeCancel").and_then(|v| v.as_str()))
            .map(|s| s.to_string());

        let challenge_code_reason = webhook_body
            .results_request
            .as_ref()
            .and_then(|v| v.get("transStatusReason").and_then(|v| v.as_str()))
            .map(|s| s.to_string());

        Ok(ExternalAuthenticationPayload {
            trans_status: webhook_body
                .trans_status
                .unwrap_or(common_enums::TransactionStatus::InformationOnly),
            authentication_value: webhook_body.authentication_value,
            eci: webhook_body.eci,
            challenge_cancel,
            challenge_code_reason,
        })
    }
}

fn build_endpoint(
    base_url: &str,
    connector_metadata: &Option<common_utils::pii::SecretSerdeValue>,
) -> CustomResult<String, ConnectorError> {
    let metadata = netcetera::NetceteraMetaData::try_from(connector_metadata)?;
    let endpoint_prefix = metadata.endpoint_prefix;
    Ok(base_url.replace("{{merchant_endpoint_prefix}}", &endpoint_prefix))
}

impl ConnectorPreAuthentication for Netcetera {}
impl ConnectorPreAuthenticationVersionCall for Netcetera {}
impl ExternalAuthentication for Netcetera {}
impl ConnectorAuthentication for Netcetera {}
impl ConnectorPostAuthentication for Netcetera {}

impl ConnectorIntegration<PreAuthentication, PreAuthNRequestData, AuthenticationResponseData>
    for Netcetera
{
    fn get_headers(
        &self,
        req: &PreAuthNRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PreAuthNRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        let base_url = build_endpoint(self.base_url(connectors), &req.connector_meta_data)?;
        Ok(format!("{base_url}/3ds/versioning"))
    }

    fn get_request_body(
        &self,
        req: &PreAuthNRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_router_data = netcetera::NetceteraRouterData::try_from((0, req))?;
        let req_obj =
            netcetera::NetceteraPreAuthenticationRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(req_obj)))
    }

    fn build_request(
        &self,
        req: &PreAuthNRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let netcetera_auth_type = netcetera::NetceteraAuthType::try_from(&req.connector_auth_type)?;
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&ConnectorPreAuthenticationType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(ConnectorPreAuthenticationType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(ConnectorPreAuthenticationType::get_request_body(
                    self, req, connectors,
                )?)
                .add_certificate(Some(netcetera_auth_type.certificate))
                .add_certificate_key(Some(netcetera_auth_type.private_key))
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PreAuthNRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PreAuthNRouterData, ConnectorError> {
        let response: netcetera::NetceteraPreAuthenticationResponse = res
            .response
            .parse_struct("netcetera NetceteraPreAuthenticationResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl
    ConnectorIntegration<
        Authentication,
        ConnectorAuthenticationRequestData,
        AuthenticationResponseData,
    > for Netcetera
{
    fn get_headers(
        &self,
        req: &ConnectorAuthenticationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &ConnectorAuthenticationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        let base_url = build_endpoint(self.base_url(connectors), &req.connector_meta_data)?;
        Ok(format!("{base_url}/3ds/authentication"))
    }

    fn get_request_body(
        &self,
        req: &ConnectorAuthenticationRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let connector_router_data = netcetera::NetceteraRouterData::try_from((
            &self.get_currency_unit(),
            req.request
                .currency
                .ok_or(ConnectorError::MissingRequiredField {
                    field_name: "currency",
                })?,
            req.request
                .amount
                .ok_or(ConnectorError::MissingRequiredField {
                    field_name: "amount",
                })?,
            req,
        ))?;
        let req_obj = netcetera::NetceteraAuthenticationRequest::try_from(&connector_router_data);
        Ok(RequestContent::Json(Box::new(req_obj?)))
    }

    fn build_request(
        &self,
        req: &ConnectorAuthenticationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let netcetera_auth_type = netcetera::NetceteraAuthType::try_from(&req.connector_auth_type)?;
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&ConnectorAuthenticationType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(ConnectorAuthenticationType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(ConnectorAuthenticationType::get_request_body(
                    self, req, connectors,
                )?)
                .add_certificate(Some(netcetera_auth_type.certificate))
                .add_certificate_key(Some(netcetera_auth_type.private_key))
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &ConnectorAuthenticationRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<ConnectorAuthenticationRouterData, ConnectorError> {
        let response: netcetera::NetceteraAuthenticationResponse = res
            .response
            .parse_struct("NetceteraAuthenticationResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl
    ConnectorIntegration<
        PostAuthentication,
        ConnectorPostAuthenticationRequestData,
        AuthenticationResponseData,
    > for Netcetera
{
}

impl
    ConnectorIntegration<
        PreAuthenticationVersionCall,
        PreAuthNRequestData,
        AuthenticationResponseData,
    > for Netcetera
{
}

static NETCETERA_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Netcetera",
    description: "Netcetera authentication provider for comprehensive 3D Secure solutions including certified ACS, Directory Server, and multi-protocol EMV 3DS supports",
    connector_type: common_enums::HyperswitchConnectorCategory::AuthenticationProvider,
    integration_status: common_enums::ConnectorIntegrationStatus::Sandbox,
};

impl ConnectorSpecifications for Netcetera {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&NETCETERA_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::enums::EventClass]> {
        None
    }
}


// Struct: Data
// File: crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Data


// Function: server
// File: crates/router/src/routes/app.rs
// Module: router

pub fn server(state: AppState) -> Scope


// Struct: OrganizationUpdateRequest
// File: crates/api_models/src/organization.rs
// Module: api_models
// Implementations: 0

pub struct OrganizationUpdateRequest


// Implementation: impl Responder
// File: crates/router/src/analytics.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Implementation: impl webhooks::IncomingWebhook for for Celero
// File: crates/hyperswitch_connectors/src/connectors/celero.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl webhooks::IncomingWebhook for for Celero


// Function: confirm_payment_method_intent_api
// File: crates/openapi/src/routes/payment_method.rs
// Module: openapi

pub fn confirm_payment_method_intent_api()


// Implementation: impl ConnectorValidation for for Shift4
// File: crates/hyperswitch_connectors/src/connectors/shift4.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorValidation for for Shift4


// Function: get_blocked_count_from_cache
// File: crates/router/src/services/card_testing_guard.rs
// Module: router

pub fn get_blocked_count_from_cache<A>(
    state: &A,
    cache_key: &str,
) -> RouterResult<Option<i32>>
where
    A: SessionStateInfo + Sync,


// Implementation: impl Payment for for Netcetera
// File: crates/hyperswitch_connectors/src/connectors/netcetera.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl Payment for for Netcetera


// Struct: InstructedAmount
// File: crates/hyperswitch_connectors/src/connectors/nordea/requests.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct InstructedAmount


// Function: get_pm_auth_key
// File: crates/common_utils/src/id_type/payment.rs
// Module: common_utils

// Documentation: Generate a key for pm_auth

pub fn get_pm_auth_key(&self) -> String


// Struct: CompleteAuthorize
// File: crates/router/src/core/payments/operations/payment_complete_authorize.rs
// Module: router
// Implementations: 0

pub struct CompleteAuthorize


// Implementation: impl Deref for for AlwaysRequestExtendedAuthorization
// File: crates/common_types/src/primitive_wrappers.rs
// Module: common_types
// Methods: 1 total (0 public)

impl Deref for for AlwaysRequestExtendedAuthorization


// Function: get_next_connector
// File: crates/router/src/core/payouts.rs
// Module: router

pub fn get_next_connector(
    connectors: &mut IntoIter<api::ConnectorRoutingData>,
) -> RouterResult<api::ConnectorRoutingData>


// Struct: FiservemeaPaymentCard
// File: crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct FiservemeaPaymentCard


// Function: get_payout_connector_config
// File: crates/connector_configs/src/connector.rs
// Module: connector_configs

pub fn get_payout_connector_config(
        connector: PayoutConnectors,
    ) -> Result<Option<ConnectorTomlConfig>, String>


// Struct: ClientTokenResponse
// File: crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ClientTokenResponse


// Implementation: impl PaymentVoid for for Plaid
// File: crates/hyperswitch_connectors/src/connectors/plaid.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl PaymentVoid for for Plaid


// Struct: PlacetopayStatusResponse
// File: crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PlacetopayStatusResponse


// Struct: ChatListResponse
// File: crates/api_models/src/chat.rs
// Module: api_models
// Implementations: 0

pub struct ChatListResponse


// Struct: JWTAuthMerchantAndProfileFromRoute
// File: crates/router/src/services/authentication.rs
// Module: router
// Implementations: 0

pub struct JWTAuthMerchantAndProfileFromRoute


// File: crates/router/src/routes/currency.rs
// Module: router
// Public functions: 2

use actix_web::{web, HttpRequest, HttpResponse};
use router_env::Flow;

use crate::{
    core::{api_locking, currency},
    routes::AppState,
    services::{api, authentication as auth},
};

#[cfg(feature = "v1")]
pub async fn retrieve_forex(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
    let flow = Flow::RetrieveForexFlow;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        (),
        |state, _auth: auth::AuthenticationData, _, _| currency::retrieve_forex(state),
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::DashboardNoPermissionAuth,
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
pub async fn convert_forex(
    state: web::Data<AppState>,
    req: HttpRequest,
    params: web::Query<api_models::currency::CurrencyConversionParams>,
) -> HttpResponse {
    let flow = Flow::RetrieveForexFlow;
    let amount = params.amount;
    let to_currency = &params.to_currency;
    let from_currency = &params.from_currency;
    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &req,
        (),
        |state, _: auth::AuthenticationData, _, _| {
            currency::convert_forex(
                state,
                amount.get_amount_as_i64(),
                to_currency.to_string(),
                from_currency.to_string(),
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::DashboardNoPermissionAuth,
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// Struct: EmailSettings
// File: crates/external_services/src/email.rs
// Module: external_services
// Implementations: 1

pub struct EmailSettings