// File: crates/router/src/core/payments/payment_methods.rs
// Module: router
// Public functions: 2

//! Contains functions of payment methods that are used in payments
//! one of such functions is `list_payment_methods`

use std::{
    collections::{BTreeMap, HashSet},
    str::FromStr,
};

use common_utils::{
    ext_traits::{OptionExt, ValueExt},
    id_type,
};
use error_stack::ResultExt;
use hyperswitch_interfaces::secrets_interface::secret_state::RawSecret;

use super::errors;
use crate::{
    configs::settings,
    core::{payment_methods, payments::helpers},
    db::errors::StorageErrorExt,
    logger, routes,
    types::{self, api, domain, storage},
};

#[cfg(feature = "v2")]
pub async fn list_payment_methods(
    state: routes::SessionState,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    payment_id: id_type::GlobalPaymentId,
    req: api_models::payments::ListMethodsForPaymentsRequest,
    header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
) -> errors::RouterResponse<api_models::payments::PaymentMethodListResponseForPayments> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();

    let payment_intent = db
        .find_payment_intent_by_id(
            key_manager_state,
            &payment_id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    validate_payment_status_for_payment_method_list(payment_intent.status)?;

    let payment_connector_accounts = db
        .list_enabled_connector_accounts_by_profile_id(
            key_manager_state,
            profile.get_id(),
            merchant_context.get_merchant_key_store(),
            common_enums::ConnectorType::PaymentProcessor,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("error when fetching merchant connector accounts")?;

    let customer_payment_methods = match &payment_intent.customer_id {
        Some(customer_id) => Some(
            payment_methods::list_customer_payment_methods_core(
                &state,
                &merchant_context,
                customer_id,
            )
            .await?,
        ),
        None => None,
    };

    let response =
        FlattenedPaymentMethodsEnabled(hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts))
            .perform_filtering(
                &state,
                &merchant_context,
                profile.get_id(),
                &req,
                &payment_intent,
            ).await?
            .store_gift_card_mca_in_redis(&payment_id, db, &profile).await
            .merge_and_transform()
            .get_required_fields(RequiredFieldsInput::new(state.conf.required_fields.clone(), payment_intent.setup_future_usage))
            .perform_surcharge_calculation()
            .populate_pm_subtype_specific_data(&state.conf.bank_config)
            .generate_response(customer_payment_methods);

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

/// Container for the inputs required for the required fields
struct RequiredFieldsInput {
    required_fields_config: settings::RequiredFields,
    setup_future_usage: common_enums::FutureUsage,
}

impl RequiredFieldsInput {
    fn new(
        required_fields_config: settings::RequiredFields,
        setup_future_usage: common_enums::FutureUsage,
    ) -> Self {
        Self {
            required_fields_config,
            setup_future_usage,
        }
    }
}

trait GetRequiredFields {
    fn get_required_fields(
        &self,
        payment_method_enabled: &MergedEnabledPaymentMethod,
    ) -> Option<&settings::RequiredFieldFinal>;
}

impl GetRequiredFields for settings::RequiredFields {
    fn get_required_fields(
        &self,
        payment_method_enabled: &MergedEnabledPaymentMethod,
    ) -> Option<&settings::RequiredFieldFinal> {
        self.0
            .get(&payment_method_enabled.payment_method_type)
            .and_then(|required_fields_for_payment_method| {
                required_fields_for_payment_method
                    .0
                    .get(&payment_method_enabled.payment_method_subtype)
            })
            .map(|connector_fields| &connector_fields.fields)
            .and_then(|connector_hashmap| {
                payment_method_enabled
                    .connectors
                    .first()
                    .and_then(|connector| connector_hashmap.get(connector))
            })
    }
}

struct FlattenedPaymentMethodsEnabled(
    hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled,
);

/// Container for the filtered payment methods
struct FilteredPaymentMethodsEnabled(
    Vec<hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector>,
);

impl FilteredPaymentMethodsEnabled {
    fn merge_and_transform(self) -> MergedEnabledPaymentMethodTypes {
        let values = self
            .0
            .into_iter()
            // BTreeMap used to ensure soretd response, otherwise the response is arbitrarily ordered
            .fold(BTreeMap::new(), |mut acc, item| {
                let key = (
                    item.payment_method,
                    item.payment_methods_enabled.payment_method_subtype,
                );
                let (experiences, connectors) = acc
                    .entry(key)
                    // HashSet used to ensure payment_experience does not have duplicates, due to multiple connectors for a pm_subtype
                    .or_insert_with(|| (HashSet::new(), Vec::new()));

                if let Some(experience) = item.payment_methods_enabled.payment_experience {
                    experiences.insert(experience);
                }
                connectors.push(item.connector);

                acc
            })
            .into_iter()
            .map(
                |(
                    (payment_method_type, payment_method_subtype),
                    (payment_experience, connectors),
                )| {
                    MergedEnabledPaymentMethod {
                        payment_method_type,
                        payment_method_subtype,
                        payment_experience: if payment_experience.is_empty() {
                            None
                        } else {
                            Some(payment_experience.into_iter().collect())
                        },
                        connectors,
                    }
                },
            )
            .collect();
        MergedEnabledPaymentMethodTypes(values)
    }
    async fn store_gift_card_mca_in_redis(
        self,
        payment_id: &id_type::GlobalPaymentId,
        db: &dyn crate::db::StorageInterface,
        profile: &domain::Profile,
    ) -> Self {
        let gift_card_connector_id = self
            .0
            .iter()
            .find(|item| item.payment_method == common_enums::PaymentMethod::GiftCard)
            .map(|item| &item.merchant_connector_id);

        if let Some(gift_card_mca) = gift_card_connector_id {
            let gc_key = payment_id.get_gift_card_connector_key();
            let redis_expiry = profile
                .get_order_fulfillment_time()
                .unwrap_or(common_utils::consts::DEFAULT_INTENT_FULFILLMENT_TIME);

            let redis_conn = db
                .get_redis_conn()
                .map_err(|redis_error| logger::error!(?redis_error))
                .ok();

            if let Some(rc) = redis_conn {
                rc.set_key_with_expiry(
                    &gc_key.as_str().into(),
                    gift_card_mca.get_string_repr().to_string(),
                    redis_expiry,
                )
                .await
                .attach_printable("Failed to store gift card mca_id in redis")
                .unwrap_or_else(|error| {
                    logger::error!(?error);
                })
            };
        } else {
            logger::error!(
                "Could not find any configured MCA supporting gift card for payment_id -> {}",
                payment_id.get_string_repr()
            );
        }

        self
    }
}

/// Element container to hold the filtered payment methods with payment_experience and connectors merged for a pm_subtype
struct MergedEnabledPaymentMethod {
    payment_method_subtype: common_enums::PaymentMethodType,
    payment_method_type: common_enums::PaymentMethod,
    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
    connectors: Vec<api_models::enums::Connector>,
}

/// Container to hold the filtered payment methods with payment_experience and connectors merged for a pm_subtype
struct MergedEnabledPaymentMethodTypes(Vec<MergedEnabledPaymentMethod>);

impl MergedEnabledPaymentMethodTypes {
    fn get_required_fields(
        self,
        input: RequiredFieldsInput,
    ) -> RequiredFieldsForEnabledPaymentMethodTypes {
        let required_fields_config = input.required_fields_config;
        let is_cit_transaction = input.setup_future_usage == common_enums::FutureUsage::OffSession;

        let required_fields_info = self
            .0
            .into_iter()
            .map(|payment_methods_enabled| {
                let required_fields =
                    required_fields_config.get_required_fields(&payment_methods_enabled);

                let required_fields = required_fields
                    .map(|required_fields| {
                        let common_required_fields = required_fields
                            .common
                            .iter()
                            .flatten()
                            .map(ToOwned::to_owned);

                        // Collect mandate required fields because this is for zero auth mandates only
                        let mandate_required_fields = required_fields
                            .mandate
                            .iter()
                            .flatten()
                            .map(ToOwned::to_owned);

                        // Collect non-mandate required fields because this is for zero auth mandates only
                        let non_mandate_required_fields = required_fields
                            .non_mandate
                            .iter()
                            .flatten()
                            .map(ToOwned::to_owned);

                        // Combine mandate and non-mandate required fields based on setup_future_usage
                        if is_cit_transaction {
                            common_required_fields
                                .chain(non_mandate_required_fields)
                                .collect::<Vec<_>>()
                        } else {
                            common_required_fields
                                .chain(mandate_required_fields)
                                .collect::<Vec<_>>()
                        }
                    })
                    .unwrap_or_default();

                RequiredFieldsForEnabledPaymentMethod {
                    required_fields,
                    payment_method_type: payment_methods_enabled.payment_method_type,
                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
                    payment_experience: payment_methods_enabled.payment_experience,
                    connectors: payment_methods_enabled.connectors,
                }
            })
            .collect();

        RequiredFieldsForEnabledPaymentMethodTypes(required_fields_info)
    }
}

/// Element container to hold the filtered payment methods with required fields
struct RequiredFieldsForEnabledPaymentMethod {
    required_fields: Vec<api_models::payment_methods::RequiredFieldInfo>,
    payment_method_subtype: common_enums::PaymentMethodType,
    payment_method_type: common_enums::PaymentMethod,
    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
    connectors: Vec<api_models::enums::Connector>,
}

/// Container to hold the filtered payment methods enabled with required fields
struct RequiredFieldsForEnabledPaymentMethodTypes(Vec<RequiredFieldsForEnabledPaymentMethod>);

impl RequiredFieldsForEnabledPaymentMethodTypes {
    fn perform_surcharge_calculation(
        self,
    ) -> RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
        // TODO: Perform surcharge calculation
        let details_with_surcharge = self
            .0
            .into_iter()
            .map(
                |payment_methods_enabled| RequiredFieldsAndSurchargeForEnabledPaymentMethodType {
                    payment_method_type: payment_methods_enabled.payment_method_type,
                    required_fields: payment_methods_enabled.required_fields,
                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
                    payment_experience: payment_methods_enabled.payment_experience,
                    surcharge: None,
                    connectors: payment_methods_enabled.connectors,
                },
            )
            .collect();

        RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes(details_with_surcharge)
    }
}

