// File: crates/router/src/core/payment_methods.rs
// Module: router
// Public functions: 46
// Public structs: 1

pub mod cards;
pub mod migration;
pub mod network_tokenization;
pub mod surcharge_decision_configs;
#[cfg(feature = "v1")]
pub mod tokenize;
pub mod transformers;
pub mod utils;
mod validator;
pub mod vault;
use std::borrow::Cow;
#[cfg(feature = "v1")]
use std::collections::HashSet;
#[cfg(feature = "v2")]
use std::str::FromStr;

#[cfg(feature = "v2")]
pub use api_models::enums as api_enums;
pub use api_models::enums::Connector;
use api_models::payment_methods;
#[cfg(feature = "payouts")]
pub use api_models::{enums::PayoutConnectors, payouts as payout_types};
#[cfg(feature = "v1")]
use common_utils::{consts::DEFAULT_LOCALE, ext_traits::OptionExt};
#[cfg(feature = "v2")]
use common_utils::{
    crypto::Encryptable,
    errors::CustomResult,
    ext_traits::{AsyncExt, ValueExt},
    fp_utils::when,
    generate_id, types as util_types,
};
use common_utils::{ext_traits::Encode, id_type};
use diesel_models::{
    enums, GenericLinkNew, PaymentMethodCollectLink, PaymentMethodCollectLinkData,
};
use error_stack::{report, ResultExt};
#[cfg(feature = "v2")]
use futures::TryStreamExt;
#[cfg(feature = "v1")]
use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData};
use hyperswitch_domain_models::{
    payments::{payment_attempt::PaymentAttempt, PaymentIntent, VaultData},
    router_data_v2::flow_common_types::VaultConnectorFlowData,
    router_flow_types::ExternalVaultInsertFlow,
    types::VaultRouterData,
};
use hyperswitch_interfaces::connector_integration_interface::RouterDataConversion;
use masking::{PeekInterface, Secret};
use router_env::{instrument, tracing};
use time::Duration;

#[cfg(feature = "v2")]
use super::payments::tokenization;
use super::{
    errors::{RouterResponse, StorageErrorExt},
    pm_auth,
};
#[cfg(feature = "v2")]
use crate::{
    configs::settings,
    core::{payment_methods::transformers as pm_transforms, tokenization as tokenization_core},
    headers,
    routes::{self, payment_methods as pm_routes},
    services::encryption,
    types::{
        api::PaymentMethodCreateExt,
        domain::types as domain_types,
        storage::{ephemeral_key, PaymentMethodListContext},
        transformers::{ForeignFrom, ForeignTryFrom},
        Tokenizable,
    },
    utils::ext_traits::OptionExt,
};
use crate::{
    consts,
    core::{
        errors::{ProcessTrackerError, RouterResult},
        payments::{self as payments_core, helpers as payment_helpers},
        utils as core_utils,
    },
    db::errors::ConnectorErrorExt,
    errors, logger,
    routes::{app::StorageInterface, SessionState},
    services,
    types::{
        self, api, domain, payment_methods as pm_types,
        storage::{self, enums as storage_enums},
    },
};

const PAYMENT_METHOD_STATUS_UPDATE_TASK: &str = "PAYMENT_METHOD_STATUS_UPDATE";
const PAYMENT_METHOD_STATUS_TAG: &str = "PAYMENT_METHOD_STATUS";

