File: crates/router/src/db/user/theme.rs

use common_utils::types::user::ThemeLineage;
use diesel_models::user::theme::{self as storage, ThemeUpdate};
use error_stack::report;

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

#[async_trait::async_trait]
pub trait ThemeInterface {
    async fn insert_theme(
        &self,
        theme: storage::ThemeNew,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn find_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn find_most_specific_theme_in_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn find_theme_by_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn update_theme_by_theme_id(
        &self,
        theme_id: String,
        theme_update: ThemeUpdate,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn delete_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError>;

    async fn list_themes_at_and_under_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<Vec<storage::Theme>, errors::StorageError>;
}

#[async_trait::async_trait]
impl ThemeInterface for Store {
    async fn insert_theme(
        &self,
        theme: storage::ThemeNew,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        theme
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn find_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Theme::find_by_theme_id(&conn, theme_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn find_most_specific_theme_in_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Theme::find_most_specific_theme_in_lineage(&conn, lineage)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn find_theme_by_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Theme::find_by_lineage(&conn, lineage)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn update_theme_by_theme_id(
        &self,
        theme_id: String,
        theme_update: ThemeUpdate,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::Theme::update_by_theme_id(&conn, theme_id, theme_update)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    async fn delete_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::Theme::delete_by_theme_id(&conn, theme_id)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
    async fn list_themes_at_and_under_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<Vec<storage::Theme>, errors::StorageError> {
        let conn = connection::pg_connection_read(self).await?;
        storage::Theme::find_all_by_lineage_hierarchy(&conn, lineage)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

fn check_theme_with_lineage(theme: &storage::Theme, lineage: &ThemeLineage) -> bool {
    match lineage {
        ThemeLineage::Tenant { tenant_id } => {
            &theme.tenant_id == tenant_id
                && theme.org_id.is_none()
                && theme.merchant_id.is_none()
                && theme.profile_id.is_none()
        }
        ThemeLineage::Organization { tenant_id, org_id } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
                && theme.merchant_id.is_none()
                && theme.profile_id.is_none()
        }
        ThemeLineage::Merchant {
            tenant_id,
            org_id,
            merchant_id,
        } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
                && theme
                    .merchant_id
                    .as_ref()
                    .is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
                && theme.profile_id.is_none()
        }
        ThemeLineage::Profile {
            tenant_id,
            org_id,
            merchant_id,
            profile_id,
        } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
                && theme
                    .merchant_id
                    .as_ref()
                    .is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
                && theme
                    .profile_id
                    .as_ref()
                    .is_some_and(|profile_id_inner| profile_id_inner == profile_id)
        }
    }
}

fn check_theme_belongs_to_lineage_hierarchy(
    theme: &storage::Theme,
    lineage: &ThemeLineage,
) -> bool {
    match lineage {
        ThemeLineage::Tenant { tenant_id } => &theme.tenant_id == tenant_id,
        ThemeLineage::Organization { tenant_id, org_id } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
        }
        ThemeLineage::Merchant {
            tenant_id,
            org_id,
            merchant_id,
        } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
                && theme
                    .merchant_id
                    .as_ref()
                    .is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
        }
        ThemeLineage::Profile {
            tenant_id,
            org_id,
            merchant_id,
            profile_id,
        } => {
            &theme.tenant_id == tenant_id
                && theme
                    .org_id
                    .as_ref()
                    .is_some_and(|org_id_inner| org_id_inner == org_id)
                && theme
                    .merchant_id
                    .as_ref()
                    .is_some_and(|merchant_id_inner| merchant_id_inner == merchant_id)
                && theme
                    .profile_id
                    .as_ref()
                    .is_some_and(|profile_id_inner| profile_id_inner == profile_id)
        }
    }
}

#[async_trait::async_trait]
impl ThemeInterface for MockDb {
    async fn insert_theme(
        &self,
        new_theme: storage::ThemeNew,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let mut themes = self.themes.lock().await;
        for theme in themes.iter() {
            if new_theme.theme_id == theme.theme_id {
                return Err(errors::StorageError::DuplicateValue {
                    entity: "theme_id",
                    key: None,
                }
                .into());
            }

            if new_theme.tenant_id == theme.tenant_id
                && new_theme.org_id == theme.org_id
                && new_theme.merchant_id == theme.merchant_id
                && new_theme.profile_id == theme.profile_id
            {
                return Err(errors::StorageError::DuplicateValue {
                    entity: "lineage",
                    key: None,
                }
                .into());
            }
        }

        let theme = storage::Theme {
            theme_id: new_theme.theme_id,
            tenant_id: new_theme.tenant_id,
            org_id: new_theme.org_id,
            merchant_id: new_theme.merchant_id,
            profile_id: new_theme.profile_id,
            created_at: new_theme.created_at,
            last_modified_at: new_theme.last_modified_at,
            entity_type: new_theme.entity_type,
            theme_name: new_theme.theme_name,
            email_primary_color: new_theme.email_primary_color,
            email_foreground_color: new_theme.email_foreground_color,
            email_background_color: new_theme.email_background_color,
            email_entity_name: new_theme.email_entity_name,
            email_entity_logo_url: new_theme.email_entity_logo_url,
        };
        themes.push(theme.clone());

        Ok(theme)
    }

    async fn find_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let themes = self.themes.lock().await;
        themes
            .iter()
            .find(|theme| theme.theme_id == theme_id)
            .cloned()
            .ok_or(
                errors::StorageError::ValueNotFound(format!("Theme with id {theme_id} not found"))
                    .into(),
            )
    }

    async fn find_most_specific_theme_in_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let themes = self.themes.lock().await;
        let lineages = lineage.get_same_and_higher_lineages();

        themes
            .iter()
            .filter(|theme| {
                lineages
                    .iter()
                    .any(|lineage| check_theme_with_lineage(theme, lineage))
            })
            .min_by_key(|theme| theme.entity_type)
            .ok_or(
                errors::StorageError::ValueNotFound("No theme found in lineage".to_string()).into(),
            )
            .cloned()
    }

    async fn find_theme_by_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let themes = self.themes.lock().await;
        themes
            .iter()
            .find(|theme| check_theme_with_lineage(theme, &lineage))
            .cloned()
            .ok_or(
                errors::StorageError::ValueNotFound(format!(
                    "Theme with lineage {lineage:?} not found",
                ))
                .into(),
            )
    }

    async fn update_theme_by_theme_id(
        &self,
        theme_id: String,
        theme_update: ThemeUpdate,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let mut themes = self.themes.lock().await;
        themes
            .iter_mut()
            .find(|theme| theme.theme_id == theme_id)
            .map(|theme| {
                match theme_update {
                    ThemeUpdate::EmailConfig { email_config } => {
                        theme.email_primary_color = email_config.primary_color;
                        theme.email_foreground_color = email_config.foreground_color;
                        theme.email_background_color = email_config.background_color;
                        theme.email_entity_name = email_config.entity_name;
                        theme.email_entity_logo_url = email_config.entity_logo_url;
                    }
                }
                theme.clone()
            })
            .ok_or_else(|| {
                report!(errors::StorageError::ValueNotFound(format!(
                    "Theme with id {theme_id} not found",
                )))
            })
    }

    async fn delete_theme_by_theme_id(
        &self,
        theme_id: String,
    ) -> CustomResult<storage::Theme, errors::StorageError> {
        let mut themes = self.themes.lock().await;
        let index = themes
            .iter()
            .position(|theme| theme.theme_id == theme_id)
            .ok_or(errors::StorageError::ValueNotFound(format!(
                "Theme with id {theme_id} not found"
            )))?;

        let theme = themes.remove(index);
        Ok(theme)
    }
    async fn list_themes_at_and_under_lineage(
        &self,
        lineage: ThemeLineage,
    ) -> CustomResult<Vec<storage::Theme>, errors::StorageError> {
        let themes = self.themes.lock().await;
        let matching_themes: Vec<storage::Theme> = themes
            .iter()
            .filter(|theme| check_theme_belongs_to_lineage_hierarchy(theme, &lineage))
            .cloned()
            .collect();

        Ok(matching_themes)
    }
}