/// Element Container to hold the filtered payment methods enabled with required fields and surcharge
struct RequiredFieldsAndSurchargeForEnabledPaymentMethodType {
    required_fields: Vec<api_models::payment_methods::RequiredFieldInfo>,
    payment_method_subtype: common_enums::PaymentMethodType,
    payment_method_type: common_enums::PaymentMethod,
    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
    connectors: Vec<api_models::enums::Connector>,
    surcharge: Option<api_models::payment_methods::SurchargeDetailsResponse>,
}

/// Container to hold the filtered payment methods enabled with required fields and surcharge
struct RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes(
    Vec<RequiredFieldsAndSurchargeForEnabledPaymentMethodType>,
);

fn get_pm_subtype_specific_data(
    bank_config: &settings::BankRedirectConfig,
    payment_method_type: common_enums::enums::PaymentMethod,
    payment_method_subtype: common_enums::enums::PaymentMethodType,
    connectors: &[api_models::enums::Connector],
) -> Option<api_models::payment_methods::PaymentMethodSubtypeSpecificData> {
    match payment_method_type {
        // TODO: Return card_networks
        common_enums::PaymentMethod::Card | common_enums::PaymentMethod::CardRedirect => None,

        common_enums::PaymentMethod::BankRedirect
        | common_enums::PaymentMethod::BankTransfer
        | common_enums::PaymentMethod::BankDebit
        | common_enums::PaymentMethod::OpenBanking => {
            if let Some(connector_bank_names) = bank_config.0.get(&payment_method_subtype) {
                let bank_names = connectors
                    .iter()
                    .filter_map(|connector| {
                        connector_bank_names.0.get(&connector.to_string())
                            .map(|connector_hash_set| {
                                connector_hash_set.banks.clone()
                            })
                            .or_else(|| {
                                logger::debug!("Could not find any configured connectors for payment_method -> {payment_method_subtype} for connector -> {connector}");
                                None
                            })
                    })
                    .flatten()
                    .collect();
                Some(
                    api_models::payment_methods::PaymentMethodSubtypeSpecificData::Bank {
                        bank_names,
                    },
                )
            } else {
                logger::debug!("Could not find any configured banks for payment_method -> {payment_method_subtype}");
                None
            }
        }

        common_enums::PaymentMethod::PayLater
        | common_enums::PaymentMethod::Wallet
        | common_enums::PaymentMethod::Crypto
        | common_enums::PaymentMethod::Reward
        | common_enums::PaymentMethod::RealTimePayment
        | common_enums::PaymentMethod::Upi
        | common_enums::PaymentMethod::Voucher
        | common_enums::PaymentMethod::GiftCard
        | common_enums::PaymentMethod::MobilePayment => None,
    }
}

impl RequiredFieldsAndSurchargeForEnabledPaymentMethodTypes {
    fn populate_pm_subtype_specific_data(
        self,
        bank_config: &settings::BankRedirectConfig,
    ) -> RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes {
        let response_payment_methods = self
            .0
            .into_iter()
            .map(|payment_methods_enabled| {
                RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType {
                    payment_method_type: payment_methods_enabled.payment_method_type,
                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
                    payment_experience: payment_methods_enabled.payment_experience,
                    required_fields: payment_methods_enabled.required_fields,
                    surcharge: payment_methods_enabled.surcharge,
                    pm_subtype_specific_data: get_pm_subtype_specific_data(
                        bank_config,
                        payment_methods_enabled.payment_method_type,
                        payment_methods_enabled.payment_method_subtype,
                        &payment_methods_enabled.connectors,
                    ),
                }
            })
            .collect();

        RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes(
            response_payment_methods,
        )
    }
}

/// Element Container to hold the filtered payment methods enabled with required fields, surcharge and subtype specific data
struct RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType {
    required_fields: Vec<api_models::payment_methods::RequiredFieldInfo>,
    payment_method_subtype: common_enums::PaymentMethodType,
    payment_method_type: common_enums::PaymentMethod,
    payment_experience: Option<Vec<common_enums::PaymentExperience>>,
    surcharge: Option<api_models::payment_methods::SurchargeDetailsResponse>,
    pm_subtype_specific_data: Option<api_models::payment_methods::PaymentMethodSubtypeSpecificData>,
}

/// Container to hold the filtered payment methods enabled with required fields, surcharge and subtype specific data
struct RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes(
    Vec<RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodType>,
);

impl RequiredFieldsAndSurchargeWithExtraInfoForEnabledPaymentMethodTypes {
    fn generate_response(
        self,
        customer_payment_methods: Option<
            Vec<api_models::payment_methods::CustomerPaymentMethodResponseItem>,
        >,
    ) -> api_models::payments::PaymentMethodListResponseForPayments {
        let response_payment_methods = self
            .0
            .into_iter()
            .map(|payment_methods_enabled| {
                api_models::payments::ResponsePaymentMethodTypesForPayments {
                    payment_method_type: payment_methods_enabled.payment_method_type,
                    payment_method_subtype: payment_methods_enabled.payment_method_subtype,
                    payment_experience: payment_methods_enabled.payment_experience,
                    required_fields: payment_methods_enabled.required_fields,
                    surcharge_details: payment_methods_enabled.surcharge,
                    extra_information: payment_methods_enabled.pm_subtype_specific_data,
                }
            })
            .collect();

        api_models::payments::PaymentMethodListResponseForPayments {
            payment_methods_enabled: response_payment_methods,
            customer_payment_methods,
        }
    }
}

impl FlattenedPaymentMethodsEnabled {
    async fn perform_filtering(
        self,
        state: &routes::SessionState,
        merchant_context: &domain::MerchantContext,
        profile_id: &id_type::ProfileId,
        req: &api_models::payments::ListMethodsForPaymentsRequest,
        payment_intent: &hyperswitch_domain_models::payments::PaymentIntent,
    ) -> errors::RouterResult<FilteredPaymentMethodsEnabled> {
        let billing_address = payment_intent
            .billing_address
            .clone()
            .and_then(|address| address.into_inner().address);

        let mut response: Vec<hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector> = vec![];

        for payment_method_enabled_details in self.0.payment_methods_enabled {
            filter_payment_methods(
                payment_method_enabled_details,
                req,
                &mut response,
                Some(payment_intent),
                billing_address.as_ref(),
                &state.conf,
            )
            .await?;
        }

        Ok(FilteredPaymentMethodsEnabled(response))
    }
}

// note: v2 type for ListMethodsForPaymentMethodsRequest will not have the installment_payment_enabled field,
#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn filter_payment_methods(
    payment_method_type_details: hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector,
    req: &api_models::payments::ListMethodsForPaymentsRequest,
    resp: &mut Vec<
        hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector,
    >,
    payment_intent: Option<&storage::PaymentIntent>,
    address: Option<&hyperswitch_domain_models::address::AddressDetails>,
    configs: &settings::Settings<RawSecret>,
) -> errors::CustomResult<(), errors::ApiErrorResponse> {
    let payment_method = payment_method_type_details.payment_method;
    let mut payment_method_object = payment_method_type_details.payment_methods_enabled.clone();

    // filter based on request parameters
    let request_based_filter =
        filter_recurring_based(&payment_method_object, req.recurring_enabled)
            && filter_amount_based(&payment_method_object, req.amount)
            && filter_card_network_based(
                payment_method_object.card_networks.as_ref(),
                req.card_networks.as_ref(),
                payment_method_object.payment_method_subtype,
            );

    // filter based on payment intent
    let intent_based_filter = if let Some(payment_intent) = payment_intent {
        filter_country_based(address, &payment_method_object)
            && filter_currency_based(
                payment_intent.amount_details.currency,
                &payment_method_object,
            )
            && filter_amount_based(
                &payment_method_object,
                Some(payment_intent.amount_details.calculate_net_amount()),
            )
            && filter_zero_mandate_based(configs, payment_intent, &payment_method_type_details)
            && filter_allowed_payment_method_types_based(
                payment_intent.allowed_payment_method_types.as_ref(),
                payment_method_object.payment_method_subtype,
            )
    } else {
        true
    };

    // filter based on payment method type configuration
    let config_based_filter = filter_config_based(
        configs,
        &payment_method_type_details.connector.to_string(),
        payment_method_object.payment_method_subtype,
        payment_intent,
        &mut payment_method_object.card_networks,
        address.and_then(|inner| inner.country),
        payment_intent.map(|value| value.amount_details.currency),
    );

    // if all filters pass, add the payment method type details to the response
    if request_based_filter && intent_based_filter && config_based_filter {
        resp.push(payment_method_type_details);
    }

    Ok(())
}