#[instrument(skip_all)]
pub async fn retrieve_payment_method_core(
    pm_data: &Option<domain::PaymentMethodData>,
    state: &SessionState,
    payment_intent: &PaymentIntent,
    payment_attempt: &PaymentAttempt,
    merchant_key_store: &domain::MerchantKeyStore,
    business_profile: Option<&domain::Profile>,
) -> RouterResult<(Option<domain::PaymentMethodData>, Option<String>)> {
    match pm_data {
        pm_opt @ Some(pm @ domain::PaymentMethodData::Card(_)) => {
            let payment_token = payment_helpers::store_payment_method_data_in_vault(
                state,
                payment_attempt,
                payment_intent,
                enums::PaymentMethod::Card,
                pm,
                merchant_key_store,
                business_profile,
            )
            .await?;
            Ok((pm_opt.to_owned(), payment_token))
        }
        pm_opt @ Some(pm @ domain::PaymentMethodData::BankDebit(_)) => {
            let payment_token = payment_helpers::store_payment_method_data_in_vault(
                state,
                payment_attempt,
                payment_intent,
                enums::PaymentMethod::BankDebit,
                pm,
                merchant_key_store,
                business_profile,
            )
            .await?;

            Ok((pm_opt.to_owned(), payment_token))
        }
        pm @ Some(domain::PaymentMethodData::PayLater(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::Crypto(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::Upi(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::Voucher(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::Reward) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::RealTimePayment(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::CardRedirect(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::GiftCard(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::OpenBanking(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::MobilePayment(_)) => Ok((pm.to_owned(), None)),
        pm @ Some(domain::PaymentMethodData::NetworkToken(_)) => Ok((pm.to_owned(), None)),
        pm_opt @ Some(pm @ domain::PaymentMethodData::BankTransfer(_)) => {
            let payment_token = payment_helpers::store_payment_method_data_in_vault(
                state,
                payment_attempt,
                payment_intent,
                enums::PaymentMethod::BankTransfer,
                pm,
                merchant_key_store,
                business_profile,
            )
            .await?;

            Ok((pm_opt.to_owned(), payment_token))
        }
        pm_opt @ Some(pm @ domain::PaymentMethodData::Wallet(_)) => {
            let payment_token = payment_helpers::store_payment_method_data_in_vault(
                state,
                payment_attempt,
                payment_intent,
                enums::PaymentMethod::Wallet,
                pm,
                merchant_key_store,
                business_profile,
            )
            .await?;

            Ok((pm_opt.to_owned(), payment_token))
        }
        pm_opt @ Some(pm @ domain::PaymentMethodData::BankRedirect(_)) => {
            let payment_token = payment_helpers::store_payment_method_data_in_vault(
                state,
                payment_attempt,
                payment_intent,
                enums::PaymentMethod::BankRedirect,
                pm,
                merchant_key_store,
                business_profile,
            )
            .await?;

            Ok((pm_opt.to_owned(), payment_token))
        }
        _ => Ok((None, None)),
    }
}

pub async fn initiate_pm_collect_link(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: payment_methods::PaymentMethodCollectLinkRequest,
) -> RouterResponse<payment_methods::PaymentMethodCollectLinkResponse> {
    // Validate request and initiate flow
    let pm_collect_link_data =
        validator::validate_request_and_initiate_payment_method_collect_link(
            &state,
            &merchant_context,
            &req,
        )
        .await?;

    // Create DB entry
    let pm_collect_link = create_pm_collect_db_entry(
        &state,
        &merchant_context,
        &pm_collect_link_data,
        req.return_url.clone(),
    )
    .await?;
    let customer_id = id_type::CustomerId::try_from(Cow::from(pm_collect_link.primary_reference))
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
        field_name: "customer_id",
    })?;

    // Return response
    let url = pm_collect_link.url.peek();
    let response = payment_methods::PaymentMethodCollectLinkResponse {
        pm_collect_link_id: pm_collect_link.link_id,
        customer_id,
        expiry: pm_collect_link.expiry,
        link: url::Url::parse(url)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable_lazy(|| {
                format!("Failed to parse the payment method collect link - {url}")
            })?
            .into(),
        return_url: pm_collect_link.return_url,
        ui_config: pm_collect_link.link_data.ui_config,
        enabled_payment_methods: pm_collect_link.link_data.enabled_payment_methods,
    };
    Ok(services::ApplicationResponse::Json(response))
}

pub async fn create_pm_collect_db_entry(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    pm_collect_link_data: &PaymentMethodCollectLinkData,
    return_url: Option<String>,
) -> RouterResult<PaymentMethodCollectLink> {
    let db: &dyn StorageInterface = &*state.store;

    let link_data = serde_json::to_value(pm_collect_link_data)
        .map_err(|_| report!(errors::ApiErrorResponse::InternalServerError))
        .attach_printable("Failed to convert PaymentMethodCollectLinkData to Value")?;

    let pm_collect_link = GenericLinkNew {
        link_id: pm_collect_link_data.pm_collect_link_id.to_string(),
        primary_reference: pm_collect_link_data
            .customer_id
            .get_string_repr()
            .to_string(),
        merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
        link_type: common_enums::GenericLinkType::PaymentMethodCollect,
        link_data,
        url: pm_collect_link_data.link.clone(),
        return_url,
        expiry: common_utils::date_time::now()
            + Duration::seconds(pm_collect_link_data.session_expiry.into()),
        ..Default::default()
    };

    db.insert_pm_collect_link(pm_collect_link)
        .await
        .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
            message: "payment method collect link already exists".to_string(),
        })
}

#[cfg(feature = "v2")]
pub async fn render_pm_collect_link(
    _state: SessionState,
    _merchant_context: domain::MerchantContext,
    _req: payment_methods::PaymentMethodCollectLinkRenderRequest,
) -> RouterResponse<services::GenericLinkFormData> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn render_pm_collect_link(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: payment_methods::PaymentMethodCollectLinkRenderRequest,
) -> RouterResponse<services::GenericLinkFormData> {
    let db: &dyn StorageInterface = &*state.store;

    // Fetch pm collect link
    let pm_collect_link = db
        .find_pm_collect_link_by_link_id(&req.pm_collect_link_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
            message: "payment method collect link not found".to_string(),
        })?;

    // Check status and return form data accordingly
    let has_expired = common_utils::date_time::now() > pm_collect_link.expiry;
    let status = pm_collect_link.link_status;
    let link_data = pm_collect_link.link_data;
    let default_config = &state.conf.generic_link.payment_method_collect;
    let default_ui_config = default_config.ui_config.clone();
    let ui_config_data = common_utils::link_utils::GenericLinkUiConfigFormData {
        merchant_name: link_data
            .ui_config
            .merchant_name
            .unwrap_or(default_ui_config.merchant_name),
        logo: link_data.ui_config.logo.unwrap_or(default_ui_config.logo),
        theme: link_data
            .ui_config
            .theme
            .clone()
            .unwrap_or(default_ui_config.theme.clone()),
    };
    match status {
        common_utils::link_utils::PaymentMethodCollectStatus::Initiated => {
            // if expired, send back expired status page
            if has_expired {
                let expired_link_data = services::GenericExpiredLinkData {
                    title: "Payment collect link has expired".to_string(),
                    message: "This payment collect link has expired.".to_string(),
                    theme: link_data.ui_config.theme.unwrap_or(default_ui_config.theme),
                };
                Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                    GenericLinks {
                        allowed_domains: HashSet::from([]),
                        data: GenericLinksData::ExpiredLink(expired_link_data),
                        locale: DEFAULT_LOCALE.to_string(),
                    },
                )))

            // else, send back form link
            } else {
                let customer_id = id_type::CustomerId::try_from(Cow::from(
                    pm_collect_link.primary_reference.clone(),
                ))
                .change_context(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "customer_id",
                })?;
                // Fetch customer

                let customer = db
                    .find_customer_by_customer_id_merchant_id(
                        &(&state).into(),
                        &customer_id,
                        &req.merchant_id,
                        merchant_context.get_merchant_key_store(),
                        merchant_context.get_merchant_account().storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InvalidRequestData {
                        message: format!(
                            "Customer [{}] not found for link_id - {}",
                            pm_collect_link.primary_reference, pm_collect_link.link_id
                        ),
                    })
                    .attach_printable(format!(
                        "customer [{}] not found",
                        pm_collect_link.primary_reference
                    ))?;

                let js_data = payment_methods::PaymentMethodCollectLinkDetails {
                    publishable_key: Secret::new(
                        merchant_context
                            .get_merchant_account()
                            .clone()
                            .publishable_key,
                    ),
                    client_secret: link_data.client_secret.clone(),
                    pm_collect_link_id: pm_collect_link.link_id,
                    customer_id: customer.customer_id,
                    session_expiry: pm_collect_link.expiry,
                    return_url: pm_collect_link.return_url,
                    ui_config: ui_config_data,
                    enabled_payment_methods: link_data.enabled_payment_methods,
                };

                let serialized_css_content = String::new();

                let serialized_js_content = format!(
                    "window.__PM_COLLECT_DETAILS = {}",
                    js_data
                        .encode_to_string_of_json()
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed to serialize PaymentMethodCollectLinkDetails")?
                );

                let generic_form_data = services::GenericLinkFormData {
                    js_data: serialized_js_content,
                    css_data: serialized_css_content,
                    sdk_url: default_config.sdk_url.clone(),
                    html_meta_tags: String::new(),
                };
                Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                    GenericLinks {
                        allowed_domains: HashSet::from([]),
                        data: GenericLinksData::PaymentMethodCollect(generic_form_data),
                        locale: DEFAULT_LOCALE.to_string(),
                    },
                )))
            }
        }

        // Send back status page
        status => {
            let js_data = payment_methods::PaymentMethodCollectLinkStatusDetails {
                pm_collect_link_id: pm_collect_link.link_id,
                customer_id: link_data.customer_id,
                session_expiry: pm_collect_link.expiry,
                return_url: pm_collect_link
                    .return_url
                    .as_ref()
                    .map(|url| url::Url::parse(url))
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Failed to parse return URL for payment method collect's status link",
                    )?,
                ui_config: ui_config_data,
                status,
            };

            let serialized_css_content = String::new();

            let serialized_js_content = format!(
                "window.__PM_COLLECT_DETAILS = {}",
                js_data
                    .encode_to_string_of_json()
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Failed to serialize PaymentMethodCollectLinkStatusDetails"
                    )?
            );

            let generic_status_data = services::GenericLinkStatusData {
                js_data: serialized_js_content,
                css_data: serialized_css_content,
            };
            Ok(services::ApplicationResponse::GenericLinkForm(Box::new(
                GenericLinks {
                    allowed_domains: HashSet::from([]),
                    data: GenericLinksData::PaymentMethodCollectStatus(generic_status_data),
                    locale: DEFAULT_LOCALE.to_string(),
                },
            )))
        }
    }
}

fn generate_task_id_for_payment_method_status_update_workflow(
    key_id: &str,
    runner: storage::ProcessTrackerRunner,
    task: &str,
) -> String {
    format!("{runner}_{task}_{key_id}")
}

