// File: crates/payment_methods/src/configs/settings.rs

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

use api_models::{enums, payment_methods::RequiredFieldInfo};
use common_utils::errors::CustomResult;
use hyperswitch_interfaces::secrets_interface::{
    secret_handler::SecretsHandler,
    secret_state::{RawSecret, SecretStateContainer, SecuredSecret},
    SecretManagementInterface, SecretsManagementError,
};
use masking::Secret;
use serde::{self, Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "v2", derive(Default))] // Configs are read from the config file in config/payment_required_fields.toml
pub struct RequiredFields(pub HashMap<enums::PaymentMethod, PaymentMethodType>);

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PaymentMethodType(pub HashMap<enums::PaymentMethodType, ConnectorFields>);

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConnectorFields {
    pub fields: HashMap<enums::Connector, RequiredFieldFinal>,
}

#[derive(Debug, Deserialize, Clone, Default)]
pub struct BankRedirectConfig(pub HashMap<enums::PaymentMethodType, ConnectorBankNames>);
#[derive(Debug, Deserialize, Clone)]
pub struct ConnectorBankNames(pub HashMap<String, BanksVector>);

#[derive(Debug, Deserialize, Clone)]
pub struct BanksVector {
    #[serde(deserialize_with = "deserialize_hashset")]
    pub banks: HashSet<common_enums::enums::BankNames>,
}

#[cfg(feature = "v1")]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RequiredFieldFinal {
    pub mandate: HashMap<String, RequiredFieldInfo>,
    pub non_mandate: HashMap<String, RequiredFieldInfo>,
    pub common: HashMap<String, RequiredFieldInfo>,
}
#[cfg(feature = "v2")]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RequiredFieldFinal {
    pub mandate: Option<Vec<RequiredFieldInfo>>,
    pub non_mandate: Option<Vec<RequiredFieldInfo>>,
    pub common: Option<Vec<RequiredFieldInfo>>,
}

#[derive(Debug, Deserialize, Clone, Default)]
pub struct PaymentMethodAuth {
    pub redis_expiry: i64,
    pub pm_auth_key: Secret<String>,
}

#[async_trait::async_trait]
impl SecretsHandler for PaymentMethodAuth {
    async fn convert_to_raw_secret(
        value: SecretStateContainer<Self, SecuredSecret>,
        secret_management_client: &dyn SecretManagementInterface,
    ) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
        let payment_method_auth = value.get_inner();

        let pm_auth_key = secret_management_client
            .get_secret(payment_method_auth.pm_auth_key.clone())
            .await?;

        Ok(value.transition_state(|payment_method_auth| Self {
            pm_auth_key,
            ..payment_method_auth
        }))
    }
}

