File: crates/router/tests/connectors/wise.rs

use hyperswitch_domain_models::address::{Address, AddressDetails};
use masking::Secret;
use router::{
    types,
    types::{api, storage::enums, PaymentAddress},
};

use crate::{
    connector_auth,
    utils::{self, ConnectorActions, PaymentInfo},
};

struct WiseTest;

impl ConnectorActions for WiseTest {}

impl utils::Connector for WiseTest {
    fn get_data(&self) -> api::ConnectorData {
        use router::connector::Adyen;
        utils::construct_connector_data_old(
            Box::new(Adyen::new()),
            types::Connector::Adyen,
            api::GetToken::Connector,
            None,
        )
    }

    fn get_payout_data(&self) -> Option<api::ConnectorData> {
        use router::connector::Wise;
        Some(utils::construct_connector_data_old(
            Box::new(Wise::new()),
            types::Connector::Wise,
            api::GetToken::Connector,
            None,
        ))
    }

    fn get_auth_token(&self) -> types::ConnectorAuthType {
        utils::to_connector_auth_type(
            connector_auth::ConnectorAuthentication::new()
                .wise
                .expect("Missing connector authentication configuration")
                .into(),
        )
    }

    fn get_name(&self) -> String {
        "wise".to_string()
    }
}

impl WiseTest {
    fn get_payout_info() -> Option<PaymentInfo> {
        Some(PaymentInfo {
            currency: Some(enums::Currency::GBP),
            address: Some(PaymentAddress::new(
                None,
                Some(Address {
                    address: Some(AddressDetails {
                        country: Some(api_models::enums::CountryAlpha2::GB),
                        city: Some("London".to_string()),
                        zip: Some(Secret::new("10025".to_string())),
                        line1: Some(Secret::new("50 Branson Ave".to_string())),
                        ..Default::default()
                    }),
                    phone: None,
                    email: None,
                }),
                None,
                None,
            )),
            payout_method_data: Some(api::PayoutMethodData::Bank(api::payouts::BankPayout::Bacs(
                api::BacsBankTransfer {
                    bank_sort_code: "231470".to_string().into(),
                    bank_account_number: "28821822".to_string().into(),
                    bank_name: Some("Deutsche Bank".to_string()),
                    bank_country_code: Some(enums::CountryAlpha2::NL),
                    bank_city: Some("Amsterdam".to_string()),
                },
            ))),
            ..Default::default()
        })
    }
}

static CONNECTOR: WiseTest = WiseTest {};

/******************** Payouts test cases ********************/
// Creates a recipient at connector's end

#[actix_web::test]
async fn should_create_payout_recipient() {
    let payout_type = enums::PayoutType::Bank;
    let payment_info = WiseTest::get_payout_info();
    let response = CONNECTOR
        .create_payout_recipient(payout_type, payment_info)
        .await
        .expect("Payout recipient creation response");
    assert_eq!(
        response.status.unwrap(),
        enums::PayoutStatus::RequiresCreation
    );
}

// Create BACS payout

#[actix_web::test]
async fn should_create_bacs_payout() {
    let payout_type = enums::PayoutType::Bank;
    let payout_info = WiseTest::get_payout_info();
    // Create recipient
    let recipient_res = CONNECTOR
        .create_payout_recipient(payout_type.to_owned(), payout_info.to_owned())
        .await
        .expect("Payout recipient response");
    assert_eq!(
        recipient_res.status.unwrap(),
        enums::PayoutStatus::RequiresCreation
    );

    // Create payout
    let create_res: types::PayoutsResponseData = CONNECTOR
        .create_payout(recipient_res.connector_payout_id, payout_type, payout_info)
        .await
        .expect("Payout bank creation response");
    assert_eq!(
        create_res.status.unwrap(),
        enums::PayoutStatus::RequiresFulfillment
    );
}

// Create and fulfill BACS payout

#[actix_web::test]
async fn should_create_and_fulfill_bacs_payout() {
    let payout_type = enums::PayoutType::Bank;
    let payout_info = WiseTest::get_payout_info();
    // Create recipient
    let recipient_res = CONNECTOR
        .create_payout_recipient(payout_type.to_owned(), payout_info.to_owned())
        .await
        .expect("Payout recipient response");
    assert_eq!(
        recipient_res.status.unwrap(),
        enums::PayoutStatus::RequiresCreation
    );
    let response = CONNECTOR
        .create_and_fulfill_payout(recipient_res.connector_payout_id, payout_type, payout_info)
        .await
        .expect("Payout bank creation and fulfill response");
    assert_eq!(response.status.unwrap(), enums::PayoutStatus::Success);
}


pub struct OrderRequestAmount {
    pub currency_code: storage_enums::Currency,
    pub value: StringMajorUnit,
    pub breakdown: AmountBreakdown,
}


File: crates/common_utils/src/macros.rs
 Public functions: 4

//! Utility macros

#[allow(missing_docs)]
#[macro_export]
macro_rules! newtype_impl {
    ($is_pub:vis, $name:ident, $ty_path:path) => {
        impl core::ops::Deref for $name {
            type Target = $ty_path;

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

        impl core::ops::DerefMut for $name {
            fn deref_mut(&mut self) -> &mut Self::Target {
                &mut self.0
            }
        }

        impl From<$ty_path> for $name {
            fn from(ty: $ty_path) -> Self {
                Self(ty)
            }
        }

        impl $name {
            pub fn into_inner(self) -> $ty_path {
                self.0
            }
        }
    };
}

#[allow(missing_docs)]
#[macro_export]
macro_rules! newtype {
    ($is_pub:vis $name:ident = $ty_path:path) => {
        $is_pub struct $name(pub $ty_path);

        $crate::newtype_impl!($is_pub, $name, $ty_path);
    };

    ($is_pub:vis $name:ident = $ty_path:path, derives = ($($trt:path),*)) => {
        #[derive($($trt),*)]
        $is_pub struct $name(pub $ty_path);

        $crate::newtype_impl!($is_pub, $name, $ty_path);
    };
}

/// Use this to ensure that the corresponding
/// openapi route has been implemented in the openapi crate
#[macro_export]
macro_rules! openapi_route {
    ($route_name: ident) => {{
        #[cfg(feature = "openapi")]
        use openapi::routes::$route_name as _;

        $route_name
    }};
}

#[allow(missing_docs)]
#[macro_export]
macro_rules! fallback_reverse_lookup_not_found {
    ($a:expr,$b:expr) => {
        match $a {
            Ok(res) => res,
            Err(err) => {
                router_env::logger::error!(reverse_lookup_fallback = ?err);
                match err.current_context() {
                    errors::StorageError::ValueNotFound(_) => return $b,
                    errors::StorageError::DatabaseError(data_err) => {
                        match data_err.current_context() {
                            diesel_models::errors::DatabaseError::NotFound => return $b,
                            _ => return Err(err)
                        }
                    }
                    _=> return Err(err)
                }
            }
        };
    };
}

/// Collects names of all optional fields that are `None`.
/// This is typically useful for constructing error messages including a list of all missing fields.
#[macro_export]
macro_rules! collect_missing_value_keys {
    [$(($key:literal, $option:expr)),+] => {
        {
            let mut keys: Vec<&'static str> = Vec::new();
            $(
                if $option.is_none() {
                    keys.push($key);
                }
            )*
            keys
        }
    };
}

/// Implements the `ToSql` and `FromSql` traits on a type to allow it to be serialized/deserialized
/// to/from JSON data in the database.
#[macro_export]
macro_rules! impl_to_sql_from_sql_json {
    ($type:ty, $diesel_type:ty) => {
        #[allow(unused_qualifications)]
        impl diesel::serialize::ToSql<$diesel_type, diesel::pg::Pg> for $type {
            fn to_sql<'b>(
                &'b self,
                out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>,
            ) -> diesel::serialize::Result {
                let value = serde_json::to_value(self)?;

                // the function `reborrow` only works in case of `Pg` backend. But, in case of other backends
                // please refer to the diesel migration blog:
                // https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations
                <serde_json::Value as diesel::serialize::ToSql<
                                                                $diesel_type,
                                                                diesel::pg::Pg,
                                                            >>::to_sql(&value, &mut out.reborrow())
            }
        }

        #[allow(unused_qualifications)]
        impl diesel::deserialize::FromSql<$diesel_type, diesel::pg::Pg> for $type {
            fn from_sql(
                bytes: <diesel::pg::Pg as diesel::backend::Backend>::RawValue<'_>,
            ) -> diesel::deserialize::Result<Self> {
                let value = <serde_json::Value as diesel::deserialize::FromSql<
                    $diesel_type,
                    diesel::pg::Pg,
                >>::from_sql(bytes)?;
                Ok(serde_json::from_value(value)?)
            }
        }
    };
    ($type: ty) => {
        $crate::impl_to_sql_from_sql_json!($type, diesel::sql_types::Json);
        $crate::impl_to_sql_from_sql_json!($type, diesel::sql_types::Jsonb);
    };
}