#[cfg(feature = "v1")]
pub async fn add_payment_method_status_update_task(
    db: &dyn StorageInterface,
    payment_method: &domain::PaymentMethod,
    prev_status: enums::PaymentMethodStatus,
    curr_status: enums::PaymentMethodStatus,
    merchant_id: &id_type::MerchantId,
) -> Result<(), ProcessTrackerError> {
    let created_at = payment_method.created_at;
    let schedule_time =
        created_at.saturating_add(Duration::seconds(consts::DEFAULT_SESSION_EXPIRY));

    let tracking_data = storage::PaymentMethodStatusTrackingData {
        payment_method_id: payment_method.get_id().clone(),
        prev_status,
        curr_status,
        merchant_id: merchant_id.to_owned(),
    };

    let runner = storage::ProcessTrackerRunner::PaymentMethodStatusUpdateWorkflow;
    let task = PAYMENT_METHOD_STATUS_UPDATE_TASK;
    let tag = [PAYMENT_METHOD_STATUS_TAG];

    let process_tracker_id = generate_task_id_for_payment_method_status_update_workflow(
        payment_method.get_id().as_str(),
        runner,
        task,
    );
    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        task,
        runner,
        tag,
        tracking_data,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to construct PAYMENT_METHOD_STATUS_UPDATE process tracker task")?;

    db
        .insert_process(process_tracker_entry)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!(
                "Failed while inserting PAYMENT_METHOD_STATUS_UPDATE reminder to process_tracker for payment_method_id: {}",
                payment_method.get_id().clone()
            )
        })?;

    Ok(())
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_with_token(
    _state: &SessionState,
    _merchant_key_store: &domain::MerchantKeyStore,
    _token_data: &storage::PaymentTokenData,
    _payment_intent: &PaymentIntent,
    _card_token_data: Option<&domain::CardToken>,
    _customer: &Option<domain::Customer>,
    _storage_scheme: common_enums::enums::MerchantStorageScheme,
    _mandate_id: Option<api_models::payments::MandateIds>,
    _payment_method_info: Option<domain::PaymentMethod>,
    _business_profile: &domain::Profile,
) -> RouterResult<storage::PaymentMethodDataWithId> {
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn retrieve_payment_method_with_token(
    state: &SessionState,
    merchant_key_store: &domain::MerchantKeyStore,
    token_data: &storage::PaymentTokenData,
    payment_intent: &PaymentIntent,
    payment_attempt: &PaymentAttempt,
    card_token_data: Option<&domain::CardToken>,
    customer: &Option<domain::Customer>,
    storage_scheme: common_enums::enums::MerchantStorageScheme,
    mandate_id: Option<api_models::payments::MandateIds>,
    payment_method_info: Option<domain::PaymentMethod>,
    business_profile: &domain::Profile,
    should_retry_with_pan: bool,
    vault_data: Option<&VaultData>,
) -> RouterResult<storage::PaymentMethodDataWithId> {
    let token = match token_data {
        storage::PaymentTokenData::TemporaryGeneric(generic_token) => {
            payment_helpers::retrieve_payment_method_with_temporary_token(
                state,
                &generic_token.token,
                payment_intent,
                payment_attempt,
                merchant_key_store,
                card_token_data,
            )
            .await?
            .map(
                |(payment_method_data, payment_method)| storage::PaymentMethodDataWithId {
                    payment_method_data: Some(payment_method_data),
                    payment_method: Some(payment_method),
                    payment_method_id: None,
                },
            )
            .unwrap_or_default()
        }

        storage::PaymentTokenData::Temporary(generic_token) => {
            payment_helpers::retrieve_payment_method_with_temporary_token(
                state,
                &generic_token.token,
                payment_intent,
                payment_attempt,
                merchant_key_store,
                card_token_data,
            )
            .await?
            .map(
                |(payment_method_data, payment_method)| storage::PaymentMethodDataWithId {
                    payment_method_data: Some(payment_method_data),
                    payment_method: Some(payment_method),
                    payment_method_id: None,
                },
            )
            .unwrap_or_default()
        }

        storage::PaymentTokenData::Permanent(card_token) => {
            payment_helpers::retrieve_payment_method_data_with_permanent_token(
                state,
                card_token.locker_id.as_ref().unwrap_or(&card_token.token),
                card_token
                    .payment_method_id
                    .as_ref()
                    .unwrap_or(&card_token.token),
                payment_intent,
                card_token_data,
                merchant_key_store,
                storage_scheme,
                mandate_id,
                payment_method_info
                    .get_required_value("PaymentMethod")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("PaymentMethod not found")?,
                business_profile,
                payment_attempt.connector.clone(),
                should_retry_with_pan,
                vault_data,
            )
            .await
            .map(|card| Some((card, enums::PaymentMethod::Card)))?
            .map(
                |(payment_method_data, payment_method)| storage::PaymentMethodDataWithId {
                    payment_method_data: Some(payment_method_data),
                    payment_method: Some(payment_method),
                    payment_method_id: Some(
                        card_token
                            .payment_method_id
                            .as_ref()
                            .unwrap_or(&card_token.token)
                            .to_string(),
                    ),
                },
            )
            .unwrap_or_default()
        }

        storage::PaymentTokenData::PermanentCard(card_token) => {
            payment_helpers::retrieve_payment_method_data_with_permanent_token(
                state,
                card_token.locker_id.as_ref().unwrap_or(&card_token.token),
                card_token
                    .payment_method_id
                    .as_ref()
                    .unwrap_or(&card_token.token),
                payment_intent,
                card_token_data,
                merchant_key_store,
                storage_scheme,
                mandate_id,
                payment_method_info
                    .get_required_value("PaymentMethod")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("PaymentMethod not found")?,
                business_profile,
                payment_attempt.connector.clone(),
                should_retry_with_pan,
                vault_data,
            )
            .await
            .map(|card| Some((card, enums::PaymentMethod::Card)))?
            .map(
                |(payment_method_data, payment_method)| storage::PaymentMethodDataWithId {
                    payment_method_data: Some(payment_method_data),
                    payment_method: Some(payment_method),
                    payment_method_id: Some(
                        card_token
                            .payment_method_id
                            .as_ref()
                            .unwrap_or(&card_token.token)
                            .to_string(),
                    ),
                },
            )
            .unwrap_or_default()
        }

        storage::PaymentTokenData::AuthBankDebit(auth_token) => {
            pm_auth::retrieve_payment_method_from_auth_service(
                state,
                merchant_key_store,
                auth_token,
                payment_intent,
                customer,
            )
            .await?
            .map(
                |(payment_method_data, payment_method)| storage::PaymentMethodDataWithId {
                    payment_method_data: Some(payment_method_data),
                    payment_method: Some(payment_method),
                    payment_method_id: None,
                },
            )
            .unwrap_or_default()
        }

        storage::PaymentTokenData::WalletToken(_) => storage::PaymentMethodDataWithId {
            payment_method: None,
            payment_method_data: None,
            payment_method_id: None,
        },
    };
    Ok(token)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub(crate) fn get_payment_method_create_request(
    payment_method_data: &api_models::payments::PaymentMethodData,
    payment_method_type: storage_enums::PaymentMethod,
    payment_method_subtype: storage_enums::PaymentMethodType,
    customer_id: id_type::GlobalCustomerId,
    billing_address: Option<&api_models::payments::Address>,
    payment_method_session: Option<&domain::payment_methods::PaymentMethodSession>,
) -> RouterResult<payment_methods::PaymentMethodCreate> {
    match payment_method_data {
        api_models::payments::PaymentMethodData::Card(card) => {
            let card_detail = payment_methods::CardDetail {
                card_number: card.card_number.clone(),
                card_exp_month: card.card_exp_month.clone(),
                card_exp_year: card.card_exp_year.clone(),
                card_holder_name: card.card_holder_name.clone(),
                nick_name: card.nick_name.clone(),
                card_issuing_country: card
                    .card_issuing_country
                    .as_ref()
                    .map(|c| api_enums::CountryAlpha2::from_str(c))
                    .transpose()
                    .ok()
                    .flatten(),
                card_network: card.card_network.clone(),
                card_issuer: card.card_issuer.clone(),
                card_type: card
                    .card_type
                    .as_ref()
                    .map(|c| payment_methods::CardType::from_str(c))
                    .transpose()
                    .ok()
                    .flatten(),
                card_cvc: Some(card.card_cvc.clone()),
            };
            let payment_method_request = payment_methods::PaymentMethodCreate {
                payment_method_type,
                payment_method_subtype,
                metadata: None,
                customer_id: customer_id.clone(),
                payment_method_data: payment_methods::PaymentMethodCreateData::Card(card_detail),
                billing: billing_address.map(ToOwned::to_owned),
                psp_tokenization: payment_method_session
                    .and_then(|pm_session| pm_session.psp_tokenization.clone()),
                network_tokenization: payment_method_session
                    .and_then(|pm_session| pm_session.network_tokenization.clone()),
            };
            Ok(payment_method_request)
        }
        _ => Err(report!(errors::ApiErrorResponse::UnprocessableEntity {
            message: "only card payment methods are supported for tokenization".to_string()
        })
        .attach_printable("Payment method data is incorrect")),
    }
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub(crate) async fn get_payment_method_create_request(
    payment_method_data: Option<&domain::PaymentMethodData>,
    payment_method: Option<storage_enums::PaymentMethod>,
    payment_method_type: Option<storage_enums::PaymentMethodType>,
    customer_id: &Option<id_type::CustomerId>,
    billing_name: Option<Secret<String>>,
    payment_method_billing_address: Option<&hyperswitch_domain_models::address::Address>,
) -> RouterResult<payment_methods::PaymentMethodCreate> {
    match payment_method_data {
        Some(pm_data) => match payment_method {
            Some(payment_method) => match pm_data {
                domain::PaymentMethodData::Card(card) => {
                    let card_network = get_card_network_with_us_local_debit_network_override(
                        card.card_network.clone(),
                        card.co_badged_card_data.as_ref(),
                    );

                    let card_detail = payment_methods::CardDetail {
                        card_number: card.card_number.clone(),
                        card_exp_month: card.card_exp_month.clone(),
                        card_exp_year: card.card_exp_year.clone(),
                        card_holder_name: billing_name,
                        nick_name: card.nick_name.clone(),
                        card_issuing_country: card.card_issuing_country.clone(),
                        card_network: card_network.clone(),
                        card_issuer: card.card_issuer.clone(),
                        card_type: card.card_type.clone(),
                    };
                    let payment_method_request = payment_methods::PaymentMethodCreate {
                        payment_method: Some(payment_method),
                        payment_method_type,
                        payment_method_issuer: card.card_issuer.clone(),
                        payment_method_issuer_code: None,
                        #[cfg(feature = "payouts")]
                        bank_transfer: None,
                        #[cfg(feature = "payouts")]
                        wallet: None,
                        card: Some(card_detail),
                        metadata: None,
                        customer_id: customer_id.clone(),
                        card_network: card_network
                            .clone()
                            .as_ref()
                            .map(|card_network| card_network.to_string()),
                        client_secret: None,
                        payment_method_data: None,
                        //TODO: why are we using api model in router internally
                        billing: payment_method_billing_address.cloned().map(From::from),
                        connector_mandate_details: None,
                        network_transaction_id: None,
                    };
                    Ok(payment_method_request)
                }
                _ => {
                    let payment_method_request = payment_methods::PaymentMethodCreate {
                        payment_method: Some(payment_method),
                        payment_method_type,
                        payment_method_issuer: None,
                        payment_method_issuer_code: None,
                        #[cfg(feature = "payouts")]
                        bank_transfer: None,
                        #[cfg(feature = "payouts")]
                        wallet: None,
                        card: None,
                        metadata: None,
                        customer_id: customer_id.clone(),
                        card_network: None,
                        client_secret: None,
                        payment_method_data: None,
                        billing: None,
                        connector_mandate_details: None,
                        network_transaction_id: None,
                    };

                    Ok(payment_method_request)
                }
            },
            None => Err(report!(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "payment_method_type"
            })
            .attach_printable("PaymentMethodType Required")),
        },
        None => Err(report!(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "payment_method_data"
        })
        .attach_printable("PaymentMethodData required Or Card is already saved")),
    }
}

/// Determines the appropriate card network to to be stored.
///
/// If the provided card network is a US local network, this function attempts to
/// override it with the first global network from the co-badged card data, if available.
/// Otherwise, it returns the original card network as-is.
///
fn get_card_network_with_us_local_debit_network_override(
    card_network: Option<common_enums::CardNetwork>,
    co_badged_card_data: Option<&payment_methods::CoBadgedCardData>,
) -> Option<common_enums::CardNetwork> {
    if let Some(true) = card_network
        .as_ref()
        .map(|network| network.is_us_local_network())
    {
        services::logger::debug!("Card network is a US local network, checking for global network in co-badged card data");
        let info: Option<api_models::open_router::CoBadgedCardNetworksInfo> = co_badged_card_data
            .and_then(|data| {
                data.co_badged_card_networks_info
                    .0
                    .iter()
                    .find(|info| info.network.is_signature_network())
                    .cloned()
            });
        info.map(|data| data.network)
    } else {
        card_network
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn create_payment_method(
    state: &SessionState,
    request_state: &routes::app::ReqState,
    req: api::PaymentMethodCreate,
    merchant_context: &domain::MerchantContext,
    profile: &domain::Profile,
) -> RouterResponse<api::PaymentMethodResponse> {
    // payment_method is for internal use, can never be populated in response
    let (response, _payment_method) = Box::pin(create_payment_method_core(
        state,
        request_state,
        req,
        merchant_context,
        profile,
    ))
    .await?;

    Ok(services::ApplicationResponse::Json(response))
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn create_payment_method_core(
    state: &SessionState,
    _request_state: &routes::app::ReqState,
    req: api::PaymentMethodCreate,
    merchant_context: &domain::MerchantContext,
    profile: &domain::Profile,
) -> RouterResult<(api::PaymentMethodResponse, domain::PaymentMethod)> {
    use common_utils::ext_traits::ValueExt;

    req.validate()?;

    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = req.customer_id.to_owned();
    let key_manager_state = &(state).into();

    db.find_customer_by_global_id(
        key_manager_state,
        &customer_id,
        merchant_context.get_merchant_key_store(),
        merchant_context.get_merchant_account().storage_scheme,
    )
    .await
    .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)
    .attach_printable("Customer not found for the payment method")?;

    let payment_method_billing_address = req
        .billing
        .clone()
        .async_map(|billing| {
            cards::create_encrypted_data(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                billing,
            )
        })
        .await
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to encrypt Payment method billing address")?
        .map(|encoded_address| {
            encoded_address.deserialize_inner_value(|value| value.parse_value("address"))
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to parse Payment method billing address")?;

    let payment_method_id =
        id_type::GlobalPaymentMethodId::generate(&state.conf.cell_information.id)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to generate GlobalPaymentMethodId")?;

    match &req.payment_method_data {
        api::PaymentMethodCreateData::Card(_) => {
            create_payment_method_card_core(
                state,
                req,
                merchant_context,
                profile,
                merchant_id,
                &customer_id,
                payment_method_id,
                payment_method_billing_address,
            )
            .await
        }
        api::PaymentMethodCreateData::ProxyCard(_) => {
            create_payment_method_proxy_card_core(
                state,
                req,
                merchant_context,
                profile,
                merchant_id,
                &customer_id,
                payment_method_id,
                payment_method_billing_address,
            )
            .await
        }
    }
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn create_payment_method_card_core(
    state: &SessionState,
    req: api::PaymentMethodCreate,
    merchant_context: &domain::MerchantContext,
    profile: &domain::Profile,
    merchant_id: &id_type::MerchantId,
    customer_id: &id_type::GlobalCustomerId,
    payment_method_id: id_type::GlobalPaymentMethodId,
    payment_method_billing_address: Option<
        Encryptable<hyperswitch_domain_models::address::Address>,
    >,
) -> RouterResult<(api::PaymentMethodResponse, domain::PaymentMethod)> {
    let db = &*state.store;

    let payment_method = create_payment_method_for_intent(
        state,
        req.metadata.clone(),
        customer_id,
        payment_method_id,
        merchant_id,
        merchant_context.get_merchant_key_store(),
        merchant_context.get_merchant_account().storage_scheme,
        payment_method_billing_address,
    )
    .await
    .attach_printable("failed to add payment method to db")?;

    let payment_method_data = domain::PaymentMethodVaultingData::try_from(req.payment_method_data)?
        .populate_bin_details_for_payment_method(state)
        .await;

    let vaulting_result = vault_payment_method(
        state,
        &payment_method_data,
        merchant_context,
        profile,
        None,
        customer_id,
    )
    .await;

    let network_tokenization_resp = network_tokenize_and_vault_the_pmd(
        state,
        &payment_method_data,
        merchant_context,
        req.network_tokenization.clone(),
        profile.is_network_tokenization_enabled,
        customer_id,
    )
    .await;

    let (response, payment_method) = match vaulting_result {
        Ok((
            pm_types::AddVaultResponse {
                vault_id,
                fingerprint_id,
                ..
            },
            external_vault_source,
        )) => {
            let pm_update = create_pm_additional_data_update(
                Some(&payment_method_data),
                state,
                merchant_context.get_merchant_key_store(),
                Some(vault_id.get_string_repr().clone()),
                fingerprint_id,
                &payment_method,
                None,
                network_tokenization_resp,
                Some(req.payment_method_type),
                Some(req.payment_method_subtype),
                external_vault_source,
            )
            .await
            .attach_printable("unable to create payment method data")?;

            let payment_method = db
                .update_payment_method(
                    &(state.into()),
                    merchant_context.get_merchant_key_store(),
                    payment_method,
                    pm_update,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to update payment method in db")?;

            let resp = pm_transforms::generate_payment_method_response(&payment_method, &None)?;

            Ok((resp, payment_method))
        }
        Err(e) => {
            let pm_update = storage::PaymentMethodUpdate::StatusUpdate {
                status: Some(enums::PaymentMethodStatus::Inactive),
            };

            db.update_payment_method(
                &(state.into()),
                merchant_context.get_merchant_key_store(),
                payment_method,
                pm_update,
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update payment method in db")?;

            Err(e)
        }
    }?;

    Ok((response, payment_method))
}

// network tokenization and vaulting to locker is not required for proxy card since the card is already tokenized
#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]


// Struct: AccountInformation
// File: crates/hyperswitch_connectors/src/connectors/blackhawknetwork/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AccountInformation


// File: crates/router/src/db/authentication.rs
// Module: router

use diesel_models::authentication::AuthenticationUpdateInternal;
use error_stack::report;
use router_env::{instrument, tracing};

use super::{MockDb, Store};
use crate::{
    connection,
    core::errors::{self, CustomResult},
    types::storage,
};

#[async_trait::async_trait]
pub trait AuthenticationInterface {
    async fn insert_authentication(
        &self,
        authentication: storage::AuthenticationNew,
    ) -> CustomResult<storage::Authentication, errors::StorageError>;

    async fn find_authentication_by_merchant_id_authentication_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        authentication_id: &common_utils::id_type::AuthenticationId,
    ) -> CustomResult<storage::Authentication, errors::StorageError>;

    async fn find_authentication_by_merchant_id_connector_authentication_id(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        connector_authentication_id: String,
    ) -> CustomResult<storage::Authentication, errors::StorageError>;

    async fn update_authentication_by_merchant_id_authentication_id(
        &self,
        previous_state: storage::Authentication,
        authentication_update: storage::AuthenticationUpdate,
    ) -> CustomResult<storage::Authentication, errors::StorageError>;
}

#[async_trait::async_trait]
impl AuthenticationInterface for Store {
    #[instrument(skip_all)]
    async fn insert_authentication(
        &self,
        authentication: storage::AuthenticationNew,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        authentication
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn find_authentication_by_merchant_id_authentication_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        authentication_id: &common_utils::id_type::AuthenticationId,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Authentication::find_by_merchant_id_authentication_id(
            &conn,
            merchant_id,
            authentication_id,
        )
        .await
        .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn find_authentication_by_merchant_id_connector_authentication_id(
        &self,
        merchant_id: common_utils::id_type::MerchantId,
        connector_authentication_id: String,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Authentication::find_authentication_by_merchant_id_connector_authentication_id(
            &conn,
            &merchant_id,
            &connector_authentication_id,
        )
        .await
        .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn update_authentication_by_merchant_id_authentication_id(
        &self,
        previous_state: storage::Authentication,
        authentication_update: storage::AuthenticationUpdate,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::Authentication::update_by_merchant_id_authentication_id(
            &conn,
            previous_state.merchant_id,
            previous_state.authentication_id,
            authentication_update,
        )
        .await
        .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl AuthenticationInterface for MockDb {
    async fn insert_authentication(
        &self,
        authentication: storage::AuthenticationNew,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let mut authentications = self.authentications.lock().await;
        if authentications.iter().any(|authentication_inner| {
            authentication_inner.authentication_id == authentication.authentication_id
        }) {
            Err(errors::StorageError::DuplicateValue {
                entity: "authentication_id",
                key: Some(
                    authentication
                        .authentication_id
                        .get_string_repr()
                        .to_string(),
                ),
            })?
        }
        let authentication = storage::Authentication {
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            authentication_id: authentication.authentication_id,
            merchant_id: authentication.merchant_id,
            authentication_status: authentication.authentication_status,
            authentication_connector: authentication.authentication_connector,
            connector_authentication_id: authentication.connector_authentication_id,
            authentication_data: None,
            payment_method_id: authentication.payment_method_id,
            authentication_type: authentication.authentication_type,
            authentication_lifecycle_status: authentication.authentication_lifecycle_status,
            error_code: authentication.error_code,
            error_message: authentication.error_message,
            connector_metadata: authentication.connector_metadata,
            maximum_supported_version: authentication.maximum_supported_version,
            threeds_server_transaction_id: authentication.threeds_server_transaction_id,
            cavv: authentication.cavv,
            authentication_flow_type: authentication.authentication_flow_type,
            message_version: authentication.message_version,
            eci: authentication.eci,
            trans_status: authentication.trans_status,
            acquirer_bin: authentication.acquirer_bin,
            acquirer_merchant_id: authentication.acquirer_merchant_id,
            three_ds_method_data: authentication.three_ds_method_data,
            three_ds_method_url: authentication.three_ds_method_url,
            acs_url: authentication.acs_url,
            challenge_request: authentication.challenge_request,
            challenge_request_key: authentication.challenge_request_key,
            acs_reference_number: authentication.acs_reference_number,
            acs_trans_id: authentication.acs_trans_id,
            acs_signed_content: authentication.acs_signed_content,
            profile_id: authentication.profile_id,
            payment_id: authentication.payment_id,
            merchant_connector_id: authentication.merchant_connector_id,
            ds_trans_id: authentication.ds_trans_id,
            directory_server_id: authentication.directory_server_id,
            acquirer_country_code: authentication.acquirer_country_code,
            service_details: authentication.service_details,
            organization_id: authentication.organization_id,
            authentication_client_secret: authentication.authentication_client_secret,
            force_3ds_challenge: authentication.force_3ds_challenge,
            psd2_sca_exemption_type: authentication.psd2_sca_exemption_type,
            return_url: authentication.return_url,
            amount: authentication.amount,
            currency: authentication.currency,
            billing_address: authentication.billing_address,
            shipping_address: authentication.shipping_address,
            browser_info: authentication.browser_info,
            email: authentication.email,
            profile_acquirer_id: authentication.profile_acquirer_id,
            challenge_code: authentication.challenge_code,
            challenge_cancel: authentication.challenge_cancel,
            challenge_code_reason: authentication.challenge_code_reason,
            message_extension: authentication.message_extension,
        };
        authentications.push(authentication.clone());
        Ok(authentication)
    }

    async fn find_authentication_by_merchant_id_authentication_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        authentication_id: &common_utils::id_type::AuthenticationId,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let authentications = self.authentications.lock().await;
        authentications
            .iter()
            .find(|a| a.merchant_id == *merchant_id && a.authentication_id == *authentication_id)
            .ok_or(
                errors::StorageError::ValueNotFound(format!(
                    "cannot find authentication for authentication_id = {authentication_id:?} and merchant_id = {merchant_id:?}"
                )).into(),
            ).cloned()
    }

    async fn find_authentication_by_merchant_id_connector_authentication_id(
        &self,
        _merchant_id: common_utils::id_type::MerchantId,
        _connector_authentication_id: String,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        Err(errors::StorageError::MockDbError)?
    }

    async fn update_authentication_by_merchant_id_authentication_id(
        &self,
        previous_state: storage::Authentication,
        authentication_update: storage::AuthenticationUpdate,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let mut authentications = self.authentications.lock().await;
        let authentication_id = previous_state.authentication_id.clone();
        let merchant_id = previous_state.merchant_id.clone();
        authentications
            .iter_mut()
            .find(|authentication| authentication.authentication_id == authentication_id && authentication.merchant_id == merchant_id)
            .map(|authentication| {
                let authentication_update_internal =
                    AuthenticationUpdateInternal::from(authentication_update);
                let updated_authentication = authentication_update_internal.apply_changeset(previous_state);
                *authentication = updated_authentication.clone();
                updated_authentication
            })
            .ok_or(
                errors::StorageError::ValueNotFound(format!(
                    "cannot find authentication for authentication_id = {authentication_id:?} and merchant_id = {merchant_id:?}"
                ))
                .into(),
            )
    }
}


// Implementation: impl RequestPaymentMethodTypes
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Methods: 1 total (1 public)

impl RequestPaymentMethodTypes


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

impl webhooks::IncomingWebhook for for Authipay


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

impl Responder


// Function: set_routing_approach
// File: crates/hyperswitch_interfaces/src/events/routing_api_logs.rs
// Module: hyperswitch_interfaces

// Documentation: set routing approach

pub fn set_routing_approach(&mut self, approach: String)


// Implementation: impl api::PaymentSession for for Cryptopay
// File: crates/hyperswitch_connectors/src/connectors/cryptopay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSession for for Cryptopay


// Implementation: impl events::ApiEventMetric for for DecisionManagerRecord
// File: crates/api_models/src/conditional_configs.rs
// Module: api_models
// Methods: 1 total (0 public)

impl events::ApiEventMetric for for DecisionManagerRecord


// Struct: ApplePayCertificatesMigrationRequest
// File: crates/api_models/src/apple_pay_certificates_migration.rs
// Module: api_models
// Implementations: 1
// Traits: common_utils::events::ApiEventMetric

pub struct ApplePayCertificatesMigrationRequest


// Function: get_bank_from_hs_locker
// File: crates/router/src/core/payment_methods/cards.rs
// Module: router

pub fn get_bank_from_hs_locker(
    state: &routes::SessionState,
    key_store: &domain::MerchantKeyStore,
    temp_token: Option<&String>,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    token_ref: &str,
) -> errors::RouterResult<api::BankPayout>


// File: crates/common_utils/src/id_type/relay.rs
// Module: common_utils

use std::str::FromStr;

crate::id_type!(
    RelayId,
    "A type for relay_id that can be used for relay ids"
);
crate::impl_id_type_methods!(RelayId, "relay_id");

crate::impl_try_from_cow_str_id_type!(RelayId, "relay_id");
crate::impl_generate_id_id_type!(RelayId, "relay");
crate::impl_serializable_secret_id_type!(RelayId);
crate::impl_queryable_id_type!(RelayId);
crate::impl_to_sql_from_sql_id_type!(RelayId);

crate::impl_debug_id_type!(RelayId);

impl FromStr for RelayId {
    type Err = error_stack::Report<crate::errors::ValidationError>;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let cow_string = std::borrow::Cow::Owned(s.to_string());
        Self::try_from(cow_string)
    }
}


// Implementation: impl FromStr for for AciPaymentStatus
// File: crates/hyperswitch_connectors/src/connectors/aci/transformers.rs
// Module: hyperswitch_connectors
// Methods: 1 total (0 public)

impl FromStr for for AciPaymentStatus


// File: crates/pm_auth/src/types.rs
// Module: pm_auth
// Public structs: 19

pub mod api;

use std::marker::PhantomData;

use api::auth_service::{BankAccountCredentials, ExchangeToken, LinkToken, RecipientCreate};
use api_models::enums as api_enums;
use common_enums::{CountryAlpha2, PaymentMethod, PaymentMethodType};
use common_utils::{id_type, types};
use masking::Secret;

#[derive(Debug, Clone)]
pub struct PaymentAuthRouterData<F, Request, Response> {
    pub flow: PhantomData<F>,
    pub merchant_id: Option<id_type::MerchantId>,
    pub connector: Option<String>,
    pub request: Request,
    pub response: Result<Response, ErrorResponse>,
    pub connector_auth_type: ConnectorAuthType,
    pub connector_http_status_code: Option<u16>,
}

#[derive(Debug, Clone)]
pub struct LinkTokenRequest {
    pub client_name: String,
    pub country_codes: Option<Vec<String>>,
    pub language: Option<String>,
    pub user_info: Option<id_type::CustomerId>,
    pub client_platform: Option<api_enums::ClientPlatform>,
    pub android_package_name: Option<String>,
    pub redirect_uri: Option<String>,
}

#[derive(Debug, Clone)]
pub struct LinkTokenResponse {
    pub link_token: String,
}

pub type LinkTokenRouterData =
    PaymentAuthRouterData<LinkToken, LinkTokenRequest, LinkTokenResponse>;

#[derive(Debug, Clone)]
pub struct ExchangeTokenRequest {
    pub public_token: String,
}

#[derive(Debug, Clone)]
pub struct ExchangeTokenResponse {
    pub access_token: String,
}

impl From<ExchangeTokenResponse> for api_models::pm_auth::ExchangeTokenCreateResponse {
    fn from(value: ExchangeTokenResponse) -> Self {
        Self {
            access_token: value.access_token,
        }
    }
}

pub type ExchangeTokenRouterData =
    PaymentAuthRouterData<ExchangeToken, ExchangeTokenRequest, ExchangeTokenResponse>;

#[derive(Debug, Clone)]
pub struct BankAccountCredentialsRequest {
    pub access_token: Secret<String>,
    pub optional_ids: Option<BankAccountOptionalIDs>,
}

#[derive(Debug, Clone)]
pub struct BankAccountOptionalIDs {
    pub ids: Vec<Secret<String>>,
}

#[derive(Debug, Clone)]
pub struct BankAccountCredentialsResponse {
    pub credentials: Vec<BankAccountDetails>,
}

#[derive(Debug, Clone)]
pub struct BankAccountDetails {
    pub account_name: Option<String>,
    pub account_details: PaymentMethodTypeDetails,
    pub payment_method_type: PaymentMethodType,
    pub payment_method: PaymentMethod,
    pub account_id: Secret<String>,
    pub account_type: Option<String>,
    pub balance: Option<types::FloatMajorUnit>,
}

#[derive(Debug, Clone)]
pub enum PaymentMethodTypeDetails {
    Ach(BankAccountDetailsAch),
    Bacs(BankAccountDetailsBacs),
    Sepa(BankAccountDetailsSepa),
}
#[derive(Debug, Clone)]
pub struct BankAccountDetailsAch {
    pub account_number: Secret<String>,
    pub routing_number: Secret<String>,
}

#[derive(Debug, Clone)]
pub struct BankAccountDetailsBacs {
    pub account_number: Secret<String>,
    pub sort_code: Secret<String>,
}

#[derive(Debug, Clone)]
pub struct BankAccountDetailsSepa {
    pub iban: Secret<String>,
    pub bic: Secret<String>,
}

pub type BankDetailsRouterData = PaymentAuthRouterData<
    BankAccountCredentials,
    BankAccountCredentialsRequest,
    BankAccountCredentialsResponse,
>;

#[derive(Debug, Clone)]
pub struct RecipientCreateRequest {
    pub name: String,
    pub account_data: RecipientAccountData,
    pub address: Option<RecipientCreateAddress>,
}

#[derive(Debug, Clone)]
pub struct RecipientCreateResponse {
    pub recipient_id: String,
}

#[derive(Debug, Clone)]
pub enum RecipientAccountData {
    Iban(Secret<String>),
    Bacs {
        sort_code: Secret<String>,
        account_number: Secret<String>,
    },
    FasterPayments {
        sort_code: Secret<String>,
        account_number: Secret<String>,
    },
    Sepa(Secret<String>),
    SepaInstant(Secret<String>),
    Elixir {
        account_number: Secret<String>,
        iban: Secret<String>,
    },
    Bankgiro(Secret<String>),
    Plusgiro(Secret<String>),
}
#[derive(Debug, Clone)]
pub struct RecipientCreateAddress {
    pub street: String,
    pub city: String,
    pub postal_code: String,
    pub country: CountryAlpha2,
}

pub type RecipientCreateRouterData =
    PaymentAuthRouterData<RecipientCreate, RecipientCreateRequest, RecipientCreateResponse>;

pub type PaymentAuthLinkTokenType =
    dyn api::ConnectorIntegration<LinkToken, LinkTokenRequest, LinkTokenResponse>;

pub type PaymentAuthExchangeTokenType =
    dyn api::ConnectorIntegration<ExchangeToken, ExchangeTokenRequest, ExchangeTokenResponse>;

pub type PaymentAuthBankAccountDetailsType = dyn api::ConnectorIntegration<
    BankAccountCredentials,
    BankAccountCredentialsRequest,
    BankAccountCredentialsResponse,
>;

pub type PaymentInitiationRecipientCreateType =
    dyn api::ConnectorIntegration<RecipientCreate, RecipientCreateRequest, RecipientCreateResponse>;

#[derive(Clone, Debug, strum::EnumString, strum::Display)]
#[strum(serialize_all = "snake_case")]
pub enum PaymentMethodAuthConnectors {
    Plaid,
}

#[derive(Debug, Clone)]
pub struct ResponseRouterData<Flow, R, Request, Response> {
    pub response: R,
    pub data: PaymentAuthRouterData<Flow, Request, Response>,
    pub http_code: u16,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct ErrorResponse {
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
    pub status_code: u16,
}

impl ErrorResponse {
    fn get_not_implemented() -> Self {
        Self {
            code: "IR_00".to_string(),
            message: "This API is under development and will be made available soon.".to_string(),
            reason: None,
            status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
        }
    }
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum MerchantAccountData {
    Iban {
        iban: Secret<String>,
        name: String,
    },
    Bacs {
        account_number: Secret<String>,
        sort_code: Secret<String>,
        name: String,
    },
    FasterPayments {
        account_number: Secret<String>,
        sort_code: Secret<String>,
        name: String,
    },
    Sepa {
        iban: Secret<String>,
        name: String,
    },
    SepaInstant {
        iban: Secret<String>,
        name: String,
    },
    Elixir {
        account_number: Secret<String>,
        iban: Secret<String>,
        name: String,
    },
    Bankgiro {
        number: Secret<String>,
        name: String,
    },
    Plusgiro {
        number: Secret<String>,
        name: String,
    },
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MerchantRecipientData {
    ConnectorRecipientId(Secret<String>),
    WalletId(Secret<String>),
    AccountData(MerchantAccountData),
}

#[derive(Default, Debug, Clone, serde::Deserialize)]
pub enum ConnectorAuthType {
    BodyKey {
        client_id: Secret<String>,
        secret: Secret<String>,
    },
    #[default]
    NoKey,
}

#[derive(Clone, Debug)]
pub struct Response {
    pub headers: Option<http::HeaderMap>,
    pub response: bytes::Bytes,
    pub status_code: u16,
}

#[derive(serde::Deserialize, Clone)]
pub struct AuthServiceQueryParam {
    pub client_secret: Option<String>,
}


// Implementation: impl GlobalAttemptId
// File: crates/common_utils/src/id_type/global_id/payment.rs
// Module: common_utils
// Methods: 3 total (3 public)

impl GlobalAttemptId


// File: crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs
// Module: hyperswitch_connectors
// Public structs: 14

use common_enums::enums;
use common_utils::{
    pii::{self, Email},
    types::StringMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, RouterData},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm},
    types,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::Date;

use crate::{
    types::ResponseRouterData,
    utils::{self, PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData as _},
};

pub struct MifinityRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}

impl<T> From<(StringMajorUnit, T)> for MifinityRouterData<T> {
    fn from((amount, router_data): (StringMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data,
        }
    }
}
pub mod auth_headers {
    pub const API_VERSION: &str = "api-version";
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MifinityConnectorMetadataObject {
    pub brand_id: Secret<String>,
    pub destination_account_number: Secret<String>,
}

impl TryFrom<&Option<pii::SecretSerdeValue>> for MifinityConnectorMetadataObject {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(meta_data: &Option<pii::SecretSerdeValue>) -> Result<Self, Self::Error> {
        let metadata: Self = utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "merchant_connector_account.metadata",
            })?;
        Ok(metadata)
    }
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MifinityPaymentsRequest {
    money: Money,
    client: MifinityClient,
    address: MifinityAddress,
    validation_key: String,
    client_reference: common_utils::id_type::CustomerId,
    trace_id: String,
    description: String,
    destination_account_number: Secret<String>,
    brand_id: Secret<String>,
    return_url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    language_preference: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct Money {
    amount: StringMajorUnit,
    currency: String,
}

#[derive(Debug, Clone, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MifinityClient {
    first_name: Secret<String>,
    last_name: Secret<String>,
    phone: Secret<String>,
    dialing_code: String,
    nationality: api_models::enums::CountryAlpha2,
    email_address: Email,
    dob: Secret<Date>,
}

#[derive(Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MifinityAddress {
    address_line1: Secret<String>,
    country_code: api_models::enums::CountryAlpha2,
    city: String,
}

impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for MifinityPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &MifinityRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let metadata: MifinityConnectorMetadataObject =
            utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())
                .change_context(errors::ConnectorError::InvalidConnectorConfig {
                    config: "merchant_connector_account.metadata",
                })?;
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                WalletData::Mifinity(data) => {
                    let money = Money {
                        amount: item.amount.clone(),
                        currency: item.router_data.request.currency.to_string(),
                    };
                    let phone_details = item.router_data.get_billing_phone()?;
                    let client = MifinityClient {
                        first_name: item.router_data.get_billing_first_name()?,
                        last_name: item.router_data.get_billing_last_name()?,
                        phone: phone_details.get_number()?,
                        dialing_code: phone_details.get_country_code()?,
                        nationality: item.router_data.get_billing_country()?,
                        email_address: item.router_data.get_billing_email()?,
                        dob: data.date_of_birth.clone(),
                    };
                    let address = MifinityAddress {
                        address_line1: item.router_data.get_billing_line1()?,
                        country_code: item.router_data.get_billing_country()?,
                        city: item.router_data.get_billing_city()?,
                    };
                    let validation_key = format!(
                        "payment_validation_key_{}_{}",
                        item.router_data.merchant_id.get_string_repr(),
                        item.router_data.connector_request_reference_id.clone()
                    );
                    let client_reference = item.router_data.customer_id.clone().ok_or(
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "client_reference",
                        },
                    )?;
                    let destination_account_number = metadata.destination_account_number;
                    let trace_id = item.router_data.connector_request_reference_id.clone();
                    let brand_id = metadata.brand_id;
                    let language_preference = data.language_preference;
                    Ok(Self {
                        money,
                        client,
                        address,
                        validation_key,
                        client_reference,
                        trace_id: trace_id.clone(),
                        description: trace_id.clone(), //Connector recommend to use the traceId for a better experience in the BackOffice application later.
                        destination_account_number,
                        brand_id,
                        return_url: item.router_data.request.get_router_return_url()?,
                        language_preference,
                    })
                }
                WalletData::AliPayQr(_)
                | WalletData::AliPayRedirect(_)
                | WalletData::AliPayHkRedirect(_)
                | WalletData::AmazonPay(_)
                | WalletData::AmazonPayRedirect(_)
                | WalletData::Paysera(_)
                | WalletData::Skrill(_)
                | WalletData::BluecodeRedirect {}
                | WalletData::MomoRedirect(_)
                | WalletData::KakaoPayRedirect(_)
                | WalletData::GoPayRedirect(_)
                | WalletData::GcashRedirect(_)
                | WalletData::ApplePay(_)
                | WalletData::ApplePayRedirect(_)
                | WalletData::ApplePayThirdPartySdk(_)
                | WalletData::DanaRedirect {}
                | WalletData::GooglePay(_)
                | WalletData::GooglePayRedirect(_)
                | WalletData::GooglePayThirdPartySdk(_)
                | WalletData::MbWayRedirect(_)
                | WalletData::MobilePayRedirect(_)
                | WalletData::PaypalRedirect(_)
                | WalletData::PaypalSdk(_)
                | WalletData::Paze(_)
                | WalletData::SamsungPay(_)
                | WalletData::TwintRedirect {}
                | WalletData::VippsRedirect {}
                | WalletData::TouchNGoRedirect(_)
                | WalletData::WeChatPayRedirect(_)
                | WalletData::WeChatPayQr(_)
                | WalletData::CashappQr(_)
                | WalletData::SwishQr(_)
                | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Mifinity"),
                )
                .into()),
            },
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Mifinity"),
                )
                .into())
            }
        }
    }
}