#[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)]
pub struct EligiblePaymentMethods {
    #[serde(deserialize_with = "deserialize_hashset")]
    pub sdk_eligible_payment_methods: HashSet<String>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct SupportedPaymentMethodsForMandate(
    pub HashMap<enums::PaymentMethod, SupportedPaymentMethodTypesForMandate>,
);

#[derive(Debug, Deserialize, Clone)]
pub struct SupportedPaymentMethodTypesForMandate(
    pub HashMap<enums::PaymentMethodType, SupportedConnectorsForMandate>,
);

#[derive(Debug, Deserialize, Clone)]
pub struct SupportedConnectorsForMandate {
    #[serde(deserialize_with = "deserialize_hashset")]
    pub connector_list: HashSet<enums::Connector>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct Mandates {
    pub supported_payment_methods: SupportedPaymentMethodsForMandate,
    pub update_mandate_supported: SupportedPaymentMethodsForMandate,
}

#[derive(Debug, Deserialize, Clone)]
pub struct ZeroMandates {
    pub supported_payment_methods: SupportedPaymentMethodsForMandate,
}

fn deserialize_hashset_inner<T>(value: impl AsRef<str>) -> Result<HashSet<T>, String>
where
    T: Eq + std::str::FromStr + std::hash::Hash,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    let (values, errors) = value
        .as_ref()
        .trim()
        .split(',')
        .map(|s| {
            T::from_str(s.trim()).map_err(|error| {
                format!(
                    "Unable to deserialize `{}` as `{}`: {error}",
                    s.trim(),
                    std::any::type_name::<T>()
                )
            })
        })
        .fold(
            (HashSet::new(), Vec::new()),
            |(mut values, mut errors), result| match result {
                Ok(t) => {
                    values.insert(t);
                    (values, errors)
                }
                Err(error) => {
                    errors.push(error);
                    (values, errors)
                }
            },
        );
    if !errors.is_empty() {
        Err(format!("Some errors occurred:\n{}", errors.join("\n")))
    } else {
        Ok(values)
    }
}

fn deserialize_hashset<'a, D, T>(deserializer: D) -> Result<HashSet<T>, D::Error>
where
    D: serde::Deserializer<'a>,
    T: Eq + std::str::FromStr + std::hash::Hash,
    <T as std::str::FromStr>::Err: std::fmt::Display,
{
    use serde::de::Error;

    deserialize_hashset_inner(<String>::deserialize(deserializer)?).map_err(D::Error::custom)
}


// File: crates/pm_auth/Cargo.toml

[package]
name = "pm_auth"
description = "Open banking services"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
readme = "README.md"
license.workspace = true

[features]
v1 = ["api_models/v1", "common_utils/v1"]

[dependencies]
# First party crates
api_models = { version = "0.1.0", path = "../api_models" }
common_enums = { version = "0.1.0", path = "../common_enums" }
common_utils = { version = "0.1.0", path = "../common_utils" }
masking = { version = "0.1.0", path = "../masking" }

# Third party crates
async-trait = "0.1.88"
bytes = "1.10.1"
error-stack = "0.4.1"
http = "0.2.12"
mime = "0.3.17"
serde = "1.0.219"
serde_json = "1.0.140"
strum = { version = "0.26.3", features = ["derive"] }
thiserror = "1.0.69"

[lints]
workspace = true


// File: crates/pm_auth/README.md

# Payment Method Auth Services

An open banking services for payment method auth validation


// File: crates/pm_auth/src/core.rs

pub mod errors;


// File: crates/pm_auth/src/consts.rs

pub const REQUEST_TIME_OUT: u64 = 30; // will timeout after the mentioned limit
pub const REQUEST_TIMEOUT_ERROR_CODE: &str = "TIMEOUT"; // timeout error code
pub const REQUEST_TIMEOUT_ERROR_MESSAGE: &str = "Connector did not respond in specified time"; // error message for timed out request
pub const NO_ERROR_CODE: &str = "No error code";
pub const NO_ERROR_MESSAGE: &str = "No error message";


// File: crates/pm_auth/src/types.rs

pub mod api;

use std::marker::PhantomData;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


// File: crates/pm_auth/src/lib.rs

pub mod connector;
pub mod consts;
pub mod core;
pub mod types;


// File: crates/pm_auth/src/connector.rs

pub mod plaid;

pub use self::plaid::Plaid;


// File: crates/pm_auth/src/types/api.rs

pub mod auth_service;

use std::fmt::Debug;

use common_utils::{
    errors::CustomResult,
    request::{Request, RequestContent},
};
use masking::Maskable;

use crate::{
    core::errors::ConnectorError,
    types::{
        self as auth_types,
        api::auth_service::{AuthService, PaymentInitiation},
    },
};

#[async_trait::async_trait]
pub trait ConnectorIntegration<T, Req, Resp>: ConnectorIntegrationAny<T, Req, Resp> + Sync {
    fn get_headers(
        &self,
        _req: &super::PaymentAuthRouterData<T, Req, Resp>,
        _connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        Ok(vec![])
    }