// filter based on country supported by payment method type
// return true if the intent's country is null or if the country is in the accepted countries list
fn filter_country_based(
    address: Option<&hyperswitch_domain_models::address::AddressDetails>,
    pm: &common_types::payment_methods::RequestPaymentMethodTypes,
) -> bool {
    address.is_none_or(|address| {
        address.country.as_ref().is_none_or(|country| {
            pm.accepted_countries.as_ref().is_none_or(|ac| match ac {
                common_types::payment_methods::AcceptedCountries::EnableOnly(acc) => {
                    acc.contains(country)
                }
                common_types::payment_methods::AcceptedCountries::DisableOnly(den) => {
                    !den.contains(country)
                }
                common_types::payment_methods::AcceptedCountries::AllAccepted => true,
            })
        })
    })
}

// filter based on currency supported by payment method type
// return true if the intent's currency is null or if the currency is in the accepted currencies list
fn filter_currency_based(
    currency: common_enums::Currency,
    pm: &common_types::payment_methods::RequestPaymentMethodTypes,
) -> bool {
    pm.accepted_currencies.as_ref().is_none_or(|ac| match ac {
        common_types::payment_methods::AcceptedCurrencies::EnableOnly(acc) => {
            acc.contains(&currency)
        }
        common_types::payment_methods::AcceptedCurrencies::DisableOnly(den) => {
            !den.contains(&currency)
        }
        common_types::payment_methods::AcceptedCurrencies::AllAccepted => true,
    })
}

// filter based on payment method type configuration
// return true if the payment method type is in the configuration for the connector
// return true if the configuration is not available for the connector
fn filter_config_based<'a>(
    config: &'a settings::Settings<RawSecret>,
    connector: &'a str,
    payment_method_type: common_enums::PaymentMethodType,
    payment_intent: Option<&storage::PaymentIntent>,
    card_network: &mut Option<Vec<common_enums::CardNetwork>>,
    country: Option<common_enums::CountryAlpha2>,
    currency: Option<common_enums::Currency>,
) -> bool {
    config
        .pm_filters
        .0
        .get(connector)
        .or_else(|| config.pm_filters.0.get("default"))
        .and_then(|inner| match payment_method_type {
            common_enums::PaymentMethodType::Credit | common_enums::PaymentMethodType::Debit => {
                inner
                    .0
                    .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                        payment_method_type,
                    ))
                    .map(|value| filter_config_country_currency_based(value, country, currency))
            }
            payment_method_type => inner
                .0
                .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                    payment_method_type,
                ))
                .map(|value| filter_config_country_currency_based(value, country, currency)),
        })
        .unwrap_or(true)
}

// filter country and currency based on config for payment method type
// return true if the country and currency are in the accepted countries and currencies list
fn filter_config_country_currency_based(
    item: &settings::CurrencyCountryFlowFilter,
    country: Option<common_enums::CountryAlpha2>,
    currency: Option<common_enums::Currency>,
) -> bool {
    let country_condition = item
        .country
        .as_ref()
        .zip(country.as_ref())
        .map(|(lhs, rhs)| lhs.contains(rhs));
    let currency_condition = item
        .currency
        .as_ref()
        .zip(currency)
        .map(|(lhs, rhs)| lhs.contains(&rhs));
    country_condition.unwrap_or(true) && currency_condition.unwrap_or(true)
}

// filter based on recurring enabled parameter of request
// return true if recurring_enabled is null or if it matches the payment method's recurring_enabled
fn filter_recurring_based(
    payment_method: &common_types::payment_methods::RequestPaymentMethodTypes,
    recurring_enabled: Option<bool>,
) -> bool {
    recurring_enabled.is_none_or(|enabled| payment_method.recurring_enabled == Some(enabled))
}

// filter based on valid amount range of payment method type
// return true if the amount is within the payment method's minimum and maximum amount range
// return true if the amount is null or zero
fn filter_amount_based(
    payment_method: &common_types::payment_methods::RequestPaymentMethodTypes,
    amount: Option<types::MinorUnit>,
) -> bool {
    let min_check = amount
        .and_then(|amt| payment_method.minimum_amount.map(|min_amt| amt >= min_amt))
        .unwrap_or(true);
    let max_check = amount
        .and_then(|amt| payment_method.maximum_amount.map(|max_amt| amt <= max_amt))
        .unwrap_or(true);
    (min_check && max_check) || amount == Some(types::MinorUnit::zero())
}

// return true if the intent is a zero mandate intent and the payment method is supported for zero mandates
// return false if the intent is a zero mandate intent and the payment method is not supported for zero mandates
// return true if the intent is not a zero mandate intent
fn filter_zero_mandate_based(
    configs: &settings::Settings<RawSecret>,
    payment_intent: &storage::PaymentIntent,
    payment_method_type_details: &hyperswitch_domain_models::merchant_connector_account::PaymentMethodsEnabledForConnector,
) -> bool {
    if payment_intent.setup_future_usage == common_enums::FutureUsage::OffSession
        && payment_intent.amount_details.calculate_net_amount() == types::MinorUnit::zero()
    {
        configs
            .zero_mandates
            .supported_payment_methods
            .0
            .get(&payment_method_type_details.payment_method)
            .and_then(|supported_pm_for_mandates| {
                supported_pm_for_mandates
                    .0
                    .get(
                        &payment_method_type_details
                            .payment_methods_enabled
                            .payment_method_subtype,
                    )
                    .map(|supported_connector_for_mandates| {
                        supported_connector_for_mandates
                            .connector_list
                            .contains(&payment_method_type_details.connector)
                    })
            })
            .unwrap_or(false)
    } else {
        true
    }
}

// filter based on allowed payment method types
// return true if the allowed types are null or if the payment method type is in the allowed types list
fn filter_allowed_payment_method_types_based(
    allowed_types: Option<&Vec<api_models::enums::PaymentMethodType>>,
    payment_method_type: api_models::enums::PaymentMethodType,
) -> bool {
    allowed_types.is_none_or(|pm| pm.contains(&payment_method_type))
}

// filter based on card networks
// return true if the payment method type's card networks are a subset of the request's card networks
// return true if the card networks are not specified in the request
fn filter_card_network_based(
    pm_card_networks: Option<&Vec<api_models::enums::CardNetwork>>,
    request_card_networks: Option<&Vec<api_models::enums::CardNetwork>>,
    pm_type: api_models::enums::PaymentMethodType,
) -> bool {
    match pm_type {
        api_models::enums::PaymentMethodType::Credit
        | api_models::enums::PaymentMethodType::Debit => {
            match (pm_card_networks, request_card_networks) {
                (Some(pm_card_networks), Some(request_card_networks)) => request_card_networks
                    .iter()
                    .all(|card_network| pm_card_networks.contains(card_network)),
                (None, Some(_)) => false,
                _ => true,
            }
        }
        _ => true,
    }
}

/// Validate if payment methods list can be performed on the current status of payment intent
fn validate_payment_status_for_payment_method_list(
    intent_status: common_enums::IntentStatus,
) -> Result<(), errors::ApiErrorResponse> {
    match intent_status {
        common_enums::IntentStatus::RequiresPaymentMethod => Ok(()),
        common_enums::IntentStatus::Succeeded
        | common_enums::IntentStatus::Conflicted
        | common_enums::IntentStatus::Failed
        | common_enums::IntentStatus::Cancelled
        | common_enums::IntentStatus::CancelledPostCapture
        | common_enums::IntentStatus::Processing
        | common_enums::IntentStatus::RequiresCustomerAction
        | common_enums::IntentStatus::RequiresMerchantAction
        | common_enums::IntentStatus::RequiresCapture
        | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture
        | common_enums::IntentStatus::PartiallyCaptured
        | common_enums::IntentStatus::RequiresConfirmation
        | common_enums::IntentStatus::PartiallyCapturedAndCapturable
        | common_enums::IntentStatus::Expired => {
            Err(errors::ApiErrorResponse::PaymentUnexpectedState {
                current_flow: "list_payment_methods".to_string(),
                field_name: "status".to_string(),
                current_value: intent_status.to_string(),
                states: ["requires_payment_method".to_string()].join(", "),
            })
        }
    }
}


// Struct: NonThreeDSResponseData
// File: crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NonThreeDSResponseData


// Function: perform_decide_gateway_call_with_open_router
// File: crates/router/src/core/payments/routing.rs
// Module: router

pub fn perform_decide_gateway_call_with_open_router<F, D>(
    state: &SessionState,
    mut routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
    profile_id: &common_utils::id_type::ProfileId,
    payment_attempt: &oss_storage::PaymentAttempt,
    is_elimination_enabled: bool,
    old_payment_data: &mut D,
) -> RoutingResult<Vec<api_routing::RoutableConnectorChoice>>
where
    F: Send + Clone,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,


// File: crates/pm_auth/src/connector/plaid/transformers.rs
// Module: pm_auth
// Public structs: 21

use std::collections::HashMap;