// Auth Struct
pub struct MifinityAuthType {
    pub(super) key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for MifinityAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MifinityPaymentsResponse {
    payload: Vec<MifinityPayload>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MifinityPayload {
    trace_id: String,
    initialization_token: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, MifinityPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, MifinityPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let payload = item.response.payload.first();
        match payload {
            Some(payload) => {
                let trace_id = payload.trace_id.clone();
                let initialization_token = payload.initialization_token.clone();
                Ok(Self {
                    status: enums::AttemptStatus::AuthenticationPending,
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(trace_id.clone()),
                        redirection_data: Box::new(Some(RedirectForm::Mifinity {
                            initialization_token,
                        })),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: Some(trace_id),
                        incremental_authorization_allowed: None,
                        charges: None,
                    }),
                    ..item.data
                })
            }
            None => Ok(Self {
                status: enums::AttemptStatus::AuthenticationPending,
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::NoResponseId,
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: None,
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                ..item.data
            }),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MifinityPsyncResponse {
    payload: Vec<MifinityPsyncPayload>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MifinityPsyncPayload {
    status: MifinityPaymentStatus,
    payment_response: Option<PaymentResponse>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentResponse {
    trace_id: Option<String>,
    client_reference: Option<String>,
    validation_key: Option<String>,
    transaction_reference: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MifinityPaymentStatus {
    Successful,
    Pending,
    Failed,
    NotCompleted,
}

impl<F, T> TryFrom<ResponseRouterData<F, MifinityPsyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, MifinityPsyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let payload = item.response.payload.first();

        match payload {
            Some(payload) => {
                let status = payload.to_owned().status.clone();
                let payment_response = payload.payment_response.clone();

                match payment_response {
                    Some(payment_response) => {
                        let transaction_reference = payment_response.transaction_reference.clone();
                        Ok(Self {
                            status: enums::AttemptStatus::from(status),
                            response: Ok(PaymentsResponseData::TransactionResponse {
                                resource_id: ResponseId::ConnectorTransactionId(
                                    transaction_reference,
                                ),
                                redirection_data: Box::new(None),
                                mandate_reference: Box::new(None),
                                connector_metadata: None,
                                network_txn_id: None,
                                connector_response_reference_id: None,
                                incremental_authorization_allowed: None,
                                charges: None,
                            }),
                            ..item.data
                        })
                    }
                    None => Ok(Self {
                        status: enums::AttemptStatus::from(status),
                        response: Ok(PaymentsResponseData::TransactionResponse {
                            resource_id: ResponseId::NoResponseId,
                            redirection_data: Box::new(None),
                            mandate_reference: Box::new(None),
                            connector_metadata: None,
                            network_txn_id: None,
                            connector_response_reference_id: None,
                            incremental_authorization_allowed: None,
                            charges: None,
                        }),
                        ..item.data
                    }),
                }
            }
            None => Ok(Self {
                status: item.data.status,
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::NoResponseId,
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: None,
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                ..item.data
            }),
        }
    }
}

impl From<MifinityPaymentStatus> for enums::AttemptStatus {
    fn from(item: MifinityPaymentStatus) -> Self {
        match item {
            MifinityPaymentStatus::Successful => Self::Charged,
            MifinityPaymentStatus::Failed => Self::Failure,
            MifinityPaymentStatus::NotCompleted => Self::AuthenticationPending,
            MifinityPaymentStatus::Pending => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct MifinityErrorResponse {
    pub errors: Vec<MifinityErrorList>,
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MifinityErrorList {
    #[serde(rename = "type")]
    pub error_type: String,
    pub error_code: String,
    pub message: String,
    pub field: Option<String>,
}


// Struct: XenditErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct XenditErrorResponse


// File: crates/router/src/routes/profiles.rs
// Module: router
// Public functions: 13

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

use super::app::AppState;
use crate::{
    core::{admin::*, api_locking, errors},
    services::{api, authentication as auth, authorization::permissions},
    types::{api::admin, domain},
};

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::ProfileCreate))]
pub async fn profile_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::ProfileCreate>,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ProfileCreate;
    let payload = json_payload.into_inner();
    let merchant_id = path.into_inner();
    if let Err(api_error) = payload
        .webhook_details
        .as_ref()
        .map(|details| {
            details
                .validate()
                .map_err(|message| errors::ApiErrorResponse::InvalidRequestData { message })
        })
        .transpose()
    {
        return api::log_and_return_error_response(api_error.into());
    }

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth_data.merchant_account, auth_data.key_store),
            ));
            create_profile(state, req, merchant_context)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: permissions::Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v2"))]
#[instrument(skip_all, fields(flow = ?Flow::ProfileCreate))]
pub async fn profile_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::ProfileCreate>,
) -> HttpResponse {
    let flow = Flow::ProfileCreate;
    let payload = json_payload.into_inner();
    if let Err(api_error) = payload
        .webhook_details
        .as_ref()
        .map(|details| {
            details
                .validate()
                .map_err(|message| errors::ApiErrorResponse::InvalidRequestData { message })
        })
        .transpose()
    {
        return api::log_and_return_error_response(api_error.into());
    }

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state,
         auth::AuthenticationDataWithoutProfile {
             merchant_account,
             key_store,
         },
         req,
         _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(merchant_account, key_store),
            ));
            create_profile(state, req, merchant_context)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: permissions::Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileRetrieve))]