    fn get_content_type(&self) -> &'static str {
        mime::APPLICATION_JSON.essence_str()
    }

    fn get_url(
        &self,
        _req: &super::PaymentAuthRouterData<T, Req, Resp>,
        _connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> CustomResult<String, ConnectorError> {
        Ok(String::new())
    }

    fn get_request_body(
        &self,
        _req: &super::PaymentAuthRouterData<T, Req, Resp>,
    ) -> CustomResult<RequestContent, ConnectorError> {
        Ok(RequestContent::Json(Box::new(serde_json::json!(r#"{}"#))))
    }

    fn build_request(
        &self,
        _req: &super::PaymentAuthRouterData<T, Req, Resp>,
        _connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        Ok(None)
    }

    fn handle_response(
        &self,
        data: &super::PaymentAuthRouterData<T, Req, Resp>,
        _res: auth_types::Response,
    ) -> CustomResult<super::PaymentAuthRouterData<T, Req, Resp>, ConnectorError>
    where
        T: Clone,
        Req: Clone,
        Resp: Clone,
    {
        Ok(data.clone())
    }

    fn get_error_response(
        &self,
        _res: auth_types::Response,
    ) -> CustomResult<auth_types::ErrorResponse, ConnectorError> {
        Ok(auth_types::ErrorResponse::get_not_implemented())
    }

    fn get_5xx_error_response(
        &self,
        res: auth_types::Response,
    ) -> CustomResult<auth_types::ErrorResponse, ConnectorError> {
        let error_message = match res.status_code {
            500 => "internal_server_error",
            501 => "not_implemented",
            502 => "bad_gateway",
            503 => "service_unavailable",
            504 => "gateway_timeout",
            505 => "http_version_not_supported",
            506 => "variant_also_negotiates",
            507 => "insufficient_storage",
            508 => "loop_detected",
            510 => "not_extended",
            511 => "network_authentication_required",
            _ => "unknown_error",
        };
        Ok(auth_types::ErrorResponse {
            code: res.status_code.to_string(),
            message: error_message.to_string(),
            reason: String::from_utf8(res.response.to_vec()).ok(),
            status_code: res.status_code,
        })
    }
}

pub trait ConnectorCommonExt<Flow, Req, Resp>:
    ConnectorCommon + ConnectorIntegration<Flow, Req, Resp>
{
    fn build_headers(
        &self,
        _req: &auth_types::PaymentAuthRouterData<Flow, Req, Resp>,
        _connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        Ok(Vec::new())
    }
}

pub type BoxedConnectorIntegration<'a, T, Req, Resp> =
    Box<&'a (dyn ConnectorIntegration<T, Req, Resp> + Send + Sync)>;

pub trait ConnectorIntegrationAny<T, Req, Resp>: Send + Sync + 'static {
    fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp>;
}

impl<S, T, Req, Resp> ConnectorIntegrationAny<T, Req, Resp> for S
where
    S: ConnectorIntegration<T, Req, Resp>,
{
    fn get_connector_integration(&self) -> BoxedConnectorIntegration<'_, T, Req, Resp> {
        Box::new(self)
    }
}

pub trait AuthServiceConnector: AuthService + Send + Debug + PaymentInitiation {}

impl<T: Send + Debug + AuthService + PaymentInitiation> AuthServiceConnector for T {}

pub type BoxedPaymentAuthConnector = Box<&'static (dyn AuthServiceConnector + Sync)>;

#[derive(Clone, Debug)]
pub struct PaymentAuthConnectorData {
    pub connector: BoxedPaymentAuthConnector,
    pub connector_name: super::PaymentMethodAuthConnectors,
}

pub trait ConnectorCommon {
    fn id(&self) -> &'static str;

    fn get_auth_header(
        &self,
        _auth_type: &auth_types::ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        Ok(Vec::new())
    }

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

    fn base_url<'a>(&self, connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str;

    fn build_error_response(
        &self,
        res: auth_types::Response,
    ) -> CustomResult<auth_types::ErrorResponse, ConnectorError> {
        Ok(auth_types::ErrorResponse {
            status_code: res.status_code,
            code: crate::consts::NO_ERROR_CODE.to_string(),
            message: crate::consts::NO_ERROR_MESSAGE.to_string(),
            reason: None,
        })
    }
}


// File: crates/pm_auth/src/types/api/auth_service.rs

use crate::types::{
    BankAccountCredentialsRequest, BankAccountCredentialsResponse, ExchangeTokenRequest,
    ExchangeTokenResponse, LinkTokenRequest, LinkTokenResponse, RecipientCreateRequest,
    RecipientCreateResponse,
};

pub trait AuthService:
    super::ConnectorCommon
    + AuthServiceLinkToken
    + AuthServiceExchangeToken
    + AuthServiceBankAccountCredentials
{
}

pub trait PaymentInitiation: super::ConnectorCommon + PaymentInitiationRecipientCreate {}

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

pub trait AuthServiceLinkToken:
    super::ConnectorIntegration<LinkToken, LinkTokenRequest, LinkTokenResponse>
{
}

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

pub trait AuthServiceExchangeToken:
    super::ConnectorIntegration<ExchangeToken, ExchangeTokenRequest, ExchangeTokenResponse>
{
}

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

pub trait AuthServiceBankAccountCredentials:
    super::ConnectorIntegration<
    BankAccountCredentials,
    BankAccountCredentialsRequest,
    BankAccountCredentialsResponse,
>
{
}

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

pub trait PaymentInitiationRecipientCreate:
    super::ConnectorIntegration<RecipientCreate, RecipientCreateRequest, RecipientCreateResponse>
{
}


// File: crates/pm_auth/src/core/errors.rs

#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ConnectorError {
    #[error("Failed to obtain authentication type")]
    FailedToObtainAuthType,
    #[error("Missing required field: {field_name}")]
    MissingRequiredField { field_name: &'static str },
    #[error("Failed to execute a processing step: {0:?}")]
    ProcessingStepFailed(Option<bytes::Bytes>),
    #[error("Failed to deserialize connector response")]
    ResponseDeserializationFailed,
    #[error("Failed to encode connector request")]
    RequestEncodingFailed,
    #[error("Invalid connector configuration: {config}")]
    InvalidConnectorConfig { config: &'static str },
}
pub type CustomResult<T, E> = error_stack::Result<T, E>;

#[derive(Debug, thiserror::Error)]
pub enum ParsingError {
    #[error("Failed to parse enum: {0}")]
    EnumParseFailure(&'static str),
    #[error("Failed to parse struct: {0}")]
    StructParseFailure(&'static str),
    #[error("Failed to serialize to {0} format")]
    EncodeError(&'static str),
    #[error("Unknown error while parsing")]
    UnknownError,
}


// File: crates/pm_auth/src/connector/plaid.rs

pub mod transformers;

use std::fmt::Debug;

use common_utils::{
    ext_traits::BytesExt,
    request::{Method, Request, RequestBuilder, RequestContent},
};
use error_stack::ResultExt;
use masking::{Mask, Maskable};
use transformers as plaid;

use crate::{
    core::errors,
    types::{
        self as auth_types,
        api::{
            auth_service::{
                self, BankAccountCredentials, ExchangeToken, LinkToken, RecipientCreate,
            },
            ConnectorCommon, ConnectorCommonExt, ConnectorIntegration,
        },
    },
};

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

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Plaid
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &auth_types::PaymentAuthRouterData<Flow, Request, Response>,
        _connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![(
            "Content-Type".to_string(),
            self.get_content_type().to_string().into(),
        )];

        let mut auth = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut auth);
        Ok(header)
    }
}

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

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }
    fn base_url<'a>(&self, _connectors: &'a auth_types::PaymentMethodAuthConnectors) -> &'a str {
        "https://sandbox.plaid.com"
    }