use common_enums::{PaymentMethod, PaymentMethodType};
use common_utils::{id_type, types as util_types};
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{core::errors, types};

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidLinkTokenRequest {
    client_name: String,
    country_codes: Vec<String>,
    language: String,
    products: Vec<String>,
    user: User,
    android_package_name: Option<String>,
    redirect_uri: Option<String>,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct User {
    pub client_user_id: id_type::CustomerId,
}

impl TryFrom<&types::LinkTokenRouterData> for PlaidLinkTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::LinkTokenRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            client_name: item.request.client_name.clone(),
            country_codes: item.request.country_codes.clone().ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "country_codes",
                },
            )?,
            language: item.request.language.clone().unwrap_or("en".to_string()),
            products: vec!["auth".to_string()],
            user: User {
                client_user_id: item.request.user_info.clone().ok_or(
                    errors::ConnectorError::MissingRequiredField {
                        field_name: "country_codes",
                    },
                )?,
            },
            android_package_name: match item.request.client_platform {
                Some(api_models::enums::ClientPlatform::Android) => {
                    item.request.android_package_name.clone()
                }
                Some(api_models::enums::ClientPlatform::Ios)
                | Some(api_models::enums::ClientPlatform::Web)
                | Some(api_models::enums::ClientPlatform::Unknown)
                | None => None,
            },
            redirect_uri: match item.request.client_platform {
                Some(api_models::enums::ClientPlatform::Ios) => item.request.redirect_uri.clone(),
                Some(api_models::enums::ClientPlatform::Android)
                | Some(api_models::enums::ClientPlatform::Web)
                | Some(api_models::enums::ClientPlatform::Unknown)
                | None => None,
            },
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidLinkTokenResponse {
    link_token: String,
}

impl<F, T>
    TryFrom<types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::LinkTokenResponse>>
    for types::PaymentAuthRouterData<F, T, types::LinkTokenResponse>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::LinkTokenResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(types::LinkTokenResponse {
                link_token: item.response.link_token,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidExchangeTokenRequest {
    public_token: String,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidExchangeTokenResponse {
    pub access_token: String,
}

impl<F, T>
    TryFrom<
        types::ResponseRouterData<F, PlaidExchangeTokenResponse, T, types::ExchangeTokenResponse>,
    > for types::PaymentAuthRouterData<F, T, types::ExchangeTokenResponse>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: types::ResponseRouterData<
            F,
            PlaidExchangeTokenResponse,
            T,
            types::ExchangeTokenResponse,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(types::ExchangeTokenResponse {
                access_token: item.response.access_token,
            }),
            ..item.data
        })
    }
}

impl TryFrom<&types::ExchangeTokenRouterData> for PlaidExchangeTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::ExchangeTokenRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            public_token: item.request.public_token.clone(),
        })
    }
}

#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct PlaidRecipientCreateRequest {
    pub name: String,
    #[serde(flatten)]
    pub account_data: PlaidRecipientAccountData,
    pub address: Option<PlaidRecipientCreateAddress>,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidRecipientCreateResponse {
    pub recipient_id: String,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PlaidRecipientAccountData {
    Iban(Secret<String>),
    Bacs {
        sort_code: Secret<String>,
        account: Secret<String>,
    },
}

impl TryFrom<&types::RecipientAccountData> for PlaidRecipientAccountData {
    type Error = errors::ConnectorError;

    fn try_from(item: &types::RecipientAccountData) -> Result<Self, Self::Error> {
        match item {
            types::RecipientAccountData::Iban(iban) => Ok(Self::Iban(iban.clone())),
            types::RecipientAccountData::Bacs {
                sort_code,
                account_number,
            } => Ok(Self::Bacs {
                sort_code: sort_code.clone(),
                account: account_number.clone(),
            }),
            types::RecipientAccountData::FasterPayments { .. }
            | types::RecipientAccountData::Sepa(_)
            | types::RecipientAccountData::SepaInstant(_)
            | types::RecipientAccountData::Elixir { .. }
            | types::RecipientAccountData::Bankgiro(_)
            | types::RecipientAccountData::Plusgiro(_) => {
                Err(errors::ConnectorError::InvalidConnectorConfig {
                    config: "Invalid payment method selected. Only Iban, Bacs Supported",
                })
            }
        }
    }
}
#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct PlaidRecipientCreateAddress {
    pub street: String,
    pub city: String,
    pub postal_code: String,
    pub country: String,
}

impl From<&types::RecipientCreateAddress> for PlaidRecipientCreateAddress {
    fn from(item: &types::RecipientCreateAddress) -> Self {
        Self {
            street: item.street.clone(),
            city: item.city.clone(),
            postal_code: item.postal_code.clone(),
            country: common_enums::CountryAlpha2::to_string(&item.country),
        }
    }
}

impl TryFrom<&types::RecipientCreateRouterData> for PlaidRecipientCreateRequest {
    type Error = errors::ConnectorError;

    fn try_from(item: &types::RecipientCreateRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            name: item.request.name.clone(),
            account_data: PlaidRecipientAccountData::try_from(&item.request.account_data)?,
            address: item
                .request
                .address
                .as_ref()
                .map(PlaidRecipientCreateAddress::from),
        })
    }
}