pub async fn profile_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ProfileId,
    )>,
) -> HttpResponse {
    let flow = Flow::ProfileRetrieve;
    let (merchant_id, profile_id) = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        profile_id,
        |state, auth_data, profile_id, _| retrieve_profile(state, profile_id, auth_data.key_store),
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: permissions::Permission::ProfileAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileRetrieve))]
pub async fn profile_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::ProfileId>,
) -> HttpResponse {
    let flow = Flow::ProfileRetrieve;
    let profile_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        profile_id,
        |state, auth::AuthenticationDataWithoutProfile { key_store, .. }, profile_id, _| {
            retrieve_profile(state, profile_id, key_store)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: permissions::Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::ProfileUpdate))]
pub async fn profile_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ProfileId,
    )>,
    json_payload: web::Json<api_models::admin::ProfileUpdate>,
) -> HttpResponse {
    let flow = Flow::ProfileUpdate;
    let (merchant_id, profile_id) = path.into_inner();
    let payload = json_payload.into_inner();
    if let Err(api_error) = payload
        .webhook_details
        .as_ref()
        .map(|details| {
            details
                .validate()
                .map_err(|message| errors::ApiErrorResponse::InvalidRequestData { message })
        })
        .transpose()
    {
        return api::log_and_return_error_response(api_error.into());
    }

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data, req, _| update_profile(state, &profile_id, auth_data.key_store, req),
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantAndProfileFromRoute {
                merchant_id: merchant_id.clone(),
                profile_id: profile_id.clone(),
                required_permission: permissions::Permission::ProfileAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileUpdate))]