    fn get_auth_header(
        &self,
        auth_type: &auth_types::ConnectorAuthType,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let auth = plaid::PlaidAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let client_id = auth.client_id.into_masked();
        let secret = auth.secret.into_masked();

        Ok(vec![
            ("PLAID-CLIENT-ID".to_string(), client_id),
            ("PLAID-SECRET".to_string(), secret),
        ])
    }

    fn build_error_response(
        &self,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ErrorResponse, errors::ConnectorError> {
        let response: plaid::PlaidErrorResponse =
            res.response
                .parse_struct("PlaidErrorResponse")
                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        Ok(auth_types::ErrorResponse {
            status_code: res.status_code,
            code: crate::consts::NO_ERROR_CODE.to_string(),
            message: response.error_message,
            reason: response.display_message,
        })
    }
}

impl auth_service::AuthService for Plaid {}
impl auth_service::PaymentInitiationRecipientCreate for Plaid {}
impl auth_service::PaymentInitiation for Plaid {}
impl auth_service::AuthServiceLinkToken for Plaid {}

impl ConnectorIntegration<LinkToken, auth_types::LinkTokenRequest, auth_types::LinkTokenResponse>
    for Plaid
{
    fn get_headers(
        &self,
        req: &auth_types::LinkTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &auth_types::LinkTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "/link/token/create"
        ))
    }

    fn get_request_body(
        &self,
        req: &auth_types::LinkTokenRouterData,
    ) -> errors::CustomResult<RequestContent, errors::ConnectorError> {
        let req_obj = plaid::PlaidLinkTokenRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(req_obj)))
    }

    fn build_request(
        &self,
        req: &auth_types::LinkTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&auth_types::PaymentAuthLinkTokenType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(auth_types::PaymentAuthLinkTokenType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(auth_types::PaymentAuthLinkTokenType::get_request_body(
                    self, req,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &auth_types::LinkTokenRouterData,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::LinkTokenRouterData, errors::ConnectorError> {
        let response: plaid::PlaidLinkTokenResponse = res
            .response
            .parse_struct("PlaidLinkTokenResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        <auth_types::LinkTokenRouterData>::try_from(auth_types::ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res)
    }
}

impl auth_service::AuthServiceExchangeToken for Plaid {}

impl
    ConnectorIntegration<
        ExchangeToken,
        auth_types::ExchangeTokenRequest,
        auth_types::ExchangeTokenResponse,
    > for Plaid
{
    fn get_headers(
        &self,
        req: &auth_types::ExchangeTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &auth_types::ExchangeTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "/item/public_token/exchange"
        ))
    }

    fn get_request_body(
        &self,
        req: &auth_types::ExchangeTokenRouterData,
    ) -> errors::CustomResult<RequestContent, errors::ConnectorError> {
        let req_obj = plaid::PlaidExchangeTokenRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(req_obj)))
    }

    fn build_request(
        &self,
        req: &auth_types::ExchangeTokenRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&auth_types::PaymentAuthExchangeTokenType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(auth_types::PaymentAuthExchangeTokenType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(auth_types::PaymentAuthExchangeTokenType::get_request_body(
                    self, req,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &auth_types::ExchangeTokenRouterData,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ExchangeTokenRouterData, errors::ConnectorError> {
        let response: plaid::PlaidExchangeTokenResponse = res
            .response
            .parse_struct("PlaidExchangeTokenResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        <auth_types::ExchangeTokenRouterData>::try_from(auth_types::ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res)
    }
}

impl auth_service::AuthServiceBankAccountCredentials for Plaid {}

impl
    ConnectorIntegration<
        BankAccountCredentials,
        auth_types::BankAccountCredentialsRequest,
        auth_types::BankAccountCredentialsResponse,
    > for Plaid
{
    fn get_headers(
        &self,
        req: &auth_types::BankDetailsRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &auth_types::BankDetailsRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}{}", self.base_url(connectors), "/auth/get"))
    }

    fn get_request_body(
        &self,
        req: &auth_types::BankDetailsRouterData,
    ) -> errors::CustomResult<RequestContent, errors::ConnectorError> {
        let req_obj = plaid::PlaidBankAccountCredentialsRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(req_obj)))
    }

    fn build_request(
        &self,
        req: &auth_types::BankDetailsRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&auth_types::PaymentAuthBankAccountDetailsType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(auth_types::PaymentAuthBankAccountDetailsType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(
                    auth_types::PaymentAuthBankAccountDetailsType::get_request_body(self, req)?,
                )
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &auth_types::BankDetailsRouterData,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::BankDetailsRouterData, errors::ConnectorError> {
        let response: plaid::PlaidBankAccountCredentialsResponse = res
            .response
            .parse_struct("PlaidBankAccountCredentialsResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        <auth_types::BankDetailsRouterData>::try_from(auth_types::ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res)
    }
}

impl
    ConnectorIntegration<
        RecipientCreate,
        auth_types::RecipientCreateRequest,
        auth_types::RecipientCreateResponse,
    > for Plaid
{
    fn get_headers(
        &self,
        req: &auth_types::RecipientCreateRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &auth_types::RecipientCreateRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "/payment_initiation/recipient/create"
        ))
    }

    fn get_request_body(
        &self,
        req: &auth_types::RecipientCreateRouterData,
    ) -> errors::CustomResult<RequestContent, errors::ConnectorError> {
        let req_obj = plaid::PlaidRecipientCreateRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(req_obj)))
    }

    fn build_request(
        &self,
        req: &auth_types::RecipientCreateRouterData,
        connectors: &auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&auth_types::PaymentInitiationRecipientCreateType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(
                    auth_types::PaymentInitiationRecipientCreateType::get_headers(
                        self, req, connectors,
                    )?,
                )
                .set_body(
                    auth_types::PaymentInitiationRecipientCreateType::get_request_body(self, req)?,
                )
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &auth_types::RecipientCreateRouterData,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::RecipientCreateRouterData, errors::ConnectorError> {
        let response: plaid::PlaidRecipientCreateResponse = res
            .response
            .parse_struct("PlaidRecipientCreateResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        Ok(<auth_types::RecipientCreateRouterData>::from(
            auth_types::ResponseRouterData {
                response,
                data: data.clone(),
                http_code: res.status_code,
            },
        ))
    }
    fn get_error_response(
        &self,
        res: auth_types::Response,
    ) -> errors::CustomResult<auth_types::ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res)
    }
}