mod id_type {
    /// Defines an ID type.
    #[macro_export]
    macro_rules! id_type {
        ($type:ident, $doc:literal, $diesel_type:ty, $max_length:expr, $min_length:expr) => {
            #[doc = $doc]
            #[derive(
                Clone,
                Hash,
                PartialEq,
                Eq,
                serde::Serialize,
                serde::Deserialize,
                diesel::expression::AsExpression,
                utoipa::ToSchema,
            )]
            #[diesel(sql_type = $diesel_type)]
            #[schema(value_type = String)]
            pub struct $type($crate::id_type::LengthId<$max_length, $min_length>);
        };
        ($type:ident, $doc:literal) => {
            $crate::id_type!(
                $type,
                $doc,
                diesel::sql_types::Text,
                { $crate::consts::MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH },
                { $crate::consts::MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH }
            );
        };
    }

    /// Defines a Global Id type
    #[cfg(feature = "v2")]
    #[macro_export]
    macro_rules! global_id_type {
        ($type:ident, $doc:literal) => {
            #[doc = $doc]
            #[derive(
                Debug,
                Clone,
                Hash,
                PartialEq,
                Eq,
                serde::Serialize,
                serde::Deserialize,
                diesel::expression::AsExpression,
            )]
            #[diesel(sql_type = diesel::sql_types::Text)]
            pub struct $type($crate::id_type::global_id::GlobalId);
        };
    }

    /// Implements common methods on the specified ID type.
    #[macro_export]
    macro_rules! impl_id_type_methods {
        ($type:ty, $field_name:literal) => {
            impl $type {
                /// Get the string representation of the ID type.
                pub fn get_string_repr(&self) -> &str {
                    &self.0 .0 .0
                }
            }
        };
    }

    /// Implements the `Debug` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_debug_id_type {
        ($type:ty) => {
            impl core::fmt::Debug for $type {
                fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                    f.debug_tuple(stringify!($type))
                        .field(&self.0 .0 .0)
                        .finish()
                }
            }
        };
    }

    /// Implements the `TryFrom<Cow<'static, str>>` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_try_from_cow_str_id_type {
        ($type:ty, $field_name:literal) => {
            impl TryFrom<std::borrow::Cow<'static, str>> for $type {
                type Error = error_stack::Report<$crate::errors::ValidationError>;

                fn try_from(value: std::borrow::Cow<'static, str>) -> Result<Self, Self::Error> {
                    use error_stack::ResultExt;

                    let merchant_ref_id = $crate::id_type::LengthId::from(value).change_context(
                        $crate::errors::ValidationError::IncorrectValueProvided {
                            field_name: $field_name,
                        },
                    )?;

                    Ok(Self(merchant_ref_id))
                }
            }
        };
    }

    /// Implements the `Default` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_default_id_type {
        ($type:ty, $prefix:literal) => {
            impl Default for $type {
                fn default() -> Self {
                    Self($crate::generate_ref_id_with_default_length($prefix))
                }
            }
        };
    }

    /// Implements the `GenerateId` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_generate_id_id_type {
        ($type:ty, $prefix:literal) => {
            impl $crate::id_type::GenerateId for $type {
                fn generate() -> Self {
                    Self($crate::generate_ref_id_with_default_length($prefix))
                }
            }
        };
    }

    /// Implements the `SerializableSecret` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_serializable_secret_id_type {
        ($type:ty) => {
            impl masking::SerializableSecret for $type {}
        };
    }

    /// Implements the `ToSql` and `FromSql` traits on the specified ID type.
    #[macro_export]
    macro_rules! impl_to_sql_from_sql_id_type {
        ($type:ty, $diesel_type:ty, $max_length:expr, $min_length:expr) => {
            impl<DB> diesel::serialize::ToSql<$diesel_type, DB> for $type
            where
                DB: diesel::backend::Backend,
                $crate::id_type::LengthId<$max_length, $min_length>:
                    diesel::serialize::ToSql<$diesel_type, DB>,
            {
                fn to_sql<'b>(
                    &'b self,
                    out: &mut diesel::serialize::Output<'b, '_, DB>,
                ) -> diesel::serialize::Result {
                    self.0.to_sql(out)
                }
            }

            impl<DB> diesel::deserialize::FromSql<$diesel_type, DB> for $type
            where
                DB: diesel::backend::Backend,
                $crate::id_type::LengthId<$max_length, $min_length>:
                    diesel::deserialize::FromSql<$diesel_type, DB>,
            {
                fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
                    $crate::id_type::LengthId::<$max_length, $min_length>::from_sql(value).map(Self)
                }
            }
        };
        ($type:ty) => {
            $crate::impl_to_sql_from_sql_id_type!(
                $type,
                diesel::sql_types::Text,
                { $crate::consts::MAX_ALLOWED_MERCHANT_REFERENCE_ID_LENGTH },
                { $crate::consts::MIN_REQUIRED_MERCHANT_REFERENCE_ID_LENGTH }
            );
        };
    }

    #[cfg(feature = "v2")]
    /// Implements the `ToSql` and `FromSql` traits on the specified Global ID type.
    #[macro_export]
    macro_rules! impl_to_sql_from_sql_global_id_type {
        ($type:ty, $diesel_type:ty) => {
            impl<DB> diesel::serialize::ToSql<$diesel_type, DB> for $type
            where
                DB: diesel::backend::Backend,
                $crate::id_type::global_id::GlobalId: diesel::serialize::ToSql<$diesel_type, DB>,
            {
                fn to_sql<'b>(
                    &'b self,
                    out: &mut diesel::serialize::Output<'b, '_, DB>,
                ) -> diesel::serialize::Result {
                    self.0.to_sql(out)
                }
            }

            impl<DB> diesel::deserialize::FromSql<$diesel_type, DB> for $type
            where
                DB: diesel::backend::Backend,
                $crate::id_type::global_id::GlobalId:
                    diesel::deserialize::FromSql<$diesel_type, DB>,
            {
                fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result<Self> {
                    $crate::id_type::global_id::GlobalId::from_sql(value).map(Self)
                }
            }
        };
        ($type:ty) => {
            $crate::impl_to_sql_from_sql_global_id_type!($type, diesel::sql_types::Text);
        };
    }

    /// Implements the `Queryable` trait on the specified ID type.
    #[macro_export]
    macro_rules! impl_queryable_id_type {
        ($type:ty, $diesel_type:ty) => {
            impl<DB> diesel::Queryable<$diesel_type, DB> for $type
            where
                DB: diesel::backend::Backend,
                Self: diesel::deserialize::FromSql<$diesel_type, DB>,
            {
                type Row = Self;

                fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
                    Ok(row)
                }
            }
        };
        ($type:ty) => {
            $crate::impl_queryable_id_type!($type, diesel::sql_types::Text);
        };
    }
}

/// Create new generic list wrapper
#[macro_export]
macro_rules! create_list_wrapper {
    (
        $wrapper_name:ident,
        $type_name: ty,
        impl_functions: {
            $($function_def: tt)*
        }
    ) => {
        #[derive(Clone, Debug)]
        pub struct $wrapper_name(Vec<$type_name>);
        impl $wrapper_name {
            pub fn new(list: Vec<$type_name>) -> Self {
                Self(list)
            }
            pub fn with_capacity(size: usize) -> Self {
                Self(Vec::with_capacity(size))
            }
            $($function_def)*
        }
        impl std::ops::Deref for $wrapper_name {
            type Target = Vec<$type_name>;
            fn deref(&self) -> &<Self as std::ops::Deref>::Target {
                &self.0
            }
        }
        impl std::ops::DerefMut for $wrapper_name {
            fn deref_mut(&mut self) -> &mut Self::Target {
                &mut self.0
            }
        }
        impl IntoIterator for $wrapper_name {
            type Item = $type_name;
            type IntoIter = std::vec::IntoIter<$type_name>;
            fn into_iter(self) -> Self::IntoIter {
                self.0.into_iter()
            }
        }

        impl<'a> IntoIterator for &'a $wrapper_name {
            type Item = &'a $type_name;
            type IntoIter = std::slice::Iter<'a, $type_name>;
            fn into_iter(self) -> Self::IntoIter {
                self.0.iter()
            }
        }

        impl FromIterator<$type_name> for $wrapper_name {
            fn from_iter<T: IntoIterator<Item = $type_name>>(iter: T) -> Self {
                Self(iter.into_iter().collect())
            }
        }
    };
}

/// Get the type name for a type
#[macro_export]
macro_rules! type_name {
    ($type:ty) => {
        std::any::type_name::<$type>()
            .rsplit("::")
            .nth(1)
            .unwrap_or_default();
    };
}