impl<F, T>
    From<
        types::ResponseRouterData<
            F,
            PlaidRecipientCreateResponse,
            T,
            types::RecipientCreateResponse,
        >,
    > for types::PaymentAuthRouterData<F, T, types::RecipientCreateResponse>
{
    fn from(
        item: types::ResponseRouterData<
            F,
            PlaidRecipientCreateResponse,
            T,
            types::RecipientCreateResponse,
        >,
    ) -> Self {
        Self {
            response: Ok(types::RecipientCreateResponse {
                recipient_id: item.response.recipient_id,
            }),
            ..item.data
        }
    }
}
#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidBankAccountCredentialsRequest {
    access_token: String,
    options: Option<BankAccountCredentialsOptions>,
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsResponse {
    pub accounts: Vec<PlaidBankAccountCredentialsAccounts>,
    pub numbers: PlaidBankAccountCredentialsNumbers,
    // pub item: PlaidBankAccountCredentialsItem,
    pub request_id: String,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct BankAccountCredentialsOptions {
    account_ids: Vec<String>,
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsAccounts {
    pub account_id: String,
    pub name: String,
    pub subtype: Option<String>,
    pub balances: Option<PlaidBankAccountCredentialsBalances>,
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct PlaidBankAccountCredentialsBalances {
    pub available: Option<util_types::FloatMajorUnit>,
    pub current: Option<util_types::FloatMajorUnit>,
    pub limit: Option<util_types::FloatMajorUnit>,
    pub iso_currency_code: Option<String>,
    pub unofficial_currency_code: Option<String>,
    pub last_updated_datetime: Option<String>,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsNumbers {
    pub ach: Vec<PlaidBankAccountCredentialsACH>,
    pub eft: Vec<PlaidBankAccountCredentialsEFT>,
    pub international: Vec<PlaidBankAccountCredentialsInternational>,
    pub bacs: Vec<PlaidBankAccountCredentialsBacs>,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsItem {
    pub item_id: String,
    pub institution_id: Option<String>,
    pub webhook: Option<String>,
    pub error: Option<PlaidErrorResponse>,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsACH {
    pub account_id: String,
    pub account: String,
    pub routing: String,
    pub wire_routing: Option<String>,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsEFT {
    pub account_id: String,
    pub account: String,
    pub institution: String,
    pub branch: String,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsInternational {
    pub account_id: String,
    pub iban: String,
    pub bic: String,
}

#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct PlaidBankAccountCredentialsBacs {
    pub account_id: String,
    pub account: String,
    pub sort_code: String,
}

impl TryFrom<&types::BankDetailsRouterData> for PlaidBankAccountCredentialsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::BankDetailsRouterData) -> Result<Self, Self::Error> {
        let options = item.request.optional_ids.as_ref().map(|bank_account_ids| {
            let ids = bank_account_ids
                .ids
                .iter()
                .map(|id| id.peek().to_string())
                .collect::<Vec<_>>();

            BankAccountCredentialsOptions { account_ids: ids }
        });

        Ok(Self {
            access_token: item.request.access_token.peek().to_string(),
            options,
        })
    }
}

impl<F, T>
    TryFrom<
        types::ResponseRouterData<
            F,
            PlaidBankAccountCredentialsResponse,
            T,
            types::BankAccountCredentialsResponse,
        >,
    > for types::PaymentAuthRouterData<F, T, types::BankAccountCredentialsResponse>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: types::ResponseRouterData<
            F,
            PlaidBankAccountCredentialsResponse,
            T,
            types::BankAccountCredentialsResponse,
        >,
    ) -> Result<Self, Self::Error> {
        let (account_numbers, accounts_info) = (item.response.numbers, item.response.accounts);
        let mut bank_account_vec = Vec::new();
        let mut id_to_subtype = HashMap::new();

        accounts_info.into_iter().for_each(|acc| {
            id_to_subtype.insert(
                acc.account_id,
                (
                    acc.subtype,
                    acc.name,
                    acc.balances.and_then(|balance| balance.available),
                ),
            );
        });

        account_numbers.ach.into_iter().for_each(|ach| {
            let (acc_type, acc_name, available_balance) = if let Some((
                _type,
                name,
                available_balance,
            )) = id_to_subtype.get(&ach.account_id)
            {
                (_type.to_owned(), Some(name.clone()), *available_balance)
            } else {
                (None, None, None)
            };

            let account_details =
                types::PaymentMethodTypeDetails::Ach(types::BankAccountDetailsAch {
                    account_number: Secret::new(ach.account),
                    routing_number: Secret::new(ach.routing),
                });

            let bank_details_new = types::BankAccountDetails {
                account_name: acc_name,
                account_details,
                payment_method_type: PaymentMethodType::Ach,
                payment_method: PaymentMethod::BankDebit,
                account_id: ach.account_id.into(),
                account_type: acc_type,
                balance: available_balance,
            };

            bank_account_vec.push(bank_details_new);
        });

        account_numbers.bacs.into_iter().for_each(|bacs| {
            let (acc_type, acc_name, available_balance) =
                if let Some((_type, name, available_balance)) = id_to_subtype.get(&bacs.account_id)
                {
                    (_type.to_owned(), Some(name.clone()), *available_balance)
                } else {
                    (None, None, None)
                };

            let account_details =
                types::PaymentMethodTypeDetails::Bacs(types::BankAccountDetailsBacs {
                    account_number: Secret::new(bacs.account),
                    sort_code: Secret::new(bacs.sort_code),
                });

            let bank_details_new = types::BankAccountDetails {
                account_name: acc_name,
                account_details,
                payment_method_type: PaymentMethodType::Bacs,
                payment_method: PaymentMethod::BankDebit,
                account_id: bacs.account_id.into(),
                account_type: acc_type,
                balance: available_balance,
            };

            bank_account_vec.push(bank_details_new);
        });

        account_numbers.international.into_iter().for_each(|sepa| {
            let (acc_type, acc_name, available_balance) =
                if let Some((_type, name, available_balance)) = id_to_subtype.get(&sepa.account_id)
                {
                    (_type.to_owned(), Some(name.clone()), *available_balance)
                } else {
                    (None, None, None)
                };

            let account_details =
                types::PaymentMethodTypeDetails::Sepa(types::BankAccountDetailsSepa {
                    iban: Secret::new(sepa.iban),
                    bic: Secret::new(sepa.bic),
                });

            let bank_details_new = types::BankAccountDetails {
                account_name: acc_name,
                account_details,
                payment_method_type: PaymentMethodType::Sepa,
                payment_method: PaymentMethod::BankDebit,
                account_id: sepa.account_id.into(),
                account_type: acc_type,
                balance: available_balance,
            };

            bank_account_vec.push(bank_details_new);
        });

        Ok(Self {
            response: Ok(types::BankAccountCredentialsResponse {
                credentials: bank_account_vec,
            }),
            ..item.data
        })
    }
}
pub struct PlaidAuthType {
    pub client_id: Secret<String>,
    pub secret: Secret<String>,
}

impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            types::ConnectorAuthType::BodyKey { client_id, secret } => Ok(Self {
                client_id: client_id.to_owned(),
                secret: secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidErrorResponse {
    pub display_message: Option<String>,
    pub error_code: Option<String>,
    pub error_message: String,
    pub error_type: Option<String>,
}


// Function: is_true
// File: crates/common_types/src/primitive_wrappers.rs
// Module: common_types

// Documentation: returns the inner bool value

pub fn is_true(&self) -> bool


pub struct PayseraData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct GooglePayRedirectData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct GooglePayThirdPartySdkData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct ApplePayThirdPartySdkData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPayRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPay {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPayQr {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct CashappQr {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaypalRedirection {
    /// paypal's email address
    #[schema(max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
    pub email: Option<Email>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AliPayQr {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AliPayRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AliPayHkRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct BluecodeQrRedirect {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct MomoRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct KakaoPayRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct GoPayRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct GcashRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct MobilePayRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct MbWayRedirection {
    /// Telephone number of the shopper. Should be Portuguese phone number.
    #[schema(value_type = String)]
    pub telephone_number: Option<Secret<String>>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct GooglePayPaymentMethodInfo {
    /// The name of the card network
    pub card_network: String,
    /// The details of the card
    pub card_details: String,
    //assurance_details of the card
    pub assurance_details: Option<GooglePayAssuranceDetails>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct GooglePayAssuranceDetails {
    ///indicates that Cardholder possession validation has been performed
    pub card_holder_authenticated: bool,
    /// indicates that identification and verifications (ID&V) was performed
    pub account_verified: bool,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PayPalWalletData {
    /// Token generated for the Apple pay
    pub token: String,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct TouchNGoRedirection {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct SwishQrData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct RevolutPayData {}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct MifinityData {
    #[schema(value_type = Date)]
    pub date_of_birth: Secret<Date>,
    pub language_preference: Option<String>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct AmazonPayWalletData {
    /// Checkout Session identifier
    pub checkout_session_id: String,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct ApplePayWalletData {
    /// The payment data of Apple pay
    #[schema(value_type = ApplePayPaymentData)]
    pub payment_data: common_types::payments::ApplePayPaymentData,
    /// The payment method of Apple pay
    pub payment_method: ApplepayPaymentMethod,
    /// The unique identifier for the transaction
    pub transaction_identifier: String,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct ApplepayPaymentMethod {
    /// The name to be displayed on Apple Pay button
    pub display_name: String,
    /// The network of the Apple pay payment method
    pub network: String,
    /// The type of the payment method
    #[serde(rename = "type")]
    pub pm_type: String,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct CardResponse {
    pub last4: Option<String>,
    pub card_type: Option<String>,
    #[schema(value_type = Option<CardNetwork>, example = "Visa")]
    pub card_network: Option<api_enums::CardNetwork>,
    pub card_issuer: Option<String>,
    pub card_issuing_country: Option<String>,
    pub card_isin: Option<String>,
    pub card_extended_bin: Option<String>,
    #[schema(value_type = Option<String>)]
    pub card_exp_month: Option<Secret<String>>,
    #[schema(value_type = Option<String>)]
    pub card_exp_year: Option<Secret<String>>,
    #[schema(value_type = Option<String>)]
    pub card_holder_name: Option<Secret<String>>,
    pub payment_checks: Option<serde_json::Value>,
    pub authentication_data: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct RewardData {
    /// The merchant ID with which we have to call the connector
    #[schema(value_type = String)]
    pub merchant_id: id_type::MerchantId,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct BoletoVoucherData {
    /// The shopper's social security number (CPF or CNPJ)
    #[schema(value_type = Option<String>)]
    pub social_security_number: Option<Secret<String>>,

    /// The shopper's bank account number associated with the boleto
    #[schema(value_type = Option<String>)]
    pub bank_number: Option<Secret<String>>,

    /// The type of identification document used (e.g., CPF or CNPJ)
    #[schema(value_type = Option<DocumentKind>, example = "Cpf", default = "Cnpj")]
    pub document_type: Option<common_enums::DocumentKind>,

    /// The fine percentage charged if payment is overdue
    #[schema(value_type = Option<String>)]
    pub fine_percentage: Option<String>,

    /// The number of days after the due date when the fine is applied
    #[schema(value_type = Option<String>)]
    pub fine_quantity_days: Option<String>,

    /// The interest percentage charged on late payments
    #[schema(value_type = Option<String>)]
    pub interest_percentage: Option<String>,

    /// The number of days after which the boleto is written off (canceled)
    #[schema(value_type = Option<String>)]
    pub write_off_quantity_days: Option<String>,

    /// Custom messages or instructions to display on the boleto
    #[schema(value_type = Option<Vec<String>>)]
    pub messages: Option<Vec<String>>,

    // #[serde(with = "common_utils::custom_serde::date_yyyy_mm_dd::option")]
    #[schema(value_type = Option<String>, format = "date", example = "2025-08-22")]
    // The date upon which the boleto is due and is of format: "YYYY-MM-DD"
    pub due_date: Option<String>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum VoucherData {
    Boleto(Box<BoletoVoucherData>),
    Efecty,
    PagoEfectivo,
    RedCompra,
    RedPagos,
    Alfamart(Box<AlfamartVoucherData>),
    Indomaret(Box<IndomaretVoucherData>),
    Oxxo,
    SevenEleven(Box<JCSVoucherData>),
    Lawson(Box<JCSVoucherData>),
    MiniStop(Box<JCSVoucherData>),
    FamilyMart(Box<JCSVoucherData>),
    Seicomart(Box<JCSVoucherData>),
    PayEasy(Box<JCSVoucherData>),
}

impl GetAddressFromPaymentMethodData for VoucherData {
    fn get_billing_address(&self) -> Option<Address> {
        match self {
            Self::Alfamart(voucher_data) => Some(Address {
                address: Some(AddressDetails {
                    first_name: voucher_data.first_name.clone(),
                    last_name: voucher_data.last_name.clone(),
                    ..AddressDetails::default()
                }),
                phone: None,
                email: voucher_data.email.clone(),
            }),
            Self::Indomaret(voucher_data) => Some(Address {
                address: Some(AddressDetails {
                    first_name: voucher_data.first_name.clone(),
                    last_name: voucher_data.last_name.clone(),
                    ..AddressDetails::default()
                }),
                phone: None,
                email: voucher_data.email.clone(),
            }),
            Self::Lawson(voucher_data)
            | Self::MiniStop(voucher_data)
            | Self::FamilyMart(voucher_data)
            | Self::Seicomart(voucher_data)
            | Self::PayEasy(voucher_data)
            | Self::SevenEleven(voucher_data) => Some(Address {
                address: Some(AddressDetails {
                    first_name: voucher_data.first_name.clone(),
                    last_name: voucher_data.last_name.clone(),
                    ..AddressDetails::default()
                }),
                phone: Some(PhoneDetails {
                    number: voucher_data.phone_number.clone().map(Secret::new),
                    country_code: None,
                }),
                email: voucher_data.email.clone(),
            }),
            Self::Boleto(_)
            | Self::Efecty
            | Self::PagoEfectivo
            | Self::RedCompra
            | Self::RedPagos
            | Self::Oxxo => None,
        }
    }
}

/// Use custom serializer to provide backwards compatible response for `reward` payment_method_data
pub fn serialize_payment_method_data_response<S>(
    payment_method_data_response: &Option<PaymentMethodDataResponseWithBilling>,
    serializer: S,
) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    if let Some(payment_method_data_response) = payment_method_data_response {
        if let Some(payment_method_data) = payment_method_data_response.payment_method_data.as_ref()
        {
            match payment_method_data {
                PaymentMethodDataResponse::Reward {} => serializer.serialize_str("reward"),
                PaymentMethodDataResponse::BankDebit(_)
                | PaymentMethodDataResponse::BankRedirect(_)
                | PaymentMethodDataResponse::Card(_)
                | PaymentMethodDataResponse::CardRedirect(_)
                | PaymentMethodDataResponse::CardToken(_)
                | PaymentMethodDataResponse::Crypto(_)
                | PaymentMethodDataResponse::MandatePayment {}
                | PaymentMethodDataResponse::GiftCard(_)
                | PaymentMethodDataResponse::PayLater(_)
                | PaymentMethodDataResponse::RealTimePayment(_)
                | PaymentMethodDataResponse::MobilePayment(_)
                | PaymentMethodDataResponse::Upi(_)
                | PaymentMethodDataResponse::Wallet(_)
                | PaymentMethodDataResponse::BankTransfer(_)
                | PaymentMethodDataResponse::OpenBanking(_)
                | PaymentMethodDataResponse::Voucher(_) => {
                    payment_method_data_response.serialize(serializer)
                }
            }
        } else {
            // Can serialize directly because there is no `payment_method_data`
            payment_method_data_response.serialize(serializer)
        }
    } else {
        serializer.serialize_none()
    }
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PaymentMethodDataResponse {
    Card(Box<CardResponse>),
    BankTransfer(Box<BankTransferResponse>),
    Wallet(Box<WalletResponse>),
    PayLater(Box<PaylaterResponse>),
    BankRedirect(Box<BankRedirectResponse>),
    Crypto(Box<CryptoResponse>),
    BankDebit(Box<BankDebitResponse>),
    MandatePayment {},
    Reward {},
    RealTimePayment(Box<RealTimePaymentDataResponse>),
    Upi(Box<UpiResponse>),
    Voucher(Box<VoucherResponse>),
    GiftCard(Box<GiftCardResponse>),
    CardRedirect(Box<CardRedirectResponse>),
    CardToken(Box<CardTokenResponse>),
    OpenBanking(Box<OpenBankingResponse>),
    MobilePayment(Box<MobilePaymentResponse>),
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct BankDebitResponse {
    #[serde(flatten)]
    #[schema(value_type = Option<BankDebitAdditionalData>)]
    details: Option<additional_info::BankDebitAdditionalData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case", tag = "type")]
pub struct BankRedirectResponse {
    /// Name of the bank
    #[schema(value_type = Option<BankNames>)]
    pub bank_name: Option<common_enums::BankNames>,
    #[serde(flatten)]
    #[schema(value_type = Option<BankRedirectDetails>)]
    pub details: Option<additional_info::BankRedirectDetails>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct BankTransferResponse {
    #[serde(flatten)]
    #[schema(value_type = Option<BankTransferAdditionalData>)]
    details: Option<additional_info::BankTransferAdditionalData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct CardRedirectResponse {
    #[serde(flatten)]
    details: Option<CardRedirectData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct CardTokenResponse {
    #[serde(flatten)]
    #[schema(value_type = Option<CardTokenAdditionalData>)]
    details: Option<additional_info::CardTokenAdditionalData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct CryptoResponse {
    #[serde(flatten)]
    details: Option<CryptoData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct GiftCardResponse {
    #[serde(flatten)]
    #[schema(value_type = Option<GiftCardAdditionalData>)]
    details: Option<additional_info::GiftCardAdditionalData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct OpenBankingResponse {
    #[serde(flatten)]
    details: Option<OpenBankingData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct MobilePaymentResponse {
    #[serde(flatten)]
    details: Option<MobilePaymentData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct RealTimePaymentDataResponse {
    #[serde(flatten)]
    details: Option<RealTimePaymentData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct UpiResponse {
    #[serde(flatten)]
    #[schema(value_type = Option<UpiAdditionalData>)]
    details: Option<additional_info::UpiAdditionalData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct VoucherResponse {
    #[serde(flatten)]
    details: Option<VoucherData>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct PaylaterResponse {
    klarna_sdk: Option<KlarnaSdkPaymentMethodResponse>,
}

#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct WalletResponse {
    #[serde(flatten)]
    details: Option<WalletResponseData>,
}

/// Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets.
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum WalletResponseData {
    #[schema(value_type = WalletAdditionalDataForCard)]
    ApplePay(Box<additional_info::WalletAdditionalDataForCard>),
    #[schema(value_type = WalletAdditionalDataForCard)]
    GooglePay(Box<additional_info::WalletAdditionalDataForCard>),
    #[schema(value_type = WalletAdditionalDataForCard)]
    SamsungPay(Box<additional_info::WalletAdditionalDataForCard>),
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct KlarnaSdkPaymentMethodResponse {
    pub payment_type: Option<String>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, ToSchema, serde::Serialize)]
pub struct PaymentMethodDataResponseWithBilling {
    // The struct is flattened in order to provide backwards compatibility
    #[serde(flatten)]
    pub payment_method_data: Option<PaymentMethodDataResponse>,
    pub billing: Option<Address>,
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, ToSchema, serde::Serialize)]
pub struct CustomRecoveryPaymentMethodData {
    /// Primary payment method token at payment processor end.
    #[schema(value_type = String, example = "token_1234")]
    pub primary_processor_payment_method_token: Secret<String>,

    /// AdditionalCardInfo for the primary token.
    pub additional_payment_method_info: AdditionalCardInfo,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v1")]
pub enum PaymentIdType {
    /// The identifier for payment intent
    PaymentIntentId(id_type::PaymentId),
    /// The identifier for connector transaction
    ConnectorTransactionId(String),
    /// The identifier for payment attempt
    PaymentAttemptId(String),
    /// The identifier for preprocessing step
    PreprocessingId(String),
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)]
#[cfg(feature = "v2")]
pub enum PaymentIdType {
    /// The identifier for payment intent
    PaymentIntentId(id_type::GlobalPaymentId),
    /// The identifier for connector transaction
    ConnectorTransactionId(String),
    /// The identifier for payment attempt
    PaymentAttemptId(String),
    /// The identifier for preprocessing step
    PreprocessingId(String),
}

#[cfg(feature = "v1")]
impl fmt::Display for PaymentIdType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::PaymentIntentId(payment_id) => {
                write!(
                    f,
                    "payment_intent_id = \"{}\"",
                    payment_id.get_string_repr()
                )
            }
            Self::ConnectorTransactionId(connector_transaction_id) => write!(
                f,
                "connector_transaction_id = \"{connector_transaction_id}\""
            ),
            Self::PaymentAttemptId(payment_attempt_id) => {
                write!(f, "payment_attempt_id = \"{payment_attempt_id}\"")
            }
            Self::PreprocessingId(preprocessing_id) => {
                write!(f, "preprocessing_id = \"{preprocessing_id}\"")
            }
        }
    }
}

#[cfg(feature = "v1")]
impl Default for PaymentIdType {
    fn default() -> Self {
        Self::PaymentIntentId(Default::default())
    }
}

#[derive(Default, Clone, Debug, Eq, PartialEq, ToSchema, serde::Deserialize, serde::Serialize)]
// #[serde(deny_unknown_fields)]
pub struct Address {
    /// Provide the address details
    pub address: Option<AddressDetails>,

    pub phone: Option<PhoneDetails>,

    #[schema(value_type = Option<String>)]
    pub email: Option<Email>,
}

impl masking::SerializableSecret for Address {}

impl Address {
    /// Unify the address, giving priority to `self` when details are present in both
    pub fn unify_address(self, other: Option<&Self>) -> Self {
        let other_address_details = other.and_then(|address| address.address.as_ref());
        Self {
            address: self
                .address
                .map(|address| address.unify_address_details(other_address_details))
                .or(other_address_details.cloned()),
            email: self.email.or(other.and_then(|other| other.email.clone())),
            phone: self.phone.or(other.and_then(|other| other.phone.clone())),
        }
    }
}

// used by customers also, could be moved outside
/// Address details
#[derive(Clone, Default, Debug, Eq, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
// #[serde(deny_unknown_fields)]
pub struct AddressDetails {
    /// The city, district, suburb, town, or village of the address.
    #[schema(max_length = 50, example = "New York")]
    pub city: Option<String>,

    /// The two-letter ISO 3166-1 alpha-2 country code (e.g., US, GB).
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub country: Option<api_enums::CountryAlpha2>,

    /// The first line of the street address or P.O. Box.
    #[schema(value_type = Option<String>, max_length = 200, example = "123, King Street")]
    pub line1: Option<Secret<String>>,

    /// The second line of the street address or P.O. Box (e.g., apartment, suite, unit, or building).
    #[schema(value_type = Option<String>, max_length = 50, example = "Powelson Avenue")]
    pub line2: Option<Secret<String>>,

    /// The third line of the street address, if applicable.
    #[schema(value_type = Option<String>, max_length = 50, example = "Bridgewater")]
    pub line3: Option<Secret<String>>,

    /// The zip/postal code for the address
    #[schema(value_type = Option<String>, max_length = 50, example = "08807")]
    pub zip: Option<Secret<String>>,

    /// The address state
    #[schema(value_type = Option<String>, example = "New York")]
    pub state: Option<Secret<String>>,

    /// The first name for the address
    #[schema(value_type = Option<String>, max_length = 255, example = "John")]
    pub first_name: Option<Secret<String>>,

    /// The last name for the address
    #[schema(value_type = Option<String>, max_length = 255, example = "Doe")]
    pub last_name: Option<Secret<String>>,

    /// The zip/postal code of the origin
    #[schema(value_type = Option<String>, max_length = 50, example = "08807")]
    pub origin_zip: Option<Secret<String>>,
}

impl AddressDetails {
    pub fn get_optional_full_name(&self) -> Option<Secret<String>> {
        match (self.first_name.as_ref(), self.last_name.as_ref()) {
            (Some(first_name), Some(last_name)) => Some(Secret::new(format!(
                "{} {}",
                first_name.peek(),
                last_name.peek()
            ))),
            (Some(name), None) | (None, Some(name)) => Some(name.to_owned()),
            _ => None,
        }
    }

    pub fn unify_address_details(self, other: Option<&Self>) -> Self {
        if let Some(other) = other {
            let (first_name, last_name) = if self
                .first_name
                .as_ref()
                .is_some_and(|first_name| !first_name.is_empty_after_trim())
            {
                (self.first_name, self.last_name)
            } else {
                (other.first_name.clone(), other.last_name.clone())
            };

            Self {
                first_name,
                last_name,
                city: self.city.or(other.city.clone()),
                country: self.country.or(other.country),
                line1: self.line1.or(other.line1.clone()),
                line2: self.line2.or(other.line2.clone()),
                line3: self.line3.or(other.line3.clone()),
                zip: self.zip.or(other.zip.clone()),
                state: self.state.or(other.state.clone()),
                origin_zip: self.origin_zip.or(other.origin_zip.clone()),
            }
        } else {
            self
        }
    }
}

pub struct AddressDetailsWithPhone {
    pub address: Option<AddressDetails>,
    pub phone_number: Option<Secret<String>>,
    pub email: Option<Email>,
}

pub struct EncryptableAddressDetails {
    pub line1: crypto::OptionalEncryptableSecretString,
    pub line2: crypto::OptionalEncryptableSecretString,
    pub line3: crypto::OptionalEncryptableSecretString,
    pub state: crypto::OptionalEncryptableSecretString,
    pub zip: crypto::OptionalEncryptableSecretString,
    pub first_name: crypto::OptionalEncryptableSecretString,
    pub last_name: crypto::OptionalEncryptableSecretString,
    pub phone_number: crypto::OptionalEncryptableSecretString,
    pub email: crypto::OptionalEncryptableEmail,
}

#[derive(Debug, Clone, Default, Eq, PartialEq, ToSchema, serde::Deserialize, serde::Serialize)]
pub struct PhoneDetails {
    /// The contact number
    #[schema(value_type = Option<String>, example = "9123456789")]
    pub number: Option<Secret<String>>,
    /// The country code attached to the number
    #[schema(example = "+1")]
    pub country_code: Option<String>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaymentsCaptureRequest {
    /// The unique identifier for the payment being captured. This is taken from the path parameter.
    #[serde(skip_deserializing)]
    pub payment_id: id_type::PaymentId,
    /// The unique identifier for the merchant. This is usually inferred from the API key.
    #[schema(value_type = Option<String>)]
    pub merchant_id: Option<id_type::MerchantId>,
    /// The amount to capture, in the lowest denomination of the currency. If omitted, the entire `amount_capturable` of the payment will be captured. Must be less than or equal to the current `amount_capturable`.
    #[schema(value_type = Option<i64>, example = 6540)]
    pub amount_to_capture: Option<MinorUnit>,
    /// Decider to refund the uncaptured amount. (Currently not fully supported or behavior may vary by connector).
    pub refund_uncaptured_amount: Option<bool>,
    /// A dynamic suffix that appears on your customer's credit card statement. This is concatenated with the (shortened) descriptor prefix set on your account to form the complete statement descriptor. The combined length should not exceed connector-specific limits (typically 22 characters).
    pub statement_descriptor_suffix: Option<String>,
    /// An optional prefix for the statement descriptor that appears on your customer's credit card statement. This can override the default prefix set on your merchant account. The combined length of prefix and suffix should not exceed connector-specific limits (typically 22 characters).
    pub statement_descriptor_prefix: Option<String>,
    /// Merchant connector details used to make payments. (Deprecated)
    #[schema(value_type = Option<MerchantConnectorDetailsWrap>, deprecated)]
    pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaymentsCaptureRequest {
    /// The Amount to be captured/ debited from the user's payment method. If not passed the full amount will be captured.
    #[schema(value_type = Option<i64>, example = 6540)]
    pub amount_to_capture: Option<MinorUnit>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
pub struct PaymentsCaptureResponse {
    /// The unique identifier for the payment
    pub id: id_type::GlobalPaymentId,

    /// Status of the payment
    #[schema(value_type = IntentStatus, example = "succeeded")]
    pub status: common_enums::IntentStatus,

    /// Amount details related to the payment
    pub amount: PaymentAmountDetailsResponse,
}

#[cfg(feature = "v2")]
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsCancelRequest {
    /// The reason for the payment cancel
    pub cancellation_reason: Option<String>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Serialize, ToSchema)]
pub struct PaymentsCancelResponse {
    /// The unique identifier for the payment
    pub id: id_type::GlobalPaymentId,

    /// Status of the payment
    #[schema(value_type = IntentStatus, example = "cancelled")]
    pub status: common_enums::IntentStatus,

    /// Cancellation reason for the payment cancellation
    #[schema(example = "Requested by merchant")]
    pub cancellation_reason: Option<String>,

    /// Amount details related to the payment
    pub amount: PaymentAmountDetailsResponse,

    /// The unique identifier for the customer associated with the payment
    pub customer_id: Option<id_type::GlobalCustomerId>,

    /// The connector used for the payment
    #[schema(example = "stripe")]
    pub connector: Option<api_enums::Connector>,

    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created: PrimitiveDateTime,

    /// The payment method type for this payment attempt
    #[schema(value_type = Option<PaymentMethod>, example = "wallet")]
    pub payment_method_type: Option<api_enums::PaymentMethod>,

    #[schema(value_type = Option<PaymentMethodType>, example = "apple_pay")]
    pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

    /// List of payment attempts associated with payment intent
    pub attempts: Option<Vec<PaymentAttemptResponse>>,

    /// The url to which user must be redirected to after completion of the purchase
    #[schema(value_type = Option<String>)]
    pub return_url: Option<common_utils::types::Url>,
}

#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct UrlDetails {
    pub url: String,
    pub method: String,
}
#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct AuthenticationForStartResponse {
    pub authentication: UrlDetails,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum NextActionType {
    RedirectToUrl,
    DisplayQrCode,
    InvokeSdkClient,
    TriggerApi,
    DisplayBankTransferInformation,
    DisplayWaitScreen,
    CollectOtp,
    RedirectInsidePopup,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NextActionData {
    /// Contains the url for redirection flow
    #[cfg(feature = "v1")]
    RedirectToUrl {
        redirect_to_url: String,
    },
    #[cfg(feature = "v1")]
    RedirectInsidePopup {
        popup_url: String,
        redirect_response_url: String,
    },
    /// Contains the url for redirection flow
    #[cfg(feature = "v2")]
    RedirectToUrl {
        #[schema(value_type = String)]
        redirect_to_url: Url,
    },
    /// Informs the next steps for bank transfer and also contains the charges details (ex: amount received, amount charged etc)
    DisplayBankTransferInformation {
        bank_transfer_steps_and_charges_details: BankTransferNextStepsData,
    },
    /// Contains third party sdk session token response
    ThirdPartySdkSessionToken {
        session_token: Option<SessionToken>,
    },
    /// Contains url for Qr code image, this qr code has to be shown in sdk
    QrCodeInformation {
        #[schema(value_type = String)]
        /// Hyperswitch generated image data source url
        image_data_url: Option<Url>,
        display_to_timestamp: Option<i64>,
        #[schema(value_type = String)]
        /// The url for Qr code given by the connector
        qr_code_url: Option<Url>,
        display_text: Option<String>,
        border_color: Option<String>,
    },
    /// Contains url to fetch Qr code data
    FetchQrCodeInformation {
        #[schema(value_type = String)]
        qr_code_fetch_url: Url,
    },
    /// Contains the download url and the reference number for transaction
    DisplayVoucherInformation {
        #[schema(value_type = String)]
        voucher_details: VoucherNextStepData,
    },
    /// Contains duration for displaying a wait screen, wait screen with timer is displayed by sdk
    WaitScreenInformation {
        display_from_timestamp: i128,
        display_to_timestamp: Option<i128>,
        poll_config: Option<PollConfig>,
    },
    /// Contains the information regarding three_ds_method_data submission, three_ds authentication, and authorization flows
    ThreeDsInvoke {
        three_ds_data: ThreeDsData,
    },
    InvokeSdkClient {
        next_action_data: SdkNextActionData,
    },
    /// Contains consent to collect otp for mobile payment
    CollectOtp {
        consent_data_required: MobilePaymentConsent,
    },
    /// Contains data required to invoke hidden iframe
    InvokeHiddenIframe {
        iframe_data: IframeData,
    },
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(tag = "method_key")]
pub enum IframeData {
    #[serde(rename = "threeDSMethodData")]
    ThreedsInvokeAndCompleteAutorize {
        /// ThreeDS method url
        three_ds_method_url: String,
        /// Whether ThreeDS method data submission is required
        three_ds_method_data_submission: bool,
        /// ThreeDS method data
        three_ds_method_data: Option<String>,
        /// ThreeDS Server ID
        directory_server_id: String,
        /// ThreeDS Protocol version
        message_version: Option<String>,
    },
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
pub struct ThreeDsData {
    /// ThreeDS authentication url - to initiate authentication
    pub three_ds_authentication_url: String,
    /// ThreeDS authorize url - to complete the payment authorization after authentication
    pub three_ds_authorize_url: String,
    /// ThreeDS method details
    pub three_ds_method_details: ThreeDsMethodData,
    /// Poll config for a connector
    pub poll_config: PollConfigResponse,
    /// Message Version
    pub message_version: Option<String>,
    /// Directory Server ID
    pub directory_server_id: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
#[serde(untagged)]
pub enum ThreeDsMethodData {
    AcsThreeDsMethodData {
        /// Whether ThreeDS method data submission is required
        three_ds_method_data_submission: bool,
        /// ThreeDS method data
        three_ds_method_data: Option<String>,
        /// ThreeDS method url
        three_ds_method_url: Option<String>,
        /// Three DS Method Key
        three_ds_method_key: Option<ThreeDsMethodKey>,
        /// Indicates whethere to wait for Post message after 3DS method data submission
        consume_post_message_for_three_ds_method_completion: bool,
    },
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
pub enum ThreeDsMethodKey {
    #[serde(rename = "threeDSMethodData")]
    ThreeDsMethodData,
    #[serde(rename = "JWT")]
    JWT,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)]
pub struct PollConfigResponse {
    /// Poll Id
    pub poll_id: String,
    /// Interval of the poll
    pub delay_in_secs: i8,
    /// Frequency of the poll
    pub frequency: i8,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(untagged)]
// the enum order shouldn't be changed as this is being used during serialization and deserialization
pub enum QrCodeInformation {
    QrCodeUrl {


            );
            Err(report!(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to retrieve payment method from external vault"))
        }
    }
}

// ********************************************** PROCESS TRACKER **********************************************

pub async fn add_delete_tokenized_data_task(
    db: &dyn db::StorageInterface,
    lookup_key: &str,
    pm: enums::PaymentMethod,
) -> RouterResult<()> {
    let runner = storage::ProcessTrackerRunner::DeleteTokenizeDataWorkflow;
    let process_tracker_id = format!("{runner}_{lookup_key}");
    let task = runner.to_string();
    let tag = ["BASILISK-V3"];
    let tracking_data = storage::TokenizeCoreWorkflow {
        lookup_key: lookup_key.to_owned(),
        pm,
    };
    let schedule_time = get_delete_tokenize_schedule_time(db, pm, 0)
        .await
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain initial process tracker schedule time")?;

    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 delete tokenized data process tracker task")?;

    let response = db.insert_process(process_tracker_entry).await;
    response.map(|_| ()).or_else(|err| {
        if err.current_context().is_db_unique_violation() {
            Ok(())
        } else {
            Err(report!(errors::ApiErrorResponse::InternalServerError))
        }
    })
}

pub async fn start_tokenize_data_workflow(
    state: &routes::SessionState,
    tokenize_tracker: &storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
    let db = &*state.store;
    let delete_tokenize_data = serde_json::from_value::<storage::TokenizeCoreWorkflow>(
        tokenize_tracker.tracking_data.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable_lazy(|| {
        format!(
            "unable to convert into DeleteTokenizeByTokenRequest {:?}",
            tokenize_tracker.tracking_data
        )
    })?;

    match delete_tokenized_data(state, &delete_tokenize_data.lookup_key).await {
        Ok(()) => {
            logger::info!("Card From locker deleted Successfully");
            //mark task as finished
            db.as_scheduler()
                .finish_process_with_business_status(
                    tokenize_tracker.clone(),
                    diesel_models::process_tracker::business_status::COMPLETED_BY_PT,
                )
                .await?;
        }
        Err(err) => {
            logger::error!("Err: Deleting Card From Locker : {:?}", err);
            retry_delete_tokenize(db, delete_tokenize_data.pm, tokenize_tracker.to_owned()).await?;
            metrics::RETRIED_DELETE_DATA_COUNT.add(1, &[]);
        }
    }
    Ok(())
}

pub async fn get_delete_tokenize_schedule_time(
    db: &dyn db::StorageInterface,
    pm: enums::PaymentMethod,
    retry_count: i32,
) -> Option<time::PrimitiveDateTime> {
    let redis_mapping = db::get_and_deserialize_key(
        db,
        &format!("pt_mapping_delete_{pm}_tokenize_data"),
        "PaymentMethodsPTMapping",
    )
    .await;
    let mapping = match redis_mapping {
        Ok(x) => x,
        Err(error) => {
            logger::info!(?error, "Redis Mapping Error");
            process_data::PaymentMethodsPTMapping::default()
        }
    };
    let time_delta = process_tracker_utils::get_pm_schedule_time(mapping, pm, retry_count + 1);

    process_tracker_utils::get_time_from_delta(time_delta)
}

pub async fn retry_delete_tokenize(
    db: &dyn db::StorageInterface,
    pm: enums::PaymentMethod,
    pt: storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
    let schedule_time = get_delete_tokenize_schedule_time(db, pm, pt.retry_count).await;

    match schedule_time {
        Some(s_time) => {
            let retry_schedule = db
                .as_scheduler()
                .retry_process(pt, s_time)
                .await
                .map_err(Into::into);
            metrics::TASKS_RESET_COUNT.add(
                1,
                router_env::metric_attributes!(("flow", "DeleteTokenizeData")),
            );
            retry_schedule
        }
        None => db
            .as_scheduler()
            .finish_process_with_business_status(
                pt,
                diesel_models::process_tracker::business_status::RETRIES_EXCEEDED,
            )
            .await
            .map_err(Into::into),
    }
}

// Fallback logic of old temp locker needs to be removed later


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

impl ConnectorAccessToken for for Plaid


// File: crates/drainer/src/main.rs
// Module: drainer

use std::collections::HashMap;

use drainer::{errors::DrainerResult, logger, services, settings, start_drainer, start_web_server};
use router_env::tracing::Instrument;

#[tokio::main]
async fn main() -> DrainerResult<()> {
    // Get configuration
    let cmd_line = <settings::CmdLineConf as clap::Parser>::parse();

    #[allow(clippy::expect_used)]
    let conf = settings::Settings::with_config_path(cmd_line.config_path)
        .expect("Unable to construct application configuration");
    #[allow(clippy::expect_used)]
    conf.validate()
        .expect("Failed to validate drainer configuration");

    let state = settings::AppState::new(conf.clone()).await;

    let mut stores = HashMap::new();
    for (tenant_name, tenant) in conf.multitenancy.get_tenants() {
        let store = std::sync::Arc::new(services::Store::new(&state.conf, false, tenant).await);
        stores.insert(tenant_name.clone(), store);
    }

    #[allow(clippy::print_stdout)] // The logger has not yet been initialized
    #[cfg(feature = "vergen")]
    {
        println!("Starting drainer (Version: {})", router_env::git_tag!());
    }

    let _guard = router_env::setup(
        &conf.log,
        router_env::service_name!(),
        [router_env::service_name!()],
    );

    #[allow(clippy::expect_used)]
    let web_server = Box::pin(start_web_server(
        state.conf.as_ref().clone(),
        stores.clone(),
    ))
    .await
    .expect("Failed to create the server");

    tokio::spawn(
        async move {
            let _ = web_server.await;
            logger::error!("The health check probe stopped working!");
        }
        .in_current_span(),
    );

    logger::debug!(startup_config=?conf);
    logger::info!("Drainer started [{:?}] [{:?}]", conf.drainer, conf.log);

    start_drainer(stores.clone(), conf.drainer).await?;

    Ok(())
}


// Struct: BillingAddress
// File: crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct BillingAddress


// Struct: RiskifiedAuthType
// File: crates/hyperswitch_connectors/src/connectors/riskified/transformers/auth.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RiskifiedAuthType


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

impl api::RefundSync for for Coinbase


// Function: get_connector_by_name
// File: crates/router/src/types/api/connector_mapping.rs
// Module: router

pub fn get_connector_by_name(
        _connectors: &Connectors,
        name: &str,
        connector_type: GetToken,
        connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    ) -> CustomResult<Self, errors::ApiErrorResponse>


// Struct: PhonepeErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/phonepe/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PhonepeErrorResponse


// Struct: AmountDetails
// File: crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AmountDetails


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

impl PaymentVoid for for Signifyd


// Struct: Dwolla
// File: crates/hyperswitch_connectors/src/connectors/dwolla.rs
// Module: hyperswitch_connectors
// Implementations: 18
// Traits: api::ConnectorCustomer, api::Payment, api::PaymentSession, api::ConnectorAccessToken, api::MandateSetup, api::PaymentAuthorize, api::PaymentSync, api::PaymentCapture, api::PaymentVoid, api::Refund, api::RefundExecute, api::RefundSync, api::PaymentToken, ConnectorCommon, ConnectorValidation, webhooks::IncomingWebhook, ConnectorSpecifications

pub struct Dwolla

api::ConnectorCustomer api::Payment api::PaymentSession api::ConnectorAccessToken api::MandateSetup api::PaymentAuthorize api::PaymentSync api::PaymentCapture api::PaymentVoid api::Refund api::RefundExecute api::RefundSync api::PaymentToken ConnectorCommon ConnectorValidation webhooks::IncomingWebhook ConnectorSpecifications


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

pub fn is_payout_initiated(status: api_enums::PayoutStatus) -> bool


// Implementation: impl Default for for ErrorResponse
// File: crates/hyperswitch_domain_models/src/router_data.rs
// Module: hyperswitch_domain_models
// Methods: 1 total (0 public)

impl Default for for ErrorResponse