// File: crates/pm_auth/src/connector/plaid/transformers.rs

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>,
}


// File: crates/kgraph_utils/Cargo.toml

[package]
name = "kgraph_utils"
description = "Utilities for constructing and working with Knowledge Graphs"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[features]
dummy_connector = ["api_models/dummy_connector", "euclid/dummy_connector"]
v1 = ["api_models/v1", "common_utils/v1", "common_types/v1"]
v2 = ["api_models/v2", "common_utils/v2", "common_types/v2", "common_enums/v2"]

[dependencies]
api_models = { version = "0.1.0", path = "../api_models", package = "api_models" }
common_enums = { version = "0.1.0", path = "../common_enums" }
common_utils = { version = "0.1.0", path = "../common_utils" }
common_types = {version = "0.1.0", path = "../common_types" }
euclid = { version = "0.1.0", path = "../euclid" }
hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph", features = ["viz"] }
masking = { version = "0.1.0", path = "../masking/" }

# Third party crates
serde = "1.0.219"
serde_json = "1.0.140"
strum = { version = "0.26", features = ["derive"] }
thiserror = "1.0.69"

[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "evaluation"
harness = false

[lints]
workspace = true


// File: crates/kgraph_utils/benches/evaluation.rs

#![allow(unused, clippy::expect_used)]

use std::{collections::HashMap, str::FromStr};

use api_models::{
    admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes,
};
use common_utils::types::MinorUnit;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use euclid::{
    dirval,
    dssa::graph::{self, CgraphExt},
    frontend::dir,
    types::{NumValue, NumValueRefinement},
};
use hyperswitch_constraint_graph::{CycleCheck, Memoization};
use kgraph_utils::{error::KgraphError, transformers::IntoDirValue, types::CountryCurrencyFilter};

#[cfg(feature = "v1")]
fn build_test_data(
    total_enabled: usize,
    total_pm_types: usize,
) -> hyperswitch_constraint_graph::ConstraintGraph<dir::DirValue> {
    use api_models::{admin::*, payment_methods::*};

    let mut pms_enabled: Vec<PaymentMethodsEnabled> = Vec::new();

    for _ in (0..total_enabled) {
        let mut pm_types: Vec<RequestPaymentMethodTypes> = Vec::new();
        for _ in (0..total_pm_types) {
            pm_types.push(RequestPaymentMethodTypes {
                payment_method_type: api_enums::PaymentMethodType::Credit,
                payment_experience: None,
                card_networks: Some(vec![
                    api_enums::CardNetwork::Visa,
                    api_enums::CardNetwork::Mastercard,
                ]),
                accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![
                    api_enums::Currency::USD,
                    api_enums::Currency::INR,
                ])),
                accepted_countries: None,
                minimum_amount: Some(MinorUnit::new(10)),
                maximum_amount: Some(MinorUnit::new(1000)),
                recurring_enabled: Some(true),
                installment_payment_enabled: Some(true),
            });
        }

        pms_enabled.push(PaymentMethodsEnabled {
            payment_method: api_enums::PaymentMethod::Card,
            payment_method_types: Some(pm_types),
        });
    }

    let profile_id = common_utils::generate_profile_id_of_default_length();

    // #[cfg(feature = "v2")]
    // let stripe_account = MerchantConnectorResponse {
    //     connector_type: api_enums::ConnectorType::FizOperations,
    //     connector_name: "stripe".to_string(),
    //     id: common_utils::generate_merchant_connector_account_id_of_default_length(),
    //     connector_account_details: masking::Secret::new(serde_json::json!({})),
    //     disabled: None,
    //     metadata: None,
    //     payment_methods_enabled: Some(pms_enabled),
    //     connector_label: Some("something".to_string()),
    //     frm_configs: None,
    //     connector_webhook_details: None,
    //     profile_id,
    //     applepay_verified_domains: None,
    //     pm_auth_config: None,
    //     status: api_enums::ConnectorStatus::Inactive,
    //     additional_merchant_data: None,
    //     connector_wallets_details: None,
    // };

    #[cfg(feature = "v1")]
    let stripe_account = MerchantConnectorResponse {
        connector_type: api_enums::ConnectorType::FizOperations,
        connector_name: "stripe".to_string(),
        merchant_connector_id:
            common_utils::generate_merchant_connector_account_id_of_default_length(),
        connector_account_details: masking::Secret::new(serde_json::json!({})),
        test_mode: None,
        disabled: None,
        metadata: None,
        payment_methods_enabled: Some(pms_enabled),
        business_country: Some(api_enums::CountryAlpha2::US),
        business_label: Some("hello".to_string()),
        connector_label: Some("something".to_string()),
        business_sub_label: Some("something".to_string()),
        frm_configs: None,
        connector_webhook_details: None,
        profile_id,
        applepay_verified_domains: None,
        pm_auth_config: None,
        status: api_enums::ConnectorStatus::Inactive,
        additional_merchant_data: None,
        connector_wallets_details: None,
    };
    let config = CountryCurrencyFilter {
        connector_configs: HashMap::new(),
        default_configs: None,
    };

    #[cfg(feature = "v1")]
    kgraph_utils::mca::make_mca_graph(vec![stripe_account], &config)
        .expect("Failed graph construction")
}