impl Deref for DisputePollingIntervalInHours {
        type Target = i32;

        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }

impl Deref for for DisputePollingIntervalInHours


pub struct PaymentMethodsState {
    pub store: Box<dyn PaymentMethodsStorageInterface>,
    pub key_store: Option<merchant_key_store::MerchantKeyStore>,
    pub key_manager_state: keymanager::KeyManagerState,
}


pub struct Defend;


/// set routing approach
    pub fn set_routing_approach(&mut self, approach: String) {
        self.routing_approach = Some(approach);
    }


pub struct BillingConnectorPaymentsSync;


pub struct SdkSecretInfo {
    pub display: Secret<String>,
    pub payment: Secret<String>,
}


pub struct BankAccountDetailsSepa {
    pub iban: Secret<String>,
    pub bic: Secret<String>,
}


pub async fn default_service_405<E>(req: ServiceRequest) -> Result<ServiceResponse, E> {
//     Ok(req.into_response(ApiErrorResponse::InvalidHttpMethod.error_response()))
// }


impl api::PaymentsPreProcessing for Xendit {}

impl api::PaymentsPreProcessing for for Xendit


pub fn get_payment_method_type(&self) -> Option<storage_enums::PaymentMethodType> {
        // TODO: check if we can fix this
        Some(self.payment_method_subtype)
    }


pub struct BlackhawknetworkErrorResponse {
    pub error: String,
    pub error_description: Option<String>,
}


impl api::RefundSync for Thunes {}


impl Hash for RefundMetricsBucketIdentifier {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.currency.hash(state);
        self.refund_status.hash(state);
        self.connector.hash(state);
        self.refund_type.hash(state);
        self.profile_id.hash(state);
        self.refund_reason.hash(state);
        self.refund_error_message.hash(state);
        self.time_bucket.hash(state);
    }
}

impl Hash for for RefundMetricsBucketIdentifier


pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMinorUnitForConnector,
        }
    }


pub struct Avs {
    code: Option<String>,
    code_raw: Option<String>,
}


impl Responder {
    let flow = Flow::VolumeSplitOnRoutingType;
    let routing_info = api_models::routing::RoutingVolumeSplit {
        routing_type: api_models::routing::RoutingType::Dynamic,
        split: query.into_inner().split,
    };
    let payload = api_models::routing::RoutingVolumeSplitWrapper {
        routing_info,
        profile_id: path.into_inner().profile_id,
    };