pub async fn profile_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::ProfileId>,
    json_payload: web::Json<api_models::admin::ProfileUpdate>,
) -> HttpResponse {
    let flow = Flow::ProfileUpdate;
    let profile_id = path.into_inner();
    let payload = json_payload.into_inner();
    if let Err(api_error) = payload
        .webhook_details
        .as_ref()
        .map(|details| {
            details
                .validate()
                .map_err(|message| errors::ApiErrorResponse::InvalidRequestData { message })
        })
        .transpose()
    {
        return api::log_and_return_error_response(api_error.into());
    }

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth::AuthenticationDataWithoutProfile { key_store, .. }, req, _| {
            update_profile(state, &profile_id, key_store, req)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromHeader,
            &auth::JWTAuthMerchantFromHeader {
                required_permission: permissions::Permission::MerchantAccountWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::ProfileDelete))]
pub async fn profile_delete(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ProfileId,
    )>,
) -> HttpResponse {
    let flow = Flow::ProfileDelete;
    let (merchant_id, profile_id) = path.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        profile_id,
        |state, _, profile_id, _| delete_profile(state, profile_id, &merchant_id),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileList))]
pub async fn profiles_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ProfileList;
    let merchant_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.clone(),
        |state, _auth, merchant_id, _| list_profile(state, merchant_id, None),
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuthWithMerchantIdFromRoute(merchant_id.clone())),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: permissions::Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::ProfileList))]
pub async fn profiles_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ProfileList;
    let merchant_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.clone(),
        |state, auth::AuthenticationDataWithoutProfile { .. }, merchant_id, _| {
            list_profile(state, merchant_id, None)
        },
        auth::auth_type(
            &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
            &auth::JWTAuthMerchantFromRoute {
                merchant_id,
                required_permission: permissions::Permission::MerchantAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "olap", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::ProfileList))]