#[cfg(feature = "v1")]
fn evaluation(c: &mut Criterion) {
    let small_graph = build_test_data(3, 8);
    let big_graph = build_test_data(20, 20);

    c.bench_function("MCA Small Graph Evaluation", |b| {
        b.iter(|| {
            small_graph.key_value_analysis(
                dirval!(Connector = Stripe),
                &graph::AnalysisContext::from_dir_values([
                    dirval!(Connector = Stripe),
                    dirval!(PaymentMethod = Card),
                    dirval!(CardType = Credit),
                    dirval!(CardNetwork = Visa),
                    dirval!(PaymentCurrency = BWP),
                    dirval!(PaymentAmount = 100),
                ]),
                &mut Memoization::new(),
                &mut CycleCheck::new(),
                None,
            );
        });
    });

    c.bench_function("MCA Big Graph Evaluation", |b| {
        b.iter(|| {
            big_graph.key_value_analysis(
                dirval!(Connector = Stripe),
                &graph::AnalysisContext::from_dir_values([
                    dirval!(Connector = Stripe),
                    dirval!(PaymentMethod = Card),
                    dirval!(CardType = Credit),
                    dirval!(CardNetwork = Visa),
                    dirval!(PaymentCurrency = BWP),
                    dirval!(PaymentAmount = 100),
                ]),
                &mut Memoization::new(),
                &mut CycleCheck::new(),
                None,
            );
        });
    });
}