    Box::pin(oss_api::server_wrap(
        flow,
        state,
        &req,
        payload.clone(),
        |state,
         auth: auth::AuthenticationData,
         payload: api_models::routing::RoutingVolumeSplitWrapper,
         _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            routing::configure_dynamic_routing_volume_split(
                state,
                merchant_context,
                payload.profile_id,
                payload.routing_info,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuthProfileFromRoute {
                profile_id: payload.profile_id,
                required_permission: Permission::ProfileRoutingWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct SyncIntegrityObject {
    /// Sync amount
    pub amount: Option<MinorUnit>,
    /// Sync currency
    pub currency: Option<storage_enums::Currency>,
}


pub struct ThreedsecureioAuthType {
    pub(super) api_key: Secret<String>,
}


pub trait OutgoingWebhookLogsFilterAnalytics: LoadRow<OutgoingWebhookLogsResult> {}


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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*TRUSTPAYMENTS_SUPPORTED_PAYMENT_METHODS)
    }

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

impl ConnectorSpecifications for for Trustpayments


pub trait CommonStorageInterface:
    StorageInterface
    + GlobalStorageInterface
    + AccountsStorageInterface
    + PaymentMethodsStorageInterface
{
    fn get_storage_interface(&self) -> Box<dyn StorageInterface>;
    fn get_global_storage_interface(&self) -> Box<dyn GlobalStorageInterface>;
    fn get_accounts_storage_interface(&self) -> Box<dyn AccountsStorageInterface>;
}


File: crates/hyperswitch_connectors/src/connectors/adyen/transformers.rs
 Public functions: 18
 Public structs: 84

use std::str::FromStr;

#[cfg(feature = "payouts")]
use api_models::payouts::{self, PayoutMethodData};
use api_models::{
    enums,
    payments::{self, PollConfig, QrCodeInformation, VoucherNextStepData},
};
use cards::CardNumber;
use common_enums::enums as storage_enums;
#[cfg(feature = "payouts")]
use common_utils::ext_traits::OptionExt;
use common_utils::{
    errors::{CustomResult, ParsingError},
    ext_traits::{Encode, ValueExt},
    pii::Email,
    request::Method,
    types::MinorUnit,
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    network_tokenization::NetworkTokenNumber,
    payment_method_data::{
        BankDebitData, BankRedirectData, BankTransferData, Card, CardRedirectData, GiftCardData,
        NetworkTokenData, PayLaterData, PaymentMethodData, VoucherData, WalletData,
    },
    router_data::{
        ConnectorAuthType, ErrorResponse, PaymentMethodBalance, PaymentMethodToken, RouterData,
    },
    router_request_types::{PaymentsPreProcessingData, ResponseId, SubmitEvidenceRequestData},
    router_response_types::{
        AcceptDisputeResponse, DefendDisputeResponse, MandateReference, PaymentsResponseData,
        RedirectForm, RefundsResponseData, SubmitEvidenceResponse,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsPreProcessingRouterData, RefundsRouterData,
    },
};
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_response_types::PayoutsResponseData, types::PayoutsRouterData,
};
use hyperswitch_interfaces::{
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime, PrimitiveDateTime};
use url::Url;

#[cfg(feature = "payouts")]
use crate::{types::PayoutsResponseRouterData, utils::PayoutsData};
use crate::{
    types::{
        AcceptDisputeRouterData, DefendDisputeRouterData, PaymentsCancelResponseRouterData,
        PaymentsCaptureResponseRouterData, RefundsResponseRouterData, ResponseRouterData,
        SubmitEvidenceRouterData,
    },
    utils::{
        self, is_manual_capture, missing_field_err, AddressDetailsData, BrowserInformationData,
        CardData, ForeignTryFrom, NetworkTokenData as UtilsNetworkTokenData,
        PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData as OtherRouterData,
    },
};

type Error = error_stack::Report<errors::ConnectorError>;

#[derive(Debug, Serialize)]
pub struct AdyenRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

impl<T> TryFrom<(MinorUnit, T)> for AdyenRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from((amount, item): (MinorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AdyenConnectorMetadataObject {
    pub endpoint_prefix: Option<String>,
}

impl TryFrom<&Option<common_utils::pii::SecretSerdeValue>> for AdyenConnectorMetadataObject {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        meta_data: &Option<common_utils::pii::SecretSerdeValue>,
    ) -> Result<Self, Self::Error> {
        match meta_data {
            Some(metadata) => utils::to_connector_meta_from_secret::<Self>(Some(metadata.clone()))
                .change_context(errors::ConnectorError::InvalidConnectorConfig {
                    config: "metadata",
                }),
            None => Ok(Self::default()),
        }
    }
}

// Adyen Types Definition
// Payments Request and Response Types
#[derive(Default, Debug, Serialize, Deserialize)]
pub enum AdyenShopperInteraction {
    #[default]
    Ecommerce,
    #[serde(rename = "ContAuth")]
    ContinuedAuthentication,
    Moto,
    #[serde(rename = "POS")]
    Pos,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum AdyenRecurringModel {
    UnscheduledCardOnFile,
    CardOnFile,
}

#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub enum AuthType {
    #[default]
    PreAuth,
}
#[serde_with::skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdditionalData {
    authorisation_type: Option<AuthType>,
    manual_capture: Option<String>,
    execute_three_d: Option<String>,
    pub recurring_processing_model: Option<AdyenRecurringModel>,
    /// Enable recurring details in dashboard to receive this ID, https://docs.adyen.com/online-payments/tokenization/create-and-use-tokens#test-and-go-live
    #[serde(rename = "recurring.recurringDetailReference")]
    recurring_detail_reference: Option<Secret<String>>,
    #[serde(rename = "recurring.shopperReference")]
    recurring_shopper_reference: Option<String>,
    network_tx_reference: Option<Secret<String>>,
    #[cfg(feature = "payouts")]
    payout_eligible: Option<PayoutEligibility>,
    funds_availability: Option<String>,
    refusal_reason_raw: Option<String>,
    refusal_code_raw: Option<String>,
    merchant_advice_code: Option<String>,
    #[serde(flatten)]
    riskdata: Option<RiskData>,
}

#[serde_with::skip_serializing_none]
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShopperName {
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
}

#[serde_with::skip_serializing_none]
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Address {
    city: String,
    country: enums::CountryAlpha2,
    house_number_or_name: Secret<String>,
    postal_code: Secret<String>,
    state_or_province: Option<Secret<String>>,
    street: Option<Secret<String>>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LineItem {
    amount_excluding_tax: Option<MinorUnit>,
    amount_including_tax: Option<MinorUnit>,
    description: Option<String>,
    id: Option<String>,
    tax_amount: Option<MinorUnit>,
    quantity: Option<u16>,
}

#[serde_with::skip_serializing_none]
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RiskData {
    #[serde(rename = "riskdata.basket.item1.itemID")]
    item_i_d: Option<String>,
    #[serde(rename = "riskdata.basket.item1.productTitle")]
    product_title: Option<String>,
    #[serde(rename = "riskdata.basket.item1.amountPerItem")]
    amount_per_item: Option<String>,
    #[serde(rename = "riskdata.basket.item1.currency")]
    currency: Option<String>,
    #[serde(rename = "riskdata.basket.item1.upc")]
    upc: Option<String>,
    #[serde(rename = "riskdata.basket.item1.brand")]
    brand: Option<String>,
    #[serde(rename = "riskdata.basket.item1.manufacturer")]
    manufacturer: Option<String>,
    #[serde(rename = "riskdata.basket.item1.category")]
    category: Option<String>,
    #[serde(rename = "riskdata.basket.item1.quantity")]
    quantity: Option<String>,
    #[serde(rename = "riskdata.basket.item1.color")]
    color: Option<String>,
    #[serde(rename = "riskdata.basket.item1.size")]
    size: Option<String>,
    #[serde(rename = "riskdata.deviceCountry")]
    device_country: Option<String>,
    #[serde(rename = "riskdata.houseNumberorName")]
    house_numberor_name: Option<String>,
    #[serde(rename = "riskdata.accountCreationDate")]
    account_creation_date: Option<String>,
    #[serde(rename = "riskdata.affiliateChannel")]
    affiliate_channel: Option<String>,
    #[serde(rename = "riskdata.avgOrderValue")]
    avg_order_value: Option<String>,
    #[serde(rename = "riskdata.deliveryMethod")]
    delivery_method: Option<String>,
    #[serde(rename = "riskdata.emailName")]
    email_name: Option<String>,
    #[serde(rename = "riskdata.emailDomain")]
    email_domain: Option<String>,
    #[serde(rename = "riskdata.lastOrderDate")]
    last_order_date: Option<String>,
    #[serde(rename = "riskdata.merchantReference")]
    merchant_reference: Option<String>,
    #[serde(rename = "riskdata.paymentMethod")]
    payment_method: Option<String>,
    #[serde(rename = "riskdata.promotionName")]
    promotion_name: Option<String>,
    #[serde(rename = "riskdata.secondaryPhoneNumber")]
    secondary_phone_number: Option<String>,
    #[serde(rename = "riskdata.timefromLogintoOrder")]
    timefrom_loginto_order: Option<String>,
    #[serde(rename = "riskdata.totalSessionTime")]
    total_session_time: Option<String>,
    #[serde(rename = "riskdata.totalAuthorizedAmountInLast30Days")]
    total_authorized_amount_in_last30_days: Option<String>,
    #[serde(rename = "riskdata.totalOrderQuantity")]
    total_order_quantity: Option<String>,
    #[serde(rename = "riskdata.totalLifetimeValue")]
    total_lifetime_value: Option<String>,
    #[serde(rename = "riskdata.visitsMonth")]
    visits_month: Option<String>,
    #[serde(rename = "riskdata.visitsWeek")]
    visits_week: Option<String>,
    #[serde(rename = "riskdata.visitsYear")]
    visits_year: Option<String>,
    #[serde(rename = "riskdata.shipToName")]
    ship_to_name: Option<String>,
    #[serde(rename = "riskdata.first8charactersofAddressLine1Zip")]
    first8charactersof_address_line1_zip: Option<String>,
    #[serde(rename = "riskdata.affiliateOrder")]
    affiliate_order: Option<bool>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPaymentRequest<'a> {
    amount: Amount,
    merchant_account: Secret<String>,
    payment_method: PaymentMethod<'a>,
    mpi_data: Option<AdyenMpiData>,
    reference: String,
    return_url: String,
    browser_info: Option<AdyenBrowserInfo>,
    shopper_interaction: AdyenShopperInteraction,
    recurring_processing_model: Option<AdyenRecurringModel>,
    additional_data: Option<AdditionalData>,
    shopper_reference: Option<String>,
    store_payment_method: Option<bool>,
    shopper_name: Option<ShopperName>,
    #[serde(rename = "shopperIP")]
    shopper_ip: Option<Secret<String, common_utils::pii::IpAddress>>,
    shopper_locale: Option<String>,
    shopper_email: Option<Email>,
    shopper_statement: Option<String>,
    social_security_number: Option<Secret<String>>,
    telephone_number: Option<Secret<String>>,
    billing_address: Option<Address>,
    delivery_address: Option<Address>,
    country_code: Option<enums::CountryAlpha2>,
    line_items: Option<Vec<LineItem>>,
    channel: Option<Channel>,
    merchant_order_reference: Option<String>,
    splits: Option<Vec<AdyenSplitData>>,
    store: Option<String>,
    device_fingerprint: Option<Secret<String>>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    session_validity: Option<PrimitiveDateTime>,
    metadata: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AdyenSplitData {
    amount: Option<Amount>,
    #[serde(rename = "type")]
    split_type: common_enums::AdyenSplitType,
    account: Option<String>,
    reference: String,
    description: Option<String>,
}

#[serde_with::skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct AdyenMpiData {
    directory_response: String,
    authentication_response: String,
    cavv: Option<Secret<String>>,
    token_authentication_verification_value: Option<Secret<String>>,
    eci: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct AdyenBrowserInfo {
    user_agent: String,
    accept_header: String,
    language: String,
    color_depth: u8,
    screen_height: u32,
    screen_width: u32,
    time_zone_offset: i32,
    java_enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AdyenStatus {
    AuthenticationFinished,
    AuthenticationNotRequired,
    Authorised,
    Cancelled,
    ChallengeShopper,
    Error,
    Pending,
    Received,
    RedirectShopper,
    Refused,
    PresentToShopper,
    #[cfg(feature = "payouts")]
    #[serde(rename = "[payout-confirm-received]")]
    PayoutConfirmReceived,
    #[cfg(feature = "payouts")]
    #[serde(rename = "[payout-decline-received]")]
    PayoutDeclineReceived,
    #[cfg(feature = "payouts")]
    #[serde(rename = "[payout-submit-received]")]
    PayoutSubmitReceived,
}

#[derive(Debug, Clone, Serialize)]
pub enum Channel {
    Web,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenBalanceRequest<'a> {
    pub payment_method: AdyenPaymentMethod<'a>,
    pub merchant_account: Secret<String>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenBalanceResponse {
    pub psp_reference: String,
    pub balance: Amount,
}

/// This implementation will be used only in Authorize, Automatic capture flow.
/// It is also being used in Psync flow, However Psync will be called only after create payment call that too in redirect flow.
fn get_adyen_payment_status(
    is_manual_capture: bool,
    adyen_status: AdyenStatus,
    pmt: Option<common_enums::PaymentMethodType>,
) -> storage_enums::AttemptStatus {
    match adyen_status {
        AdyenStatus::AuthenticationFinished => {
            storage_enums::AttemptStatus::AuthenticationSuccessful
        }
        AdyenStatus::AuthenticationNotRequired | AdyenStatus::Received => {
            storage_enums::AttemptStatus::Pending
        }
        AdyenStatus::Authorised => match is_manual_capture {
            true => storage_enums::AttemptStatus::Authorized,
            // In case of Automatic capture Authorized is the final status of the payment
            false => storage_enums::AttemptStatus::Charged,
        },
        AdyenStatus::Cancelled => storage_enums::AttemptStatus::Voided,
        AdyenStatus::ChallengeShopper
        | AdyenStatus::RedirectShopper
        | AdyenStatus::PresentToShopper => storage_enums::AttemptStatus::AuthenticationPending,
        AdyenStatus::Error | AdyenStatus::Refused => storage_enums::AttemptStatus::Failure,
        AdyenStatus::Pending => match pmt {
            Some(common_enums::PaymentMethodType::Pix) => {
                storage_enums::AttemptStatus::AuthenticationPending
            }
            _ => storage_enums::AttemptStatus::Pending,
        },
        #[cfg(feature = "payouts")]
        AdyenStatus::PayoutConfirmReceived => storage_enums::AttemptStatus::Started,
        #[cfg(feature = "payouts")]
        AdyenStatus::PayoutSubmitReceived => storage_enums::AttemptStatus::Pending,
        #[cfg(feature = "payouts")]
        AdyenStatus::PayoutDeclineReceived => storage_enums::AttemptStatus::Voided,
    }
}

impl ForeignTryFrom<(bool, AdyenWebhookStatus)> for storage_enums::AttemptStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        (is_manual_capture, adyen_webhook_status): (bool, AdyenWebhookStatus),
    ) -> Result<Self, Self::Error> {
        match adyen_webhook_status {
            AdyenWebhookStatus::Authorised => match is_manual_capture {
                true => Ok(Self::Authorized),
                // In case of Automatic capture Authorized is the final status of the payment
                false => Ok(Self::Charged),
            },
            AdyenWebhookStatus::AuthorisationFailed => Ok(Self::Failure),
            AdyenWebhookStatus::Cancelled => Ok(Self::Voided),
            AdyenWebhookStatus::CancelFailed => Ok(Self::VoidFailed),
            AdyenWebhookStatus::Captured => Ok(Self::Charged),
            AdyenWebhookStatus::CaptureFailed => Ok(Self::CaptureFailed),
            AdyenWebhookStatus::Expired => Ok(Self::Expired),
            //If Unexpected Event is received, need to understand how it reached this point
            //Webhooks with Payment Events only should try to conume this resource object.
            AdyenWebhookStatus::UnexpectedEvent | AdyenWebhookStatus::Reversed => {
                Err(report!(errors::ConnectorError::WebhookBodyDecodingFailed))
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct AdyenRedirectRequest {
    pub details: AdyenRedirectRequestTypes,
}

#[derive(Debug, Clone, Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum AdyenRedirectRequestTypes {
    AdyenRedirection(AdyenRedirection),
    AdyenThreeDS(AdyenThreeDS),
    AdyenRefusal(AdyenRefusal),
}

#[derive(Debug, Clone, Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AdyenRefusal {
    pub payload: String,
    #[serde(rename = "type")]
    pub type_of_redirection_result: Option<String>,
    pub result_code: Option<String>,
}

#[derive(Debug, Clone, Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AdyenRedirection {
    pub redirect_result: String,
    #[serde(rename = "type")]
    pub type_of_redirection_result: Option<String>,
    pub result_code: Option<String>,
}

#[derive(Debug, Clone, Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AdyenThreeDS {
    #[serde(rename = "threeDSResult")]
    pub three_ds_result: String,
    #[serde(rename = "type")]
    pub type_of_redirection_result: Option<String>,
    pub result_code: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum AdyenPaymentResponse {
    Response(Box<AdyenResponse>),
    PresentToShopper(Box<PresentToShopperResponse>),
    QrCodeResponse(Box<QrCodeResponseResponse>),
    RedirectionResponse(Box<RedirectionResponse>),
    RedirectionErrorResponse(Box<RedirectionErrorResponse>),
    WebhookResponse(Box<AdyenWebhookResponse>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenResponse {
    psp_reference: String,
    result_code: AdyenStatus,
    amount: Option<Amount>,
    merchant_reference: String,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    additional_data: Option<AdditionalData>,
    splits: Option<Vec<AdyenSplitData>>,
    store: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AdyenWebhookStatus {
    Authorised,
    AuthorisationFailed,
    Cancelled,
    CancelFailed,
    Captured,
    CaptureFailed,
    Reversed,
    UnexpectedEvent,
    Expired,
}

//Creating custom struct which can be consumed in Psync Handler triggered from Webhooks
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenWebhookResponse {
    transaction_id: String,
    payment_reference: Option<String>,
    status: AdyenWebhookStatus,
    amount: Option<Amount>,
    merchant_reference_id: String,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    event_code: WebhookEventCode,
    // Raw acquirer refusal code
    refusal_code_raw: Option<String>,
    // Raw acquirer refusal reason
    refusal_reason_raw: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RedirectionErrorResponse {
    result_code: AdyenStatus,
    refusal_reason: Option<String>,
    psp_reference: Option<String>,
    merchant_reference: Option<String>,
    additional_data: Option<AdditionalData>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RedirectionResponse {
    result_code: AdyenStatus,
    action: AdyenRedirectAction,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    psp_reference: Option<String>,
    merchant_reference: Option<String>,
    store: Option<String>,
    splits: Option<Vec<AdyenSplitData>>,
    additional_data: Option<AdditionalData>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PresentToShopperResponse {
    psp_reference: Option<String>,
    result_code: AdyenStatus,
    action: AdyenPtsAction,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    merchant_reference: Option<String>,
    store: Option<String>,
    splits: Option<Vec<AdyenSplitData>>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct QrCodeResponseResponse {
    result_code: AdyenStatus,
    action: AdyenQrCodeAction,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    additional_data: Option<QrCodeAdditionalData>,
    psp_reference: Option<String>,
    merchant_reference: Option<String>,
    store: Option<String>,
    splits: Option<Vec<AdyenSplitData>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenQrCodeAction {
    payment_method_type: PaymentType,
    #[serde(rename = "type")]
    type_of_response: ActionType,
    #[serde(rename = "url")]
    qr_code_url: Option<Url>,
    qr_code_data: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QrCodeAdditionalData {
    #[serde(rename = "pix.expirationDate")]
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pix_expiration_date: Option<PrimitiveDateTime>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPtsAction {
    reference: String,
    download_url: Option<Url>,
    payment_method_type: PaymentType,
    #[serde(rename = "expiresAt")]
    #[serde(
        default,
        with = "common_utils::custom_serde::iso8601::option_without_timezone"
    )]
    expires_at: Option<PrimitiveDateTime>,
    initial_amount: Option<Amount>,
    pass_creation_token: Option<String>,
    total_amount: Option<Amount>,
    #[serde(rename = "type")]
    type_of_response: ActionType,
    instructions_url: Option<Url>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenRedirectAction {
    payment_method_type: PaymentType,
    url: Option<Url>,
    method: Option<Method>,
    #[serde(rename = "type")]
    type_of_response: ActionType,
    data: Option<std::collections::HashMap<String, String>>,
    payment_data: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ActionType {
    Redirect,
    Await,
    #[serde(rename = "qrCode")]
    QrCode,
    Voucher,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Amount {
    pub currency: storage_enums::Currency,
    pub value: MinorUnit,
}

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum PaymentMethod<'a> {
    AdyenPaymentMethod(Box<AdyenPaymentMethod<'a>>),
    AdyenMandatePaymentMethod(Box<AdyenMandate>),
}

#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum AdyenPaymentMethod<'a> {
    #[serde(rename = "affirm")]
    AdyenAffirm,
    #[serde(rename = "scheme")]
    AdyenCard(Box<AdyenCard>),
    #[serde(rename = "klarna")]
    AdyenKlarna,
    #[serde(rename = "paypal")]
    AdyenPaypal,
    #[serde(rename = "networkToken")]
    AdyenPaze(Box<AdyenPazeData>),
    #[serde(rename = "afterpaytouch")]
    AfterPay,
    #[serde(rename = "alma")]
    AlmaPayLater,
    AliPay,
    #[serde(rename = "alipay_hk")]
    AliPayHk,
    ApplePay(Box<AdyenApplePay>),
    ApplePayDecrypt(Box<AdyenApplePayDecryptData>),
    Atome,
    #[serde(rename = "scheme")]
    BancontactCard(Box<AdyenCard>),
    Bizum,
    Blik(Box<BlikRedirectionData>),
    #[serde(rename = "boletobancario")]
    BoletoBancario,
    #[serde(rename = "clearpay")]
    ClearPay,
    #[serde(rename = "dana")]
    Dana,
    Eps(Box<BankRedirectionWithIssuer<'a>>),
    #[serde(rename = "gcash")]
    Gcash(Box<GcashData>),
    #[serde(rename = "googlepay")]
    Gpay(Box<AdyenGPay>),
    #[serde(rename = "gopay_wallet")]
    GoPay(Box<GoPayData>),
    Ideal,
    #[serde(rename = "kakaopay")]
    Kakaopay(Box<KakaoPayData>),
    Mbway(Box<MbwayData>),
    MobilePay,
    #[serde(rename = "momo_wallet")]
    Momo(Box<MomoData>),
    #[serde(rename = "momo_atm")]
    MomoAtm,
    #[serde(rename = "touchngo")]
    TouchNGo(Box<TouchNGoData>),
    #[serde(rename = "onlineBanking_CZ")]
    OnlineBankingCzechRepublic(Box<OnlineBankingCzechRepublicData>),
    #[serde(rename = "ebanking_FI")]
    OnlineBankingFinland,
    #[serde(rename = "onlineBanking_PL")]
    OnlineBankingPoland(Box<OnlineBankingPolandData>),
    #[serde(rename = "onlineBanking_SK")]
    OnlineBankingSlovakia(Box<OnlineBankingSlovakiaData>),
    #[serde(rename = "molpay_ebanking_fpx_MY")]
    OnlineBankingFpx(Box<OnlineBankingFpxData>),
    #[serde(rename = "molpay_ebanking_TH")]
    OnlineBankingThailand(Box<OnlineBankingThailandData>),
    #[serde(rename = "paybybank")]
    OpenBankingUK(Box<OpenBankingUKData>),
    #[serde(rename = "oxxo")]
    Oxxo,
    #[serde(rename = "paysafecard")]
    PaySafeCard,
    #[serde(rename = "paybright")]
    PayBright,
    #[serde(rename = "doku_permata_lite_atm")]
    PermataBankTransfer(Box<DokuBankData>),
    #[serde(rename = "trustly")]
    Trustly,
    #[serde(rename = "walley")]
    Walley,
    #[serde(rename = "wechatpayWeb")]
    WeChatPayWeb,
    #[serde(rename = "ach")]
    AchDirectDebit(Box<AchDirectDebitData>),
    #[serde(rename = "sepadirectdebit")]
    SepaDirectDebit(Box<SepaDirectDebitData>),
    #[serde(rename = "directdebit_GB")]
    BacsDirectDebit(Box<BacsDirectDebitData>),
    SamsungPay(Box<SamsungPayPmData>),
    #[serde(rename = "doku_bca_va")]
    BcaBankTransfer(Box<DokuBankData>),
    #[serde(rename = "doku_bni_va")]
    BniVa(Box<DokuBankData>),
    #[serde(rename = "doku_bri_va")]
    BriVa(Box<DokuBankData>),
    #[serde(rename = "doku_cimb_va")]
    CimbVa(Box<DokuBankData>),
    #[serde(rename = "doku_danamon_va")]
    DanamonVa(Box<DokuBankData>),
    #[serde(rename = "doku_mandiri_va")]
    MandiriVa(Box<DokuBankData>),
    #[serde(rename = "twint")]
    Twint,
    #[serde(rename = "vipps")]
    Vipps,
    #[serde(rename = "doku_indomaret")]
    Indomaret(Box<DokuBankData>),
    #[serde(rename = "doku_alfamart")]
    Alfamart(Box<DokuBankData>),
    #[serde(rename = "givex")]
    PaymentMethodBalance(Box<BalancePmData>),
    #[serde(rename = "giftcard")]
    AdyenGiftCard(Box<AdyenGiftCardData>),
    #[serde(rename = "swish")]
    Swish,
    #[serde(rename = "benefit")]
    Benefit,
    #[serde(rename = "knet")]
    Knet,
    #[serde(rename = "econtext_seven_eleven")]
    SevenEleven(Box<JCSVoucherData>),
    #[serde(rename = "econtext_stores")]
    JapaneseConvenienceStores(Box<JCSVoucherData>),
    Pix,
    #[serde(rename = "networkToken")]
    NetworkToken(Box<AdyenNetworkTokenData>),
}

#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JCSVoucherData {
    first_name: Secret<String>,
    last_name: Option<Secret<String>>,
    shopper_email: Email,
    telephone_number: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BalancePmData {
    number: Secret<String>,
    cvc: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenGiftCardData {
    brand: GiftCardBrand,
    number: Secret<String>,
    cvc: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AchDirectDebitData {
    bank_account_number: Secret<String>,
    bank_location_id: Secret<String>,
    owner_name: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SepaDirectDebitData {
    #[serde(rename = "sepa.ownerName")]
    owner_name: Secret<String>,
    #[serde(rename = "sepa.ibanNumber")]
    iban_number: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BacsDirectDebitData {
    bank_account_number: Secret<String>,
    bank_location_id: Secret<String>,
    holder_name: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MbwayData {
    telephone_number: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct SamsungPayPmData {
    #[serde(rename = "samsungPayToken")]
    samsung_pay_token: Secret<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct OnlineBankingCzechRepublicData {
    issuer: OnlineBankingCzechRepublicBanks,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum OnlineBankingCzechRepublicBanks {
    KB,
    CS,
}

impl TryFrom<&PaymentsAuthorizeRouterData> for JCSVoucherData {
    type Error = Error;
    fn try_from(item: &PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            first_name: item.get_billing_first_name()?,
            last_name: item.get_optional_billing_last_name(),
            shopper_email: item.get_billing_email()?,
            telephone_number: item.get_billing_phone_number()?,
        })
    }
}

impl TryFrom<&common_enums::BankNames> for OnlineBankingCzechRepublicBanks {
    type Error = Error;
    fn try_from(bank_name: &common_enums::BankNames) -> Result<Self, Self::Error> {
        match bank_name {
            common_enums::BankNames::KomercniBanka => Ok(Self::KB),
            common_enums::BankNames::CeskaSporitelna => Ok(Self::CS),
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Adyen"),
            ))?,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct OnlineBankingPolandData {
    issuer: OnlineBankingPolandBanks,
}

#[derive(Debug, Clone, Serialize)]
pub enum OnlineBankingPolandBanks {
    #[serde(rename = "154")]
    BlikPSP,
    #[serde(rename = "31")]
    PlaceZIPKO,
    #[serde(rename = "243")]
    MBank,
    #[serde(rename = "112")]
    PayWithING,
    #[serde(rename = "20")]
    SantanderPrzelew24,
    #[serde(rename = "65")]
    BankPEKAOSA,
    #[serde(rename = "85")]
    BankMillennium,
    #[serde(rename = "88")]
    PayWithAliorBank,
    #[serde(rename = "143")]
    BankiSpoldzielcze,
    #[serde(rename = "26")]
    PayWithInteligo,
    #[serde(rename = "33")]
    BNPParibasPoland,
    #[serde(rename = "144")]
    BankNowySA,
    #[serde(rename = "45")]
    CreditAgricole,
    #[serde(rename = "99")]
    PayWithBOS,
    #[serde(rename = "119")]
    PayWithCitiHandlowy,
    #[serde(rename = "131")]
    PayWithPlusBank,
    #[serde(rename = "64")]
    ToyotaBank,
    #[serde(rename = "153")]
    VeloBank,
    #[serde(rename = "141")]
    ETransferPocztowy24,
}

impl TryFrom<&common_enums::BankNames> for OnlineBankingPolandBanks {
    type Error = Error;
    fn try_from(bank_name: &common_enums::BankNames) -> Result<Self, Self::Error> {
        match bank_name {
            common_enums::BankNames::BlikPSP => Ok(Self::BlikPSP),
            common_enums::BankNames::PlaceZIPKO => Ok(Self::PlaceZIPKO),
            common_enums::BankNames::MBank => Ok(Self::MBank),
            common_enums::BankNames::PayWithING => Ok(Self::PayWithING),
            common_enums::BankNames::SantanderPrzelew24 => Ok(Self::SantanderPrzelew24),
            common_enums::BankNames::BankPEKAOSA => Ok(Self::BankPEKAOSA),
            common_enums::BankNames::BankMillennium => Ok(Self::BankMillennium),
            common_enums::BankNames::PayWithAliorBank => Ok(Self::PayWithAliorBank),
            common_enums::BankNames::BankiSpoldzielcze => Ok(Self::BankiSpoldzielcze),
            common_enums::BankNames::PayWithInteligo => Ok(Self::PayWithInteligo),
            common_enums::BankNames::BNPParibasPoland => Ok(Self::BNPParibasPoland),
            common_enums::BankNames::BankNowySA => Ok(Self::BankNowySA),
            common_enums::BankNames::CreditAgricole => Ok(Self::CreditAgricole),
            common_enums::BankNames::PayWithBOS => Ok(Self::PayWithBOS),
            common_enums::BankNames::PayWithCitiHandlowy => Ok(Self::PayWithCitiHandlowy),
            common_enums::BankNames::PayWithPlusBank => Ok(Self::PayWithPlusBank),
            common_enums::BankNames::ToyotaBank => Ok(Self::ToyotaBank),
            common_enums::BankNames::VeloBank => Ok(Self::VeloBank),
            common_enums::BankNames::ETransferPocztowy24 => Ok(Self::ETransferPocztowy24),
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Adyen"),
            ))?,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OnlineBankingSlovakiaData {
    issuer: OnlineBankingSlovakiaBanks,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OnlineBankingFpxData {
    issuer: OnlineBankingFpxIssuer,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OnlineBankingThailandData {


Documentation: api-reference/v1/customers/customers--retrieve.mdx
# Type: Doc File

---
openapi: get /customers/{customer_id}
---


pub struct ErrorResponse {
    pub error: ErrorData,
}


impl api::PaymentVoid for Multisafepay {}

impl api::PaymentVoid for for Multisafepay


File: crates/hyperswitch_connectors/src/connectors/dwolla.rs
 Public functions: 1
 Public structs: 1

pub mod transformers;

use std::sync::LazyLock;

use base64::engine::Engine;
use common_enums::enums;
use common_utils::{
    consts::BASE64_ENGINE,
    crypto,
    errors::CustomResult,
    ext_traits::{ByteSliceExt, BytesExt},
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ErrorResponse, PaymentMethodToken as PMT, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
        CreateConnectorCustomer,
    },
    router_request_types::{
        AccessTokenRequestData, ConnectorCustomerData, PaymentMethodTokenizationData,
        PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData,
        PaymentsSyncData, RefundsData, ResponseId, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsSyncRouterData,
        RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, TokenizationRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
        ConnectorValidation,
    },
    configs::Connectors,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, RefreshTokenType, Response, TokenizationType},
    webhooks,
};
use masking::{ExposeInterface, Mask, Secret};
use ring::hmac;
use transformers as dwolla;
use transformers::extract_token_from_body;

use crate::{
    constants::headers,
    types::ResponseRouterData,
    utils::{convert_amount, get_http_header, RefundsRequestData, RouterData as RD},
};

#[derive(Clone)]
pub struct Dwolla {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMajorUnit> + Sync),
}

impl Dwolla {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMajorUnitForConnector,
        }
    }
}

impl api::ConnectorCustomer for Dwolla {}
impl api::Payment for Dwolla {}
impl api::PaymentSession for Dwolla {}
impl api::ConnectorAccessToken for Dwolla {}
impl api::MandateSetup for Dwolla {}
impl api::PaymentAuthorize for Dwolla {}
impl api::PaymentSync for Dwolla {}
impl api::PaymentCapture for Dwolla {}
impl api::PaymentVoid for Dwolla {}
impl api::Refund for Dwolla {}
impl api::RefundExecute for Dwolla {}
impl api::RefundSync for Dwolla {}
impl api::PaymentToken for Dwolla {}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Dwolla
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let access_token = req
            .access_token
            .clone()
            .ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
        let header = vec![
            (
                headers::CONTENT_TYPE.to_string(),
                self.common_get_content_type().to_string().into(),
            ),
            (
                headers::ACCEPT.to_string(),
                self.common_get_content_type().to_string().into(),
            ),
            (
                headers::AUTHORIZATION.to_string(),
                format!("Bearer {}", access_token.token.expose()).into_masked(),
            ),
            (
                headers::IDEMPOTENCY_KEY.to_string(),
                uuid::Uuid::new_v4().to_string().into(),
            ),
        ];
        Ok(header)
    }
}

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

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

    fn common_get_content_type(&self) -> &'static str {
        "application/vnd.dwolla.v1.hal+json"
    }

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

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

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

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.code,
            message: response.message,
            reason: response
                ._embedded
                .as_ref()
                .and_then(|errors_vec| errors_vec.first())
                .and_then(|details| details.errors.first())
                .and_then(|err_detail| err_detail.message.clone()),
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Dwolla {
    //TODO: implement functions when support enabled
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Dwolla {
    //TODO: implement sessions flow
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Dwolla {
    fn get_url(
        &self,
        _req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/token", self.base_url(connectors)))
    }

    fn get_headers(
        &self,
        req: &RefreshTokenRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = dwolla::DwollaAuthType::try_from(&req.connector_auth_type)?;
        let mut headers = vec![(
            headers::CONTENT_TYPE.to_string(),
            "application/x-www-form-urlencoded".to_string().into(),
        )];

        let auth_str = format!(
            "{}:{}",
            auth.client_id.expose(),
            auth.client_secret.expose()
        );

        let encoded = BASE64_ENGINE.encode(auth_str);
        let auth_header_value = format!("Basic {encoded}");
        headers.push((
            headers::AUTHORIZATION.to_string(),
            auth_header_value.into_masked(),
        ));

        Ok(headers)
    }

    fn build_request(
        &self,
        req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let headers = self.get_headers(req, connectors)?;

        let connector_req = dwolla::DwollaAccessTokenRequest {
            grant_type: "client_credentials".to_string(),
        };
        let body = RequestContent::FormUrlEncoded(Box::new(connector_req));

        let req = Some(
            RequestBuilder::new()
                .method(Method::Post)
                .headers(headers)
                .url(&RefreshTokenType::get_url(self, req, connectors)?)
                .set_body(body)
                .build(),
        );
        Ok(req)
    }

    fn handle_response(
        &self,
        data: &RefreshTokenRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefreshTokenRouterData, errors::ConnectorError> {
        let response: dwolla::DwollaAccessTokenResponse = res
            .response
            .parse_struct("Dwolla DwollaAccessTokenResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

impl ConnectorIntegration<CreateConnectorCustomer, ConnectorCustomerData, PaymentsResponseData>
    for Dwolla
{
    fn get_url(
        &self,
        _req: &ConnectorCustomerRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/customers", self.base_url(connectors)))
    }

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

    fn get_headers(
        &self,
        req: &ConnectorCustomerRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &ConnectorCustomerRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = dwolla::DwollaCustomerRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &ConnectorCustomerRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::ConnectorCustomerType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(types::ConnectorCustomerType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::ConnectorCustomerType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        );
        Ok(request)
    }

    fn handle_response(
        &self,
        data: &ConnectorCustomerRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<ConnectorCustomerRouterData, errors::ConnectorError> {
        let headers = res
            .headers
            .as_ref()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
        let location = get_http_header("Location", headers)
            .change_context(errors::ConnectorError::ResponseHandlingFailed)?;

        let connector_customer_id = location
            .split('/')
            .next_back()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?
            .to_string();

        let response = serde_json::json!({"connector_customer_id": connector_customer_id.clone()});
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(RouterData {
            connector_customer: Some(connector_customer_id.clone()),
            response: Ok(PaymentsResponseData::ConnectorCustomerResponse {
                connector_customer_id,
            }),
            ..data.clone()
        })
    }

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

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Dwolla
{
    fn get_headers(
        &self,
        req: &TokenizationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::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: &TokenizationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let customer_id = req.get_connector_customer_id()?;
        Ok(format!(
            "{}/customers/{}/funding-sources",
            self.base_url(connectors),
            customer_id
        ))
    }

    fn get_request_body(
        &self,
        req: &TokenizationRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = dwolla::DwollaFundingSourceRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &TokenizationRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<TokenizationRouterData, errors::ConnectorError> {
        let token = res
            .headers
            .as_ref()
            .and_then(|headers| get_http_header("Location", headers).ok())
            .and_then(|location| location.rsplit('/').next().map(|s| s.to_string()))
            .ok_or_else(|| report!(errors::ConnectorError::ResponseHandlingFailed))?;

        let response = serde_json::json!({ "payment_token": token });

        if let Some(builder) = event_builder {
            builder.set_response_body(&response);
        }

        router_env::logger::info!(connector_response=?response);

        Ok(RouterData {
            payment_method_token: Some(PMT::Token(token.clone().into())),
            response: Ok(PaymentsResponseData::TokenizationResponse { token }),
            ..data.clone()
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        if let Ok(body) = std::str::from_utf8(&res.response) {
            if res.status_code == 400 && body.contains("Duplicate") {
                let token = extract_token_from_body(&res.response);
                let metadata = Some(Secret::new(
                    serde_json::json!({ "payment_method_token": token? }),
                ));
                let response: dwolla::DwollaErrorResponse = res
                    .response
                    .parse_struct("DwollaErrorResponse")
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

                event_builder.map(|i| i.set_response_body(&response));

                return Ok(ErrorResponse {
                    status_code: res.status_code,
                    code: response.code,
                    message: response.message,
                    reason: response
                        ._embedded
                        .as_ref()
                        .and_then(|errors_vec| errors_vec.first())
                        .and_then(|details| details.errors.first())
                        .and_then(|err_detail| err_detail.message.clone()),
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: metadata,
                });
            }
        }
        self.build_error_response(res, event_builder)
    }
}

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

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Dwolla {
    fn get_headers(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::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: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/transfers", self.base_url(connectors)))
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;
        let connector_router_data =
            dwolla::DwollaRouterData::try_from((amount, req, self.base_url(connectors)))?;
        let connector_req = dwolla::DwollaPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let headers = res
            .headers
            .as_ref()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
        let location = get_http_header("Location", headers)
            .change_context(errors::ConnectorError::ResponseHandlingFailed)?;

        let payment_id = location
            .split('/')
            .next_back()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?
            .to_string();
        let response = serde_json::json!({"payment_id : ": payment_id.clone()});

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

        let connector_metadata = data
            .payment_method_token
            .as_ref()
            .and_then(|token| match token {
                PMT::Token(t) => Some(serde_json::json!({ "payment_token": t.clone().expose() })),
                _ => None,
            });

        Ok(RouterData {
            payment_method_token: data.payment_method_token.clone(),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(payment_id.clone()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata,
                network_txn_id: None,
                connector_response_reference_id: Some(payment_id.clone()),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            amount_captured: Some(data.request.amount),
            ..data.clone()
        })
    }

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

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Dwolla {
    fn get_headers(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::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: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let connector_payment_id = req
            .request
            .connector_transaction_id
            .get_connector_transaction_id()
            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
        Ok(format!(
            "{}/transfers/{}",
            self.base_url(connectors),
            connector_payment_id.clone()
        ))
    }

    fn build_request(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

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

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

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Dwolla {
    //Not implemented for Dwolla
}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Dwolla {
    //Not implemented for Dwolla
}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Dwolla {
    fn get_headers(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::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: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/transfers", self.base_url(connectors)))
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount_in_minor_unit = MinorUnit::new(req.request.refund_amount);
        let amount = convert_amount(
            self.amount_converter,
            amount_in_minor_unit,
            req.request.currency,
        )?;
        let connector_router_data =
            dwolla::DwollaRouterData::try_from((amount, req, self.base_url(connectors)))?;
        let connector_req = dwolla::DwollaRefundsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &RefundsRouterData<Execute>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
        let headers = res
            .headers
            .as_ref()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;
        let location = get_http_header("Location", headers)
            .change_context(errors::ConnectorError::ResponseHandlingFailed)?;

        let refund_id = location
            .split('/')
            .next_back()
            .ok_or(errors::ConnectorError::ResponseHandlingFailed)?
            .to_string();

        let response = serde_json::json!({"refund_id : ": refund_id.clone()});

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

        Ok(RouterData {
            response: Ok(RefundsResponseData {
                connector_refund_id: refund_id.clone(),
                refund_status: enums::RefundStatus::Pending,
            }),
            ..data.clone()
        })
    }

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

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Dwolla {
    fn get_headers(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::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: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let connector_refund_id = req.request.get_connector_refund_id()?;
        Ok(format!(
            "{}/transfers/{}",
            self.base_url(connectors),
            connector_refund_id.clone()
        ))
    }

    fn build_request(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

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

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

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Dwolla {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
        Ok(Box::new(crypto::HmacSha256))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let sig = request
            .headers
            .get("X-Request-Signature-SHA-256")
            .and_then(|hv| hv.to_str().ok())
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?;
        hex::decode(sig).map_err(|_| errors::ConnectorError::WebhookSignatureNotFound.into())
    }

    async fn verify_webhook_source(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
        _connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
        connector_name: &str,
    ) -> CustomResult<bool, errors::ConnectorError> {
        let connector_webhook_secrets = self
            .get_webhook_source_verification_merchant_secret(
                merchant_id,
                connector_name,
                connector_webhook_details,
            )
            .await?;

        let signature = self
            .get_webhook_source_verification_signature(request, &connector_webhook_secrets)
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;

        let secret_bytes = connector_webhook_secrets.secret.as_ref();
        let key = hmac::Key::new(hmac::HMAC_SHA256, secret_bytes);

        let verify = hmac::verify(&key, request.body, &signature)
            .map(|_| true)
            .map_err(|_| errors::ConnectorError::WebhookSourceVerificationFailed)?;
        Ok(verify)
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let details: dwolla::DwollaWebhookDetails = request
            .body
            .parse_struct("DwollaWebhookDetails")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;

        if let Some(correlation_id) = &details.correlation_id {
            if correlation_id.starts_with("refund_") {
                Ok(api_models::webhooks::ObjectReferenceId::RefundId(
                    api_models::webhooks::RefundIdType::ConnectorRefundId(
                        details.resource_id.clone(),
                    ),
                ))
            } else if correlation_id.starts_with("payment_") {
                Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::ConnectorTransactionId(
                        details.resource_id.clone(),
                    ),
                ))
            } else {
                Err(report!(errors::ConnectorError::WebhookReferenceIdNotFound))
            }
        } else {
            Err(report!(errors::ConnectorError::WebhookReferenceIdNotFound))
        }
    }

    fn get_webhook_event_type(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        let details: dwolla::DwollaWebhookDetails = request
            .body
            .parse_struct("DwollaWebhookDetails")
            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;

        let incoming = api_models::webhooks::IncomingWebhookEvent::try_from(details)?;
        Ok(incoming)
    }

    fn get_webhook_resource_object(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let details: dwolla::DwollaWebhookDetails = request
            .body
            .parse_struct("DwollaWebhookDetails")
            .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
        Ok(Box::new(details))
    }
}

static DWOLLA_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> = LazyLock::new(|| {
    let supported_capture_methods = vec![
        enums::CaptureMethod::Automatic,
        enums::CaptureMethod::SequentialAutomatic,
    ];

    let mut dwolla_supported_payment_methods = SupportedPaymentMethods::new();

    dwolla_supported_payment_methods.add(
        enums::PaymentMethod::BankDebit,
        enums::PaymentMethodType::Ach,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods: supported_capture_methods.clone(),
            specific_features: None,
        },
    );

    dwolla_supported_payment_methods
});

static DWOLLA_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Dwolla",
    description: "Dwolla is a multinational financial technology company offering financial services and software as a service (SaaS)",
    connector_type: enums::HyperswitchConnectorCategory::PaymentGateway,
    integration_status: enums::ConnectorIntegrationStatus::Beta,
};

static DWOLLA_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 2] =
    [enums::EventClass::Payments, enums::EventClass::Refunds];

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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*DWOLLA_SUPPORTED_PAYMENT_METHODS)
    }

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