/// **Note** Creates an enum wrapper that implements `FromStr`, `Display`, `Serialize`, and `Deserialize`
/// based on a specific string representation format: `"VariantName<delimiter>FieldValue"`.
/// It handles parsing errors by returning a dedicated `Invalid` variant.
/// *Note*: The macro adds `Invalid,` automatically.
///
/// # Use Case
///
/// This macro is designed for scenarios where you need an enum, with each variant
/// holding a single piece of associated data, to be easily convertible to and from
/// a simple string format. This is useful for cases where enum is serialized to key value pairs
///
/// It avoids more complex serialization structures (like JSON objects `{"VariantName": value}`)
/// in favor of a plain string representation.
///
/// # Input Enum Format and Constraints
///
/// To use this macro, the enum definition must adhere to the following structure:
///
/// 1.  **Public Enum:** The enum must be declared as `pub enum EnumName { ... }`.
/// 2.  **Struct Variants Only:** All variants must be struct variants (using `{}`).
/// 3.  **Exactly One Field:** Each struct variant must contain *exactly one* named field.
///     * **Valid:** `VariantA { value: i32 }`
///     * **Invalid:** `VariantA(i32)` (tuple variant)
///     * **Invalid:** `VariantA` or `VariantA {}` (no field)
///     * **Invalid:** `VariantA { value: i32, other: bool }` (multiple fields)
/// 4.  **Tag Delimiter:** The macro invocation must specify a `tag_delimiter` literal,
///     which is the character used to separate the variant name from the field data in
///     the string representation (e.g., `tag_delimiter = ":",`).
/// 5.  **Field Type Requirements:** The type of the single field in each variant (`$field_ty`)
///     must implement:
///     * `core::str::FromStr`: To parse the field's data from the string part.
///       The `Err` type should ideally be convertible to a meaningful error, though the
///       macro currently uses a generic error message upon failure.
///     * `core::fmt::Display`: To convert the field's data into the string part.
///     * `serde::Serialize` and `serde::Deserialize<'de>`: Although the macro implements
///       custom `Serialize`/`Deserialize` for the *enum* using the string format, the field
///       type itself must satisfy these bounds if required elsewhere or by generic contexts.
///       The macro's implementations rely solely on `Display` and `FromStr` for the conversion.
/// 6.  **Error Type:** This macro uses `core::convert::Infallible` as it never fails but gives
///     `Self::Invalid` variant.
///
/// # Serialization and Deserialization (`serde`)
///
/// When `serde` features are enabled and the necessary traits are derived or implemented,
/// this macro implements `Serialize` and `Deserialize` for the enum:
///
/// **Serialization:** An enum value like `MyEnum::VariantA { value: 123 }` (with `tag_delimiter = ":",`)
///     will be serialized into the string `"VariantA:123"`. If serializing to JSON, this results
///     in a JSON string: `"\"VariantA:123\""`.
/// **Deserialization:** The macro expects a string matching the format `"VariantName<delimiter>FieldValue"`.
///     It uses the enum's `FromStr` implementation internally. When deserializing from JSON, it
///     expects a JSON string containing the correctly formatted value (e.g., `"\"VariantA:123\""`).
///
/// # `Display` and `FromStr`
///
/// **`Display`:** Formats valid variants to `"VariantName<delimiter>FieldValue"` and catch-all cases to `"Invalid"`.
/// **`FromStr`:** Parses `"VariantName<delimiter>FieldValue"` to the variant, or returns `Self::Invalid`
///   if the input string is malformed or `"Invalid"`.
///
/// # Example
///
/// ```rust
/// use std::str::FromStr;
///
/// crate::impl_enum_str!(
///     tag_delimiter = ":",
///     #[derive(Debug, PartialEq, Clone)] // Add other derives as needed
///     pub enum Setting {
///         Timeout { duration_ms: u32 },
///         Username { name: String },
///     }
/// );
/// // Note: The macro adds `Invalid,` automatically.
///
/// fn main() {
///     // Display
///     let setting1 = Setting::Timeout { duration_ms: 5000 };
///     assert_eq!(setting1.to_string(), "Timeout:5000");
///     assert_eq!(Setting::Invalid.to_string(), "Invalid");
///
///     // FromStr (returns Self, not Result)
///     let parsed_setting: Setting = "Username:admin".parse().expect("Valid parse"); // parse() itself doesn't panic
///     assert_eq!(parsed_setting, Setting::Username { name: "admin".to_string() });
///
///     let invalid_format: Setting = "Timeout".parse().expect("Parse always returns Self");
///     assert_eq!(invalid_format, Setting::Invalid); // Malformed input yields Invalid
///
///     let bad_data: Setting = "Timeout:fast".parse().expect("Parse always returns Self");
///     assert_eq!(bad_data, Setting::Invalid); // Bad field data yields Invalid
///
///     let unknown_tag: Setting = "Unknown:abc".parse().expect("Parse always returns Self");
///     assert_eq!(unknown_tag, Setting::Invalid); // Unknown tag yields Invalid
///
///     let explicit_invalid: Setting = "Invalid".parse().expect("Parse always returns Self");
///     assert_eq!(explicit_invalid, Setting::Invalid); // "Invalid" string yields Invalid
///
///     // Serde (requires derive Serialize/Deserialize on Setting)
///     // let json_output = serde_json::to_string(&setting1).unwrap();
///     // assert_eq!(json_output, "\"Timeout:5000\"");
///     // let invalid_json_output = serde_json::to_string(&Setting::Invalid).unwrap();
///     // assert_eq!(invalid_json_output, "\"Invalid\"");
///
///     // let deserialized: Setting = serde_json::from_str("\"Username:guest\"").unwrap();
///     // assert_eq!(deserialized, Setting::Username { name: "guest".to_string() });
///     // let deserialized_invalid: Setting = serde_json::from_str("\"Invalid\"").unwrap();
///     // assert_eq!(deserialized_invalid, Setting::Invalid);
///     // let deserialized_malformed: Setting = serde_json::from_str("\"TimeoutFast\"").unwrap();
///     // assert_eq!(deserialized_malformed, Setting::Invalid); // Malformed -> Invalid
/// }
///
/// # // Mock macro definition for doctest purposes
/// # #[macro_export] macro_rules! impl_enum_str { ($($tt:tt)*) => { $($tt)* } }
/// ```
#[macro_export]
macro_rules! impl_enum_str {
    (
        tag_delimiter = $tag_delim:literal,
        $(#[$enum_attr:meta])*
        pub enum $enum_name:ident {
            $(
                $(#[$variant_attr:meta])*
                $variant:ident {
                    $(#[$field_attr:meta])*
                    $field:ident : $field_ty:ty $(,)?
                }
            ),* $(,)?
        }
    ) => {
        $(#[$enum_attr])*
        pub enum $enum_name {
            $(
                $(#[$variant_attr])*
                $variant {
                    $(#[$field_attr])*
                    $field : $field_ty
                },
            )*
            /// Represents a parsing failure.
            Invalid, // Automatically add the Invalid variant
        }

        // Implement FromStr - now returns Self, not Result
        impl core::str::FromStr for $enum_name {
            // No associated error type needed
            type Err = core::convert::Infallible; // FromStr requires an Err type, use Infallible

            fn from_str(s: &str) -> Result<Self, Self::Err> {
                // Check for explicit "Invalid" string first
                if s == "Invalid" {
                    return Ok(Self::Invalid);
                }

                let Some((tag, associated_data)) = s.split_once($tag_delim) else {
                    // Missing delimiter -> Invalid
                    return Ok(Self::Invalid);
                };

                let result = match tag {
                    $(
                        stringify!($variant) => {
                            // Try to parse the field data
                            match associated_data.parse::<$field_ty>() {
                                Ok(parsed_field) => {
                                    // Success -> construct the variant
                                     Self::$variant { $field: parsed_field }
                                },
                                Err(_) => {
                                     // Field parse failure -> Invalid
                                     Self::Invalid
                                }
                            }
                        }
                    ),*
                    // Unknown tag -> Invalid
                    _ => Self::Invalid,
                };
                Ok(result) // Always Ok because failure modes return Self::Invalid
            }
        }

        // Implement Serialize
        impl ::serde::Serialize for $enum_name {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: ::serde::Serializer,
            {
                match self {
                    $(
                        Self::$variant { $field } => {
                            let s = format!("{}{}{}", stringify!($variant), $tag_delim, $field);
                            serializer.serialize_str(&s)
                        }
                    )*
                    // Handle Invalid variant
                    Self::Invalid => serializer.serialize_str("Invalid"),
                }
            }
        }

        // Implement Deserialize
        impl<'de> ::serde::Deserialize<'de> for $enum_name {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: ::serde::Deserializer<'de>,
            {
                struct EnumVisitor;

                impl<'de> ::serde::de::Visitor<'de> for EnumVisitor {
                    type Value = $enum_name;

                    fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                        formatter.write_str(concat!("a string like VariantName", $tag_delim, "field_data or 'Invalid'"))
                    }

                    // Leverage the FromStr implementation which now returns Self::Invalid on failure
                    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
                    where
                        E: ::serde::de::Error,
                    {
                        // parse() now returns Result<Self, Infallible>
                        // We unwrap() the Ok because it's infallible.
                       Ok(value.parse::<$enum_name>().unwrap())
                    }

                     fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
                    where
                        E: ::serde::de::Error,
                    {
                        Ok(value.parse::<$enum_name>().unwrap())
                    }
                }

                deserializer.deserialize_str(EnumVisitor)
            }
        }

        // Implement Display
        impl core::fmt::Display for $enum_name {
            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                match self {
                    $(
                        Self::$variant { $field } => {
                            write!(f, "{}{}{}", stringify!($variant), $tag_delim, $field)
                        }
                    )*
                     // Handle Invalid variant
                    Self::Invalid => write!(f, "Invalid"),
                }
            }
        }
    };
}

// --- Tests ---
#[cfg(test)]
mod tests {
    #![allow(clippy::panic, clippy::expect_used)]

    use serde_json::{json, Value as JsonValue};

    use crate::impl_enum_str;

    impl_enum_str!(
        tag_delimiter = ":",
        #[derive(Debug, PartialEq, Clone)]
        pub enum TestEnum {
            VariantA { value: i32 },
            VariantB { text: String },
            VariantC { id: u64 },
            VariantJson { data: JsonValue },
        } // Note: Invalid variant is added automatically by the macro
    );

    #[test]
    fn test_enum_from_str_ok() {
        // Success cases just parse directly
        let parsed_a: TestEnum = "VariantA:42".parse().unwrap(); // Unwrapping Infallible is fine
        assert_eq!(parsed_a, TestEnum::VariantA { value: 42 });

        let parsed_b: TestEnum = "VariantB:hello world".parse().unwrap();
        assert_eq!(
            parsed_b,
            TestEnum::VariantB {
                text: "hello world".to_string()
            }
        );

        let parsed_c: TestEnum = "VariantC:123456789012345".parse().unwrap();
        assert_eq!(
            parsed_c,
            TestEnum::VariantC {
                id: 123456789012345
            }
        );

        let parsed_json: TestEnum = r#"VariantJson:{"ok":true}"#.parse().unwrap();
        assert_eq!(
            parsed_json,
            TestEnum::VariantJson {
                data: json!({"ok": true})
            }
        );
    }

    #[test]
    fn test_enum_from_str_failures_yield_invalid() {
        // Missing delimiter
        let parsed: TestEnum = "VariantA".parse().unwrap();
        assert_eq!(parsed, TestEnum::Invalid);

        // Unknown tag
        let parsed: TestEnum = "UnknownVariant:123".parse().unwrap();
        assert_eq!(parsed, TestEnum::Invalid);

        // Bad field data for i32
        let parsed: TestEnum = "VariantA:not_a_number".parse().unwrap();
        assert_eq!(parsed, TestEnum::Invalid);

        // Bad field data for JsonValue
        let parsed: TestEnum = r#"VariantJson:{"bad_json"#.parse().unwrap();
        assert_eq!(parsed, TestEnum::Invalid);

        // Empty field data for non-string (e.g., i32)
        let parsed: TestEnum = "VariantA:".parse().unwrap();
        assert_eq!(parsed, TestEnum::Invalid);

        // Empty field data for string IS valid for String type
        let parsed_str: TestEnum = "VariantB:".parse().unwrap();
        assert_eq!(
            parsed_str,
            TestEnum::VariantB {
                text: "".to_string()
            }
        );

        // Parsing the literal "Invalid" string
        let parsed_invalid_str: TestEnum = "Invalid".parse().unwrap();
        assert_eq!(parsed_invalid_str, TestEnum::Invalid);
    }

    #[test]
    fn test_enum_display_and_serialize() {
        // Display valid
        let value_a = TestEnum::VariantA { value: 99 };
        assert_eq!(value_a.to_string(), "VariantA:99");
        // Serialize valid
        let json_a = serde_json::to_string(&value_a).expect("Serialize A failed");
        assert_eq!(json_a, "\"VariantA:99\""); // Serializes to JSON string

        // Display Invalid
        let value_invalid = TestEnum::Invalid;
        assert_eq!(value_invalid.to_string(), "Invalid");
        // Serialize Invalid
        let json_invalid = serde_json::to_string(&value_invalid).expect("Serialize Invalid failed");
        assert_eq!(json_invalid, "\"Invalid\""); // Serializes to JSON string "Invalid"
    }

    #[test]
    fn test_enum_deserialize() {
        // Deserialize valid
        let input_a = "\"VariantA:123\"";
        let deserialized_a: TestEnum = serde_json::from_str(input_a).expect("Deserialize A failed");
        assert_eq!(deserialized_a, TestEnum::VariantA { value: 123 });

        // Deserialize explicit "Invalid"
        let input_invalid = "\"Invalid\"";
        let deserialized_invalid: TestEnum =
            serde_json::from_str(input_invalid).expect("Deserialize Invalid failed");
        assert_eq!(deserialized_invalid, TestEnum::Invalid);

        // Deserialize malformed string (according to macro rules) -> Invalid
        let input_malformed = "\"VariantA_no_delimiter\"";
        let deserialized_malformed: TestEnum =
            serde_json::from_str(input_malformed).expect("Deserialize malformed should succeed");
        assert_eq!(deserialized_malformed, TestEnum::Invalid);

        // Deserialize string with bad field data -> Invalid
        let input_bad_data = "\"VariantA:not_a_number\"";
        let deserialized_bad_data: TestEnum =
            serde_json::from_str(input_bad_data).expect("Deserialize bad data should succeed");
        assert_eq!(deserialized_bad_data, TestEnum::Invalid);
    }
}


pub async fn get_sankey_data(
    clickhouse_client: &ClickhouseClient,
    auth: &AuthInfo,
    time_range: &TimeRange,
) -> MetricsResult<Vec<SankeyRow>> {
    let mut query_builder =
        QueryBuilder::<ClickhouseClient>::new(AnalyticsCollection::PaymentIntentSessionized);
    query_builder
        .add_select_column(Aggregate::<String>::Count {
            field: None,
            alias: Some("count"),
        })
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_select_column("status")
        .attach_printable("Error adding select clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_select_column("refunds_status")
        .attach_printable("Error adding select clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_select_column("dispute_status")
        .attach_printable("Error adding select clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_select_column("(attempt_count = 1) as first_attempt")
        .attach_printable("Error adding select clause")
        .change_context(MetricsError::QueryBuildingError)?;

    auth.set_filter_clause(&mut query_builder)
        .change_context(MetricsError::QueryBuildingError)?;

    time_range
        .set_filter_clause(&mut query_builder)
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_group_by_clause("status")
        .attach_printable("Error adding group by clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_group_by_clause("refunds_status")
        .attach_printable("Error adding group by clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_group_by_clause("dispute_status")
        .attach_printable("Error adding group by clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .add_group_by_clause("first_attempt")
        .attach_printable("Error adding group by clause")
        .change_context(MetricsError::QueryBuildingError)?;

    query_builder
        .execute_query::<SankeyRow, _>(clickhouse_client)
        .await
        .change_context(MetricsError::QueryBuildingError)?
        .change_context(MetricsError::QueryExecutionFailure)?
        .into_iter()
        .map(Ok)
        .collect()
}


pub async fn delete_by_merchant_id_payment_method_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_method_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, Self>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_method_id.eq(payment_method_id.to_owned())),
        )
        .await
    }


impl api::RefundSync for Checkbook {}


impl EmailClient for NoEmailClient {
    type RichText = String;
    fn convert_to_rich_text(
        &self,
        intermediate_string: IntermediateString,
    ) -> CustomResult<Self::RichText, EmailError> {
        Ok(intermediate_string.into_inner())
    }

    async fn send_email(
        &self,
        _recipient: pii::Email,
        _subject: String,
        _body: Self::RichText,
        _proxy_url: Option<&String>,
    ) -> EmailResult<()> {
        logger::info!("Email not sent as email support is disabled, please enable any of the supported email clients to send emails");
        Ok(())
    }
}


impl webhooks::IncomingWebhook for Nexixpay {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

impl webhooks::IncomingWebhook for for Nexixpay


impl ProcessTrackerNew {
    #[instrument(skip(conn))]
    pub async fn insert_process(self, conn: &PgPooledConn) -> StorageResult<ProcessTracker> {
        generics::generic_insert(conn, self).await
    }
}


pub struct Authorization {
    pub authorization_id: String,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub payment_id: common_utils::id_type::PaymentId,
    pub amount: MinorUnit,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub modified_at: PrimitiveDateTime,
    pub status: storage_enums::AuthorizationStatus,
    pub error_code: Option<String>,
    pub error_message: Option<String>,
    pub connector_authorization_id: Option<String>,
    pub previously_authorized_amount: MinorUnit,
}


File: crates/api_models/src/locker_migration.rs
 Public structs: 1

#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MigrateCardResponse {
    pub status_message: String,
    pub status_code: String,
    pub customers_moved: usize,
    pub cards_moved: usize,
}


impl ConnectorValidation for Bluesnap {
    fn validate_connector_against_payment_request(
        &self,
        capture_method: Option<enums::CaptureMethod>,
        _payment_method: enums::PaymentMethod,
        _pmt: Option<enums::PaymentMethodType>,
    ) -> CustomResult<(), errors::ConnectorError> {
        let capture_method = capture_method.unwrap_or_default();
        match capture_method {
            enums::CaptureMethod::Automatic
            | enums::CaptureMethod::Manual
            | enums::CaptureMethod::SequentialAutomatic => Ok(()),
            enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(
                construct_not_supported_error_report(capture_method, self.id()),
            ),
        }
    }

    fn validate_psync_reference_id(
        &self,
        data: &PaymentsSyncData,
        is_three_ds: bool,
        status: enums::AttemptStatus,
        connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        // If 3DS payment was triggered, connector will have context about payment in CompleteAuthorizeFlow and thus can't make force_sync
        if is_three_ds && status == enums::AttemptStatus::AuthenticationPending {
            return Err(
                errors::ConnectorError::MissingConnectorRelatedTransactionID {
                    id: "connector_transaction_id".to_string(),
                }
                .into(),
            );
        }
        // if connector_transaction_id is present, psync can be made
        if data
            .connector_transaction_id
            .get_connector_transaction_id()
            .is_ok()
        {
            return Ok(());
        }
        // if merchant_id is present, psync can be made along with attempt_id
        let meta_data: CustomResult<bluesnap::BluesnapConnectorMetaData, errors::ConnectorError> =
            to_connector_meta_from_secret(connector_meta_data.clone());

        meta_data.map(|_| ())
    }
}

impl ConnectorValidation for for Bluesnap


impl DisputeMetricsAccumulator {
    pub fn collect(self) -> DisputeMetricsBucketValue {
        let (challenge_rate, won_rate, lost_rate, total_dispute) =
            self.disputes_status_rate.collect().unwrap_or_default();
        DisputeMetricsBucketValue {
            disputes_challenged: challenge_rate,
            disputes_won: won_rate,
            disputes_lost: lost_rate,
            disputed_amount: self.disputed_amount.collect(),
            dispute_lost_amount: self.dispute_lost_amount.collect(),
            total_dispute,
        }
    }
}


pub fn server(state: AppState) -> Scope {
        let mut route = web::scope("/payment_methods").app_data(web::Data::new(state));
        #[cfg(feature = "olap")]
        {
            route =
                route.service(web::resource("/filter").route(
                    web::get().to(
                        payment_methods::list_countries_currencies_for_connector_payment_method,
                    ),
                ));
        }
        #[cfg(feature = "oltp")]
        {
            route = route
                .service(
                    web::resource("")
                        .route(web::post().to(payment_methods::create_payment_method_api))
                        .route(web::get().to(payment_methods::list_payment_method_api)), // TODO : added for sdk compatibility for now, need to deprecate this later
                )
                .service(
                    web::resource("/migrate")
                        .route(web::post().to(payment_methods::migrate_payment_method_api)),
                )
                .service(
                    web::resource("/migrate-batch")
                        .route(web::post().to(payment_methods::migrate_payment_methods)),
                )
                .service(
                    web::resource("/update-batch")
                        .route(web::post().to(payment_methods::update_payment_methods)),
                )
                .service(
                    web::resource("/tokenize-card")
                        .route(web::post().to(payment_methods::tokenize_card_api)),
                )
                .service(
                    web::resource("/tokenize-card-batch")
                        .route(web::post().to(payment_methods::tokenize_card_batch_api)),
                )
                .service(
                    web::resource("/collect")
                        .route(web::post().to(payment_methods::initiate_pm_collect_link_flow)),
                )
                .service(
                    web::resource("/collect/{merchant_id}/{collect_id}")
                        .route(web::get().to(payment_methods::render_pm_collect_link)),
                )
                .service(
                    web::resource("/{payment_method_id}")
                        .route(web::get().to(payment_methods::payment_method_retrieve_api))
                        .route(web::delete().to(payment_methods::payment_method_delete_api)),
                )
                .service(
                    web::resource("/{payment_method_id}/tokenize-card")
                        .route(web::post().to(payment_methods::tokenize_card_using_pm_api)),
                )
                .service(
                    web::resource("/{payment_method_id}/update")
                        .route(web::post().to(payment_methods::payment_method_update_api)),
                )
                .service(
                    web::resource("/{payment_method_id}/save")
                        .route(web::post().to(payment_methods::save_payment_method_api)),
                )
                .service(
                    web::resource("/auth/link").route(web::post().to(pm_auth::link_token_create)),
                )
                .service(
                    web::resource("/auth/exchange").route(web::post().to(pm_auth::exchange_token)),
                )
        }
        route
    }


            Option<storage_enums::PaymentMethodType>,
        ),
    ) -> Result<Self, Self::Error> {
        let is_manual_capture = is_manual_capture(capture_method);
        let (status, error, payment_response_data) = match item.response {
            AdyenPaymentResponse::Response(response) => {
                get_adyen_response(*response, is_manual_capture, item.http_code, pmt)?
            }
            AdyenPaymentResponse::PresentToShopper(response) => {
                get_present_to_shopper_response(*response, is_manual_capture, item.http_code, pmt)?
            }
            AdyenPaymentResponse::QrCodeResponse(response) => {
                get_qr_code_response(*response, is_manual_capture, item.http_code, pmt)?
            }
            AdyenPaymentResponse::RedirectionResponse(response) => {
                get_redirection_response(*response, is_manual_capture, item.http_code, pmt)?
            }
            AdyenPaymentResponse::RedirectionErrorResponse(response) => {
                get_redirection_error_response(*response, is_manual_capture, item.http_code, pmt)?
            }
            AdyenPaymentResponse::WebhookResponse(response) => get_webhook_response(
                *response,
                is_manual_capture,
                is_multiple_capture_psync_flow,
                item.http_code,
            )?,
        };

        Ok(Self {
            status,
            response: error.map_or_else(|| Ok(payment_response_data), Err),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenCaptureRequest {
    merchant_account: Secret<String>,
    amount: Amount,
    reference: String,
}

impl TryFrom<&AdyenRouterData<&PaymentsCaptureRouterData>> for AdyenCaptureRequest {
    type Error = Error;
    fn try_from(item: &AdyenRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
        let reference = match item.router_data.request.multiple_capture_data.clone() {
            // if multiple capture request, send capture_id as our reference for the capture
            Some(multiple_capture_request_data) => multiple_capture_request_data.capture_reference,
            // if single capture request, send connector_request_reference_id(attempt_id)
            None => item.router_data.connector_request_reference_id.clone(),
        };
        Ok(Self {
            merchant_account: auth_type.merchant_account,
            reference,
            amount: Amount {
                currency: item.router_data.request.currency,
                value: item.amount.to_owned(),
            },
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenCaptureResponse {
    merchant_account: Secret<String>,
    payment_psp_reference: String,
    psp_reference: String,
    reference: String,
    status: String,
    amount: Amount,
    merchant_reference: Option<String>,
    store: Option<String>,
    splits: Option<Vec<AdyenSplitData>>,
}

impl TryFrom<PaymentsCaptureResponseRouterData<AdyenCaptureResponse>>
    for PaymentsCaptureRouterData
{
    type Error = Error;
    fn try_from(
        item: PaymentsCaptureResponseRouterData<AdyenCaptureResponse>,
    ) -> Result<Self, Self::Error> {
        let connector_transaction_id = if item.data.request.multiple_capture_data.is_some() {
            item.response.psp_reference.clone()
        } else {
            item.response.payment_psp_reference
        };
        let charges = match &item.response.splits {
            Some(split_items) => Some(construct_charge_response(item.response.store, split_items)),
            None => None,
        };

        Ok(Self {
            // From the docs, the only value returned is "received", outcome of refund is available
            // through refund notification webhook
            // For more info: https://docs.adyen.com/online-payments/capture
            status: storage_enums::AttemptStatus::Pending,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(connector_transaction_id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.reference),
                incremental_authorization_allowed: None,
                charges,
            }),
            amount_captured: Some(0),
            ..item.data
        })
    }
}

fn construct_charge_response(
    store: Option<String>,
    split_item: &[AdyenSplitData],
) -> common_types::payments::ConnectorChargeResponseData {
    let splits: Vec<common_types::domain::AdyenSplitItem> = split_item
        .iter()
        .map(|split_item| common_types::domain::AdyenSplitItem {
            amount: split_item.amount.as_ref().map(|amount| amount.value),
            reference: split_item.reference.clone(),
            split_type: split_item.split_type.clone(),
            account: split_item.account.clone(),
            description: split_item.description.clone(),
        })
        .collect();

    common_types::payments::ConnectorChargeResponseData::AdyenSplitPayment(
        common_types::domain::AdyenSplitData {
            store,
            split_items: splits,
        },
    )
}

// Refund Request Transform
impl<F> TryFrom<&AdyenRouterData<&RefundsRouterData<F>>> for AdyenRefundRequest {
    type Error = Error;
    fn try_from(item: &AdyenRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
        let (store, splits) = match item
        .router_data
        .request
        .split_refunds
        .as_ref()
        {
                Some(hyperswitch_domain_models::router_request_types::SplitRefundsRequest::AdyenSplitRefund(adyen_split_data)) =>  get_adyen_split_request(adyen_split_data, item.router_data.request.currency),
                _ => (None, None),
        };

        Ok(Self {
            merchant_account: auth_type.merchant_account,
            amount: Amount {
                currency: item.router_data.request.currency,
                value: item.amount,
            },
            merchant_refund_reason: item
                .router_data
                .request
                .reason
                .as_ref()
                .map(|reason| AdyenRefundRequestReason::from_str(reason))
                .transpose()?,
            reference: item.router_data.request.refund_id.clone(),
            store,
            splits,
        })
    }
}

// Refund Response Transform
impl<F> TryFrom<RefundsResponseRouterData<F, AdyenRefundResponse>> for RefundsRouterData<F> {
    type Error = Error;
    fn try_from(
        item: RefundsResponseRouterData<F, AdyenRefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.psp_reference,
                // From the docs, the only value returned is "received", outcome of refund is available
                // through refund notification webhook
                // For more info: https://docs.adyen.com/online-payments/refund
                refund_status: storage_enums::RefundStatus::Pending,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenErrorResponse {
    pub status: i32,
    pub error_code: String,
    pub message: String,
    pub error_type: String,
    pub psp_reference: Option<String>,
}

// #[cfg(test)]
// mod test_adyen_transformers {
//     use super::*;

//     #[test]
//     fn verify_transform_from_router_to_adyen_req() {
//         let router_req = PaymentsRequest {
//             amount: 0.0,
//             currency: "None".to_string(),
//             ..Default::default()
//         };
//         println!("{:#?}", &router_req);
//         let adyen_req = AdyenPaymentRequest::from(router_req);
//         println!("{:#?}", &adyen_req);
//         let adyen_req_json: String = serde_json::to_string(&adyen_req).unwrap();
//         println!("{}", adyen_req_json);
//         assert_eq!(true, true)
//     }
// }

#[derive(Debug, Deserialize)]
pub enum DisputeStatus {
    Undefended,
    Pending,
    Lost,
    Accepted,
    Won,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenAdditionalDataWH {
    pub hmac_signature: Secret<String>,
    pub dispute_status: Option<DisputeStatus>,
    pub chargeback_reason_code: Option<String>,
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pub defense_period_ends_at: Option<PrimitiveDateTime>,
    /// 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")]
    pub recurring_detail_reference: Option<Secret<String>>,
    pub network_tx_reference: Option<Secret<String>>,
    /// [only for cards] Enable raw acquirer from Adyen dashboard to receive this (https://docs.adyen.com/development-resources/raw-acquirer-responses/#search-modal)
    pub refusal_reason_raw: Option<String>,
    /// [only for cards] This is only available for Visa and Mastercard
    pub refusal_code_raw: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct AdyenAmountWH {
    pub value: MinorUnit,
    pub currency: storage_enums::Currency,
}

#[derive(Clone, Debug, Deserialize, Serialize, strum::Display, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum WebhookEventCode {
    Authorisation,
    Refund,
    CancelOrRefund,
    Cancellation,
    Capture,
    CaptureFailed,
    RefundFailed,
    RefundReversed,
    NotificationOfChargeback,
    Chargeback,
    ChargebackReversed,
    SecondChargeback,
    PrearbitrationWon,
    PrearbitrationLost,
    OfferClosed,
    #[cfg(feature = "payouts")]
    PayoutThirdparty,
    #[cfg(feature = "payouts")]
    PayoutDecline,
    #[cfg(feature = "payouts")]
    PayoutExpire,
    #[cfg(feature = "payouts")]
    PayoutReversed,
    #[serde(other)]
    Unknown,
}

pub fn is_transaction_event(event_code: &WebhookEventCode) -> bool {
    matches!(
        event_code,
        WebhookEventCode::Authorisation | WebhookEventCode::OfferClosed
    )
}

pub fn is_capture_or_cancel_event(event_code: &WebhookEventCode) -> bool {
    matches!(
        event_code,
        WebhookEventCode::Capture
            | WebhookEventCode::CaptureFailed
            | WebhookEventCode::Cancellation
    )
}

pub fn is_refund_event(event_code: &WebhookEventCode) -> bool {
    matches!(
        event_code,
        WebhookEventCode::Refund
            | WebhookEventCode::CancelOrRefund
            | WebhookEventCode::RefundFailed
            | WebhookEventCode::RefundReversed
    )
}

pub fn is_chargeback_event(event_code: &WebhookEventCode) -> bool {
    matches!(
        event_code,
        WebhookEventCode::NotificationOfChargeback
            | WebhookEventCode::Chargeback
            | WebhookEventCode::ChargebackReversed
            | WebhookEventCode::SecondChargeback
            | WebhookEventCode::PrearbitrationWon
            | WebhookEventCode::PrearbitrationLost
    )
}

#[cfg(feature = "payouts")]
pub fn is_payout_event(event_code: &WebhookEventCode) -> bool {
    matches!(
        event_code,
        WebhookEventCode::PayoutThirdparty
            | WebhookEventCode::PayoutDecline
            | WebhookEventCode::PayoutExpire
            | WebhookEventCode::PayoutReversed
    )
}

fn is_success_scenario(is_success: String) -> bool {
    is_success.as_str() == "true"
}

pub(crate) fn get_adyen_webhook_event(
    code: WebhookEventCode,
    is_success: String,
    dispute_status: Option<DisputeStatus>,
) -> api_models::webhooks::IncomingWebhookEvent {
    match code {
        WebhookEventCode::Authorisation => {
            if is_success_scenario(is_success) {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess
            } else {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure
            }
        }
        WebhookEventCode::Refund | WebhookEventCode::CancelOrRefund => {
            if is_success_scenario(is_success) {
                api_models::webhooks::IncomingWebhookEvent::RefundSuccess
            } else {
                api_models::webhooks::IncomingWebhookEvent::RefundFailure
            }
        }
        WebhookEventCode::Cancellation => {
            if is_success_scenario(is_success) {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentCancelled
            } else {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentCancelFailure
            }
        }
        WebhookEventCode::RefundFailed | WebhookEventCode::RefundReversed => {
            api_models::webhooks::IncomingWebhookEvent::RefundFailure
        }
        WebhookEventCode::NotificationOfChargeback => {
            api_models::webhooks::IncomingWebhookEvent::DisputeOpened
        }
        WebhookEventCode::Chargeback => match dispute_status {
            Some(DisputeStatus::Won) => api_models::webhooks::IncomingWebhookEvent::DisputeWon,
            Some(DisputeStatus::Lost) | None => {
                api_models::webhooks::IncomingWebhookEvent::DisputeLost
            }
            Some(_) => api_models::webhooks::IncomingWebhookEvent::DisputeOpened,
        },
        WebhookEventCode::ChargebackReversed => match dispute_status {
            Some(DisputeStatus::Pending) => {
                api_models::webhooks::IncomingWebhookEvent::DisputeChallenged
            }
            _ => api_models::webhooks::IncomingWebhookEvent::DisputeWon,
        },
        WebhookEventCode::SecondChargeback => {
            api_models::webhooks::IncomingWebhookEvent::DisputeLost
        }
        WebhookEventCode::PrearbitrationWon => match dispute_status {
            Some(DisputeStatus::Pending) => {
                api_models::webhooks::IncomingWebhookEvent::DisputeOpened
            }
            _ => api_models::webhooks::IncomingWebhookEvent::DisputeWon,
        },
        WebhookEventCode::PrearbitrationLost => {
            api_models::webhooks::IncomingWebhookEvent::DisputeLost
        }
        WebhookEventCode::Capture => {
            if is_success_scenario(is_success) {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentCaptureSuccess
            } else {
                api_models::webhooks::IncomingWebhookEvent::PaymentIntentCaptureFailure
            }
        }
        WebhookEventCode::CaptureFailed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentCaptureFailure
        }
        WebhookEventCode::OfferClosed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentExpired
        }
        #[cfg(feature = "payouts")]
        WebhookEventCode::PayoutThirdparty => {
            api_models::webhooks::IncomingWebhookEvent::PayoutCreated
        }
        #[cfg(feature = "payouts")]
        WebhookEventCode::PayoutDecline => {
            api_models::webhooks::IncomingWebhookEvent::PayoutFailure
        }
        #[cfg(feature = "payouts")]
        WebhookEventCode::PayoutExpire => api_models::webhooks::IncomingWebhookEvent::PayoutExpired,
        #[cfg(feature = "payouts")]
        WebhookEventCode::PayoutReversed => {
            api_models::webhooks::IncomingWebhookEvent::PayoutReversed
        }
        WebhookEventCode::Unknown => api_models::webhooks::IncomingWebhookEvent::EventNotSupported,
    }
}

impl From<WebhookEventCode> for storage_enums::DisputeStage {
    fn from(code: WebhookEventCode) -> Self {
        match code {
            WebhookEventCode::NotificationOfChargeback => Self::PreDispute,
            WebhookEventCode::SecondChargeback => Self::PreArbitration,
            WebhookEventCode::PrearbitrationWon => Self::PreArbitration,
            WebhookEventCode::PrearbitrationLost => Self::PreArbitration,
            _ => Self::Dispute,
        }
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenNotificationRequestItemWH {
    pub additional_data: AdyenAdditionalDataWH,
    pub amount: AdyenAmountWH,
    pub original_reference: Option<String>,
    pub psp_reference: String,
    pub event_code: WebhookEventCode,
    pub merchant_account_code: String,
    pub merchant_reference: String,
    pub success: String,
    pub reason: Option<String>,
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pub event_date: Option<PrimitiveDateTime>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AdyenItemObjectWH {
    pub notification_request_item: AdyenNotificationRequestItemWH,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenIncomingWebhook {
    pub notification_items: Vec<AdyenItemObjectWH>,
}

impl From<AdyenNotificationRequestItemWH> for AdyenWebhookResponse {
    fn from(notif: AdyenNotificationRequestItemWH) -> Self {
        let (refusal_reason, refusal_reason_code) = if !is_success_scenario(notif.success.clone()) {
            (
                notif.reason.or(Some(NO_ERROR_MESSAGE.to_string())),
                Some(NO_ERROR_CODE.to_string()),
            )
        } else {
            (None, None)
        };
        Self {
            transaction_id: notif.psp_reference,
            payment_reference: notif.original_reference,
            //Translating into custom status so that it can be clearly mapped to out attempt_status
            status: match notif.event_code {
                WebhookEventCode::Authorisation => {
                    if is_success_scenario(notif.success) {
                        AdyenWebhookStatus::Authorised
                    } else {
                        AdyenWebhookStatus::AuthorisationFailed
                    }
                }
                WebhookEventCode::Cancellation => {
                    if is_success_scenario(notif.success) {
                        AdyenWebhookStatus::Cancelled
                    } else {
                        AdyenWebhookStatus::CancelFailed
                    }
                }
                WebhookEventCode::OfferClosed => AdyenWebhookStatus::Expired,
                WebhookEventCode::Capture => {
                    if is_success_scenario(notif.success) {
                        AdyenWebhookStatus::Captured
                    } else {
                        AdyenWebhookStatus::CaptureFailed
                    }
                }
                #[cfg(feature = "payouts")]
                WebhookEventCode::PayoutThirdparty => {
                    if is_success_scenario(notif.success) {
                        AdyenWebhookStatus::Authorised
                    } else {
                        AdyenWebhookStatus::AuthorisationFailed
                    }
                }
                #[cfg(feature = "payouts")]
                WebhookEventCode::PayoutDecline => AdyenWebhookStatus::Cancelled,
                #[cfg(feature = "payouts")]
                WebhookEventCode::PayoutExpire => AdyenWebhookStatus::AuthorisationFailed,
                #[cfg(feature = "payouts")]
                WebhookEventCode::PayoutReversed => AdyenWebhookStatus::Reversed,
                WebhookEventCode::CaptureFailed => AdyenWebhookStatus::CaptureFailed,
                WebhookEventCode::CancelOrRefund
                | WebhookEventCode::Refund
                | WebhookEventCode::RefundFailed
                | WebhookEventCode::RefundReversed
                | WebhookEventCode::NotificationOfChargeback
                | WebhookEventCode::Chargeback
                | WebhookEventCode::ChargebackReversed
                | WebhookEventCode::SecondChargeback
                | WebhookEventCode::PrearbitrationWon
                | WebhookEventCode::PrearbitrationLost
                | WebhookEventCode::Unknown => AdyenWebhookStatus::UnexpectedEvent,
            },
            amount: Some(Amount {
                value: notif.amount.value,
                currency: notif.amount.currency,
            }),
            merchant_reference_id: notif.merchant_reference,
            refusal_reason,
            refusal_reason_code,
            event_code: notif.event_code,
            refusal_code_raw: notif.additional_data.refusal_code_raw,
            refusal_reason_raw: notif.additional_data.refusal_reason_raw,
        }
    }
}

//This will be triggered in Psync handler of webhook response
impl utils::MultipleCaptureSyncResponse for AdyenWebhookResponse {
    fn get_connector_capture_id(&self) -> String {
        self.transaction_id.clone()
    }

    fn get_capture_attempt_status(&self) -> storage_enums::AttemptStatus {
        match self.status {
            AdyenWebhookStatus::Captured => storage_enums::AttemptStatus::Charged,
            _ => storage_enums::AttemptStatus::CaptureFailed,
        }
    }

    fn is_capture_response(&self) -> bool {
        matches!(
            self.event_code,
            WebhookEventCode::Capture | WebhookEventCode::CaptureFailed
        )
    }

    fn get_connector_reference_id(&self) -> Option<String> {
        Some(self.merchant_reference_id.clone())
    }

    fn get_amount_captured(&self) -> Result<Option<MinorUnit>, error_stack::Report<ParsingError>> {
        Ok(self.amount.clone().map(|amount| amount.value))
    }
}

// Payouts
#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPayoutCreateRequest {
    amount: Amount,
    recurring: RecurringContract,
    merchant_account: Secret<String>,
    #[serde(flatten)]
    payment_data: PayoutPaymentMethodData,
    reference: String,
    shopper_reference: String,
    shopper_email: Option<Email>,
    shopper_name: ShopperName,
    date_of_birth: Option<Secret<String>>,
    entity_type: Option<storage_enums::PayoutEntityType>,
    nationality: Option<storage_enums::CountryAlpha2>,
    billing_address: Option<Address>,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PayoutPaymentMethodData {
    PayoutBankData(PayoutBankData),
    PayoutWalletData(PayoutWalletData),
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayoutBankData {
    bank: PayoutBankDetails,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayoutWalletData {
    selected_brand: PayoutBrand,
    additional_data: PayoutAdditionalData,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PayoutBrand {
    Paypal,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PayoutAdditionalData {
    token_data_type: PayoutTokenDataType,
    email_id: Email,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
enum PayoutTokenDataType {
    PayPal,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct PayoutBankDetails {
    iban: Secret<String>,
    owner_name: Secret<String>,
    bank_city: Option<String>,
    bank_name: Option<String>,
    bic: Option<Secret<String>>,
    country_code: Option<storage_enums::CountryAlpha2>,
    tax_id: Option<Secret<String>>,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RecurringContract {
    contract: Contract,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
enum Contract {
    Oneclick,
    Recurring,
    Payout,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPayoutResponse {
    psp_reference: String,
    result_code: Option<AdyenStatus>,
    response: Option<AdyenStatus>,
    amount: Option<Amount>,
    merchant_reference: Option<String>,
    refusal_reason: Option<String>,
    refusal_reason_code: Option<String>,
    additional_data: Option<AdditionalData>,
    auth_code: Option<String>,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPayoutEligibilityRequest {
    amount: Amount,
    merchant_account: Secret<String>,
    payment_method: PayoutCardDetails,
    reference: String,
    shopper_reference: String,
}

#[cfg(feature = "payouts")]
#[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PayoutCardDetails {
    #[serde(rename = "type")]
    payment_method_type: String,
    number: CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    holder_name: Secret<String>,
}

#[cfg(feature = "payouts")]
#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum PayoutEligibility {
    #[serde(rename = "Y")]
    Yes,
    #[serde(rename = "N")]
    #[default]
    No,
    #[serde(rename = "D")]
    Domestic,
    #[serde(rename = "U")]
    Unknown,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AdyenPayoutFulfillRequest {
    GenericFulfillRequest(PayoutFulfillGenericRequest),
    Card(Box<PayoutFulfillCardRequest>),
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayoutFulfillGenericRequest {
    merchant_account: Secret<String>,
    original_reference: String,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PayoutFulfillCardRequest {
    amount: Amount,
    card: PayoutCardDetails,
    billing_address: Option<Address>,
    merchant_account: Secret<String>,
    reference: String,
    shopper_name: ShopperName,
    nationality: Option<storage_enums::CountryAlpha2>,
    entity_type: Option<storage_enums::PayoutEntityType>,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPayoutCancelRequest {
    original_reference: String,
    merchant_account: Secret<String>,
}

#[cfg(feature = "payouts")]
impl TryFrom<&PayoutMethodData> for PayoutCardDetails {
    type Error = Error;
    fn try_from(item: &PayoutMethodData) -> Result<Self, Self::Error> {
        match item {
            PayoutMethodData::Card(card) => Ok(Self {
                payment_method_type: "scheme".to_string(), // FIXME: Remove hardcoding
                number: card.card_number.clone(),
                expiry_month: card.expiry_month.clone(),
                expiry_year: card.expiry_year.clone(),
                holder_name: card
                    .card_holder_name
                    .clone()
                    .get_required_value("card_holder_name")
                    .change_context(errors::ConnectorError::MissingRequiredField {
                        field_name: "payout_method_data.card.holder_name",
                    })?,
            }),
            _ => Err(errors::ConnectorError::MissingRequiredField {
                field_name: "payout_method_data.card",
            })?,
        }
    }
}

// Payouts eligibility request transform
#[cfg(feature = "payouts")]
impl<F> TryFrom<&AdyenRouterData<&PayoutsRouterData<F>>> for AdyenPayoutEligibilityRequest {
    type Error = Error;
    fn try_from(item: &AdyenRouterData<&PayoutsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
        let payout_method_data =
            PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?;
        Ok(Self {
            amount: Amount {
                currency: item.router_data.request.destination_currency,
                value: item.amount.to_owned(),
            },
            merchant_account: auth_type.merchant_account,
            payment_method: payout_method_data,
            reference: item.router_data.connector_request_reference_id.clone(),
            shopper_reference: item.router_data.merchant_id.get_string_repr().to_owned(),
        })
    }
}

// Payouts create request transform
#[cfg(feature = "payouts")]
impl<F> TryFrom<&PayoutsRouterData<F>> for AdyenPayoutCancelRequest {
    type Error = Error;
    fn try_from(item: &PayoutsRouterData<F>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;

        let merchant_account = auth_type.merchant_account;
        if let Some(id) = &item.request.connector_payout_id {
            Ok(Self {
                merchant_account,
                original_reference: id.to_string(),
            })
        } else {
            Err(errors::ConnectorError::MissingRequiredField {
                field_name: "connector_payout_id",
            })?
        }
    }
}

// Payouts cancel request transform
#[cfg(feature = "payouts")]
impl<F> TryFrom<&AdyenRouterData<&PayoutsRouterData<F>>> for AdyenPayoutCreateRequest {
    type Error = Error;
    fn try_from(item: &AdyenRouterData<&PayoutsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
        let merchant_account = auth_type.merchant_account;
        let (owner_name, customer_email) = item
            .router_data
            .request
            .customer_details
            .to_owned()
            .map_or((None, None), |c| (c.name, c.email));
        let owner_name = owner_name.get_required_value("owner_name").change_context(
            errors::ConnectorError::MissingRequiredField {
                field_name: "payout_method_data.bank.owner_name",
            },
        )?;

        match item.router_data.get_payout_method_data()? {
            PayoutMethodData::Card(_) => Err(errors::ConnectorError::NotSupported {
                message: "Card payout creation is not supported".to_string(),
                connector: "Adyen",
            })?,
            PayoutMethodData::Bank(bd) => {
                let bank_details = match bd {
                    payouts::Bank::Sepa(b) => PayoutBankDetails {
                        bank_name: b.bank_name,
                        country_code: b.bank_country_code,
                        bank_city: b.bank_city,
                        owner_name,
                        bic: b.bic,
                        iban: b.iban,
                        tax_id: None,
                    },
                    payouts::Bank::Ach(..) => Err(errors::ConnectorError::NotSupported {
                        message: "Bank transfer via ACH is not supported".to_string(),
                        connector: "Adyen",
                    })?,
                    payouts::Bank::Bacs(..) => Err(errors::ConnectorError::NotSupported {
                        message: "Bank transfer via Bacs is not supported".to_string(),
                        connector: "Adyen",
                    })?,
                    payouts::Bank::Pix(..) => Err(errors::ConnectorError::NotSupported {
                        message: "Bank transfer via Pix is not supported".to_string(),
                        connector: "Adyen",
                    })?,
                };
                let bank_data = PayoutBankData { bank: bank_details };
                let address: &hyperswitch_domain_models::address::AddressDetails =
                    item.router_data.get_billing_address()?;
                Ok(Self {
                    amount: Amount {
                        value: item.amount.to_owned(),
                        currency: item.router_data.request.destination_currency,
                    },
                    recurring: RecurringContract {
                        contract: Contract::Payout,
                    },
                    merchant_account,
                    payment_data: PayoutPaymentMethodData::PayoutBankData(bank_data),
                    reference: item.router_data.connector_request_reference_id.to_owned(),
                    shopper_reference: item.router_data.merchant_id.get_string_repr().to_owned(),
                    shopper_email: customer_email,
                    shopper_name: ShopperName {
                        first_name: Some(address.get_first_name()?.to_owned()), // it is a required field for payouts
                        last_name: Some(address.get_last_name()?.to_owned()), // it is a required field for payouts
                    },
                    date_of_birth: None,
                    entity_type: Some(item.router_data.request.entity_type),
                    nationality: get_country_code(item.router_data.get_optional_billing()),
                    billing_address: get_address_info(item.router_data.get_optional_billing())
                        .transpose()?,
                })
            }
            PayoutMethodData::Wallet(wallet_data) => {
                let additional_data = match wallet_data {
                    payouts::Wallet::Paypal(paypal_data) => PayoutAdditionalData {
                        token_data_type: PayoutTokenDataType::PayPal,
                        email_id: paypal_data.email.clone().ok_or(
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "email_address",
                            },
                        )?,
                    },
                    payouts::Wallet::Venmo(_) => Err(errors::ConnectorError::NotSupported {
                        message: "Venmo Wallet is not supported".to_string(),
                        connector: "Adyen",
                    })?,
                };
                let address: &hyperswitch_domain_models::address::AddressDetails =
                    item.router_data.get_billing_address()?;
                let payout_wallet = PayoutWalletData {
                    selected_brand: PayoutBrand::Paypal,
                    additional_data,
                };
                Ok(Self {
                    amount: Amount {
                        value: item.amount.to_owned(),
                        currency: item.router_data.request.destination_currency,
                    },
                    recurring: RecurringContract {
                        contract: Contract::Payout,
                    },
                    merchant_account,
                    payment_data: PayoutPaymentMethodData::PayoutWalletData(payout_wallet),
                    reference: item.router_data.connector_request_reference_id.clone(),
                    shopper_reference: item.router_data.merchant_id.get_string_repr().to_owned(),
                    shopper_email: customer_email,
                    shopper_name: ShopperName {
                        first_name: Some(address.get_first_name()?.to_owned()), // it is a required field for payouts
                        last_name: Some(address.get_last_name()?.to_owned()), // it is a required field for payouts
                    },
                    date_of_birth: None,
                    entity_type: Some(item.router_data.request.entity_type),
                    nationality: get_country_code(item.router_data.get_optional_billing()),
                    billing_address: get_address_info(item.router_data.get_optional_billing())
                        .transpose()?,
                })
            }
        }
    }
}

// Payouts fulfill request transform
#[cfg(feature = "payouts")]
impl<F> TryFrom<&AdyenRouterData<&PayoutsRouterData<F>>> for AdyenPayoutFulfillRequest {
    type Error = Error;
    fn try_from(item: &AdyenRouterData<&PayoutsRouterData<F>>) -> Result<Self, Self::Error> {
        let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
        let payout_type = item.router_data.request.get_payout_type()?;
        let merchant_account = auth_type.merchant_account;
        match payout_type {
            storage_enums::PayoutType::Bank | storage_enums::PayoutType::Wallet => {
                Ok(Self::GenericFulfillRequest(PayoutFulfillGenericRequest {
                    merchant_account,
                    original_reference: item
                        .router_data
                        .request
                        .connector_payout_id
                        .clone()
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "connector_payout_id",
                        })?,
                }))
            }
            storage_enums::PayoutType::Card => {
                let address = item.router_data.get_billing_address()?;
                Ok(Self::Card(Box::new(PayoutFulfillCardRequest {
                    amount: Amount {
                        value: item.amount.to_owned(),
                        currency: item.router_data.request.destination_currency,
                    },
                    card: PayoutCardDetails::try_from(&item.router_data.get_payout_method_data()?)?,
                    billing_address: get_address_info(item.router_data.get_billing().ok())
                        .transpose()?,
                    merchant_account,
                    reference: item.router_data.connector_request_reference_id.clone(),
                    shopper_name: ShopperName {
                        first_name: Some(address.get_first_name()?.to_owned()), // it is a required field for payouts
                        last_name: Some(address.get_last_name()?.to_owned()), // it is a required field for payouts
                    },
                    nationality: get_country_code(item.router_data.get_optional_billing()),
                    entity_type: Some(item.router_data.request.entity_type),
                })))
            }
        }
    }
}

// Payouts response transform
#[cfg(feature = "payouts")]


pub async fn get_customer_mandates(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::CustomerId>,
) -> impl Responder {
    let flow = Flow::CustomersGetMandates;
    let customer_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        customer_id,
        |state, auth: auth::AuthenticationData, customer_id, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            crate::core::mandate::get_customer_mandates(state, merchant_context, customer_id)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantMandateRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub fn get_nti_and_card_details_for_mit_flow(
        recurring_details: mandates::RecurringDetails,
    ) -> Option<(api_models::payments::MandateReferenceId, Self)> {
        let network_transaction_id_and_card_details = match recurring_details {
            mandates::RecurringDetails::NetworkTransactionIdAndCardDetails(
                network_transaction_id_and_card_details,
            ) => Some(network_transaction_id_and_card_details),
            mandates::RecurringDetails::MandateId(_)
            | mandates::RecurringDetails::PaymentMethodId(_)
            | mandates::RecurringDetails::ProcessorPaymentToken(_) => None,
        }?;

        let mandate_reference_id = api_models::payments::MandateReferenceId::NetworkMandateId(
            network_transaction_id_and_card_details
                .network_transaction_id
                .peek()
                .to_string(),
        );

        Some((
            mandate_reference_id,
            network_transaction_id_and_card_details.clone().into(),
        ))
    }


impl api::MandateSetup for Trustpay {}


pub async fn perform_static_routing_v1(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    algorithm_id: Option<&common_utils::id_type::RoutingId>,
    business_profile: &domain::Profile,
    transaction_data: &routing::TransactionData<'_>,
) -> RoutingResult<(
    Vec<routing_types::RoutableConnectorChoice>,
    Option<common_enums::RoutingApproach>,
)> {
    logger::debug!("euclid_routing: performing routing for connector selection");
    let get_merchant_fallback_config = || async {
        #[cfg(feature = "v1")]
        return routing::helpers::get_merchant_default_config(
            &*state.clone().store,
            business_profile.get_id().get_string_repr(),
            &api_enums::TransactionType::from(transaction_data),
        )
        .await
        .change_context(errors::RoutingError::FallbackConfigFetchFailed);
        #[cfg(feature = "v2")]
        return admin::ProfileWrapper::new(business_profile.clone())
            .get_default_fallback_list_of_connector_under_profile()
            .change_context(errors::RoutingError::FallbackConfigFetchFailed);
    };
    let algorithm_id = if let Some(id) = algorithm_id {
        id
    } else {
        let fallback_config = get_merchant_fallback_config().await?;
        logger::debug!("euclid_routing: active algorithm isn't present, default falling back");
        return Ok((fallback_config, None));
    };
    let cached_algorithm = ensure_algorithm_cached_v1(
        state,
        merchant_id,
        algorithm_id,
        business_profile.get_id(),
        &api_enums::TransactionType::from(transaction_data),
    )
    .await?;

    let backend_input = match transaction_data {
        routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?,
        #[cfg(feature = "payouts")]
        routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?,
    };

    let payment_id = match transaction_data {
        routing::TransactionData::Payment(payment_data) => payment_data
            .payment_attempt
            .payment_id
            .clone()
            .get_string_repr()
            .to_string(),
        #[cfg(feature = "payouts")]
        routing::TransactionData::Payout(payout_data) => payout_data
            .payout_attempt
            .payout_id
            .get_string_repr()
            .to_string(),
    };

    // Decision of de-routing is stored
    let de_evaluated_connector = if !state.conf.open_router.static_routing_enabled {
        logger::debug!("decision_engine_euclid: decision_engine routing not enabled");
        Vec::default()
    } else {
        utils::decision_engine_routing(
            state,
            backend_input.clone(),
            business_profile,
            payment_id,
            get_merchant_fallback_config().await?,
        )
        .await
        .map_err(|e|
            // errors are ignored as this is just for diff checking as of now (optional flow).
            logger::error!(decision_engine_euclid_evaluate_error=?e, "decision_engine_euclid: error in evaluation of rule")
        )
        .unwrap_or_default()
    };

    let (routable_connectors, routing_approach) = match cached_algorithm.as_ref() {
        CachedAlgorithm::Single(conn) => (
            vec![(**conn).clone()],
            Some(common_enums::RoutingApproach::StraightThroughRouting),
        ),
        CachedAlgorithm::Priority(plist) => (plist.clone(), None),
        CachedAlgorithm::VolumeSplit(splits) => (
            perform_volume_split(splits.to_vec())
                .change_context(errors::RoutingError::ConnectorSelectionFailed)?,
            Some(common_enums::RoutingApproach::VolumeBasedRouting),
        ),
        CachedAlgorithm::Advanced(interpreter) => (
            execute_dsl_and_get_connector_v1(backend_input, interpreter)?,
            Some(common_enums::RoutingApproach::RuleBasedRouting),
        ),
    };

    // Results are logged for diff(between legacy and decision_engine's euclid) and have parameters as:
    // is_equal: verifies all output are matching in order,
    // is_equal_length: matches length of both outputs (useful for verifying volume based routing
    // results)
    // de_response: response from the decision_engine's euclid
    // hs_response: response from legacy_euclid
    utils::compare_and_log_result(
        de_evaluated_connector.clone(),
        routable_connectors.clone(),
        "evaluate_routing".to_string(),
    );

    Ok((
        utils::select_routing_result(
            state,
            business_profile,
            routable_connectors,
            de_evaluated_connector,
        )
        .await,
        routing_approach,
    ))
}


pub async fn call_surcharge_decision_management_for_session_flow(
    _state: &SessionState,
    _merchant_context: &domain::MerchantContext,
    _payment_attempt: &storage::PaymentAttempt,
    _payment_intent: &storage::PaymentIntent,
    _billing_address: Option<hyperswitch_domain_models::address::Address>,
    _session_connector_data: &[api::SessionConnectorData],
) -> RouterResult<Option<api::SessionSurchargeDetails>> {
    todo!()
}


pub struct BreadpayRouterData<T> {
    pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}


pub fn build_form_from_struct<T: Serialize>(data: T) -> Result<Form, common_errors::ParsingError> {
    let mut form = Form::new();
    let serialized = serde_json::to_value(&data).map_err(|e| {
        router_env::logger::error!("Error serializing data to JSON value: {:?}", e);
        common_errors::ParsingError::EncodeError("json-value")
    })?;
    let serialized_object = serialized.as_object().ok_or_else(|| {
        router_env::logger::error!("Error: Expected JSON object but got something else");
        common_errors::ParsingError::EncodeError("Expected object")
    })?;
    for (key, values) in serialized_object {
        let value = match values {
            Value::String(s) => s.clone(),
            Value::Number(n) => n.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Array(_) | Value::Object(_) | Value::Null => {
                router_env::logger::error!(serialization_error =? "Form Construction Failed.");
                "".to_string()
            }
        };
        form = form.text(key.clone(), value.clone());
    }
    Ok(form)
}


impl api::PaymentSync for Aci {}


File: crates/router/src/workflows/attach_payout_account_workflow.rs
 Public structs: 1

use common_utils::{
    consts::DEFAULT_LOCALE,
    ext_traits::{OptionExt, ValueExt},
};
use scheduler::{
    consumer::{self, workflows::ProcessTrackerWorkflow},
    errors,
};

use crate::{
    core::payouts,
    errors as core_errors,
    routes::SessionState,
    types::{api, domain, storage},
};

pub struct AttachPayoutAccountWorkflow;

#[async_trait::async_trait]
impl ProcessTrackerWorkflow<SessionState> for AttachPayoutAccountWorkflow {
    async fn execute_workflow<'a>(
        &'a self,
        state: &'a SessionState,
        process: storage::ProcessTracker,
    ) -> Result<(), errors::ProcessTrackerError> {
        // Gather context
        let db = &*state.store;
        let tracking_data: api::PayoutRetrieveRequest = process
            .tracking_data
            .clone()
            .parse_value("PayoutRetrieveRequest")?;

        let merchant_id = tracking_data
            .merchant_id
            .clone()
            .get_required_value("merchant_id")?;
        let key_manager_state = &state.into();
        let key_store = db
            .get_merchant_key_store_by_merchant_id(
                key_manager_state,
                &merchant_id,
                &db.get_master_key().to_vec().into(),
            )
            .await?;

        let merchant_account = db
            .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
            .await?;

        let request = api::payouts::PayoutRequest::PayoutRetrieveRequest(tracking_data);

        let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
            merchant_account.clone(),
            key_store.clone(),
        )));
        let mut payout_data = Box::pin(payouts::make_payout_data(
            state,
            &merchant_context,
            None,
            &request,
            DEFAULT_LOCALE,
        ))
        .await?;

        payouts::payouts_core(state, &merchant_context, &mut payout_data, None, None).await?;

        Ok(())
    }

    async fn error_handler<'a>(
        &'a self,
        state: &'a SessionState,
        process: storage::ProcessTracker,
        error: errors::ProcessTrackerError,
    ) -> core_errors::CustomResult<(), errors::ProcessTrackerError> {
        consumer::consumer_error_handler(state.store.as_scheduler(), process, error).await
    }
}


pub async fn delete_tokenized_data_api(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::GlobalTokenId>,
    json_payload: web::Json<api_models::tokenization::DeleteTokenDataRequest>,
) -> HttpResponse {
    let flow = Flow::TokenizationDelete;
    let payload = json_payload.into_inner();
    let session_id = payload.session_id.clone();
    let token_id = path.into_inner();

    Box::pin(api_service::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            tokenization::delete_tokenized_data_core(state, merchant_context, &token_id, req)
        },
        auth::api_or_client_auth(
            &auth::V2ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            },
            &auth::V2ClientAuth(
                common_utils::types::authentication::ResourceId::PaymentMethodSession(session_id),
            ),
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub async fn get_merchant_scoped_metadata_from_db(
    state: &SessionState,
    merchant_id: id_type::MerchantId,
    org_id: id_type::OrganizationId,
    metadata_keys: Vec<DBEnum>,
) -> UserResult<Vec<DashboardMetadata>> {
    state
        .store
        .find_merchant_scoped_dashboard_metadata(&merchant_id, &org_id, metadata_keys)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("DB Error Fetching DashboardMetaData")
}