#[cfg(feature = "v1")]
criterion_group!(benches, evaluation);
#[cfg(feature = "v1")]
criterion_main!(benches);

#[cfg(feature = "v2")]
fn main() {}


// File: crates/kgraph_utils/src/types.rs

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

use api_models::enums as api_enums;
use serde::Deserialize;

#[derive(Debug, Deserialize, Clone, Default)]
pub struct CountryCurrencyFilter {
    pub connector_configs: HashMap<api_enums::RoutableConnectors, PaymentMethodFilters>,
    pub default_configs: Option<PaymentMethodFilters>,
}

#[derive(Debug, Deserialize, Clone, Default)]
#[serde(transparent)]
pub struct PaymentMethodFilters(pub HashMap<PaymentMethodFilterKey, CurrencyCountryFlowFilter>);

#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum PaymentMethodFilterKey {
    PaymentMethodType(api_enums::PaymentMethodType),
    CardNetwork(api_enums::CardNetwork),
}

#[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)]
pub struct CurrencyCountryFlowFilter {
    pub currency: Option<HashSet<api_enums::Currency>>,
    pub country: Option<HashSet<api_enums::CountryAlpha2>>,
    pub not_available_flows: Option<NotAvailableFlows>,
}

#[derive(Debug, Deserialize, Copy, Clone, Default)]
#[serde(default)]
pub struct NotAvailableFlows {
    pub capture_method: Option<api_enums::CaptureMethod>,
}


// File: crates/kgraph_utils/src/error.rs

#[cfg(feature = "v2")]
use common_enums::connector_enums;
use euclid::{dssa::types::AnalysisErrorType, frontend::dir};
#[derive(Debug, thiserror::Error, serde::Serialize)]
#[serde(tag = "type", content = "info", rename_all = "snake_case")]
pub enum KgraphError {
    #[error("Invalid connector name encountered: '{0}'")]
    InvalidConnectorName(
        #[cfg(feature = "v1")] String,
        #[cfg(feature = "v2")] connector_enums::Connector,
    ),
    #[error("Error in domain creation")]
    DomainCreationError,
    #[error("There was an error constructing the graph: {0}")]
    GraphConstructionError(hyperswitch_constraint_graph::GraphError<dir::DirValue>),
    #[error("There was an error constructing the context")]
    ContextConstructionError(Box<AnalysisErrorType>),
    #[error("there was an unprecedented indexing error")]
    IndexingError,
}