pub async fn profiles_list_at_profile_level(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
    let flow = Flow::ProfileList;
    let merchant_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        merchant_id.clone(),
        |state, auth, merchant_id, _| {
            list_profile(
                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: permissions::Permission::ProfileAccountRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::ToggleConnectorAgnosticMit))]
pub async fn toggle_connector_agnostic_mit(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ProfileId,
    )>,
    json_payload: web::Json<api_models::admin::ConnectorAgnosticMitChoice>,
) -> HttpResponse {
    let flow = Flow::ToggleConnectorAgnosticMit;
    let (merchant_id, profile_id) = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _: auth::AuthenticationData, req, _| {
            connector_agnostic_mit_toggle(state, &merchant_id, &profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: permissions::Permission::MerchantRoutingWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::ToggleExtendedCardInfo))]
pub async fn toggle_extended_card_info(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ProfileId,
    )>,
    json_payload: web::Json<api_models::admin::ExtendedCardInfoChoice>,
) -> HttpResponse {
    let flow = Flow::ToggleExtendedCardInfo;
    let (merchant_id, profile_id) = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, _, req, _| extended_card_info_toggle(state, &merchant_id, &profile_id, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(feature = "v1")]
#[instrument(skip_all, fields(flow = ?Flow::MerchantConnectorsList))]
pub async fn payment_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: permissions::Permission::ProfileConnectorRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    )
    .await
}


// Struct: NexixpayErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NexixpayErrorResponse


// Implementation: impl FromStr for for FailureReason
// File: crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
// Module: hyperswitch_connectors
// Methods: 1 total (0 public)

impl FromStr for for FailureReason


// Implementation: impl api::RefundSync for for Aci
// File: crates/hyperswitch_connectors/src/connectors/aci.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundSync for for Aci


// Implementation: impl api::PaymentCapture for for Powertranz
// File: crates/hyperswitch_connectors/src/connectors/powertranz.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentCapture for for Powertranz


// Function: get_output
// File: crates/euclid/src/backend.rs
// Module: euclid

pub fn get_output(&self) -> &O


// Struct: CardDetail
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct CardDetail


// Function: rotate_password
// File: crates/router/src/core/user.rs
// Module: router

pub fn rotate_password(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    request: user_api::RotatePasswordRequest,
    _req_state: ReqState,
) -> UserResponse<()>


// Trait: AuthEventMetric
// File: crates/analytics/src/auth_events/metrics.rs
// Module: analytics

pub trait AuthEventMetric<T>


// Implementation: impl api::PaymentToken for for Bamboraapac
// File: crates/hyperswitch_connectors/src/connectors/bamboraapac.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentToken for for Bamboraapac


// Struct: AciVoidResponse
// File: crates/hyperswitch_connectors/src/connectors/aci/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AciVoidResponse


// Function: success_based_routing_update_configs
// File: crates/openapi/src/routes/routing.rs
// Module: openapi

pub fn success_based_routing_update_configs()