// File: crates/hyperswitch_domain_models/src/invoice.rs

use common_utils::{
    errors::{CustomResult, ValidationError},
    id_type::GenerateId,
    pii::SecretSerdeValue,
    types::{
        keymanager::{Identifier, KeyManagerState},
        MinorUnit,
    },
};
use masking::{PeekInterface, Secret};
use utoipa::ToSchema;

use crate::merchant_key_store::MerchantKeyStore;

#[derive(Debug, Clone, serde::Serialize, ToSchema)]
pub struct Invoice {
    pub id: common_utils::id_type::InvoiceId,
    pub subscription_id: common_utils::id_type::SubscriptionId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub profile_id: common_utils::id_type::ProfileId,
    pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
    pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
    pub payment_method_id: Option<String>,
    pub customer_id: common_utils::id_type::CustomerId,
    pub amount: MinorUnit,
    pub currency: String,
    pub status: common_enums::connector_enums::InvoiceStatus,
    pub provider_name: common_enums::connector_enums::Connector,
    pub metadata: Option<SecretSerdeValue>,
    pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
}

#[async_trait::async_trait]

impl super::behaviour::Conversion for Invoice {
    type DstType = diesel_models::invoice::Invoice;
    type NewDstType = diesel_models::invoice::InvoiceNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let now = common_utils::date_time::now();
        Ok(diesel_models::invoice::Invoice {
            id: self.id,
            subscription_id: self.subscription_id,
            merchant_id: self.merchant_id,
            profile_id: self.profile_id,
            merchant_connector_id: self.merchant_connector_id,
            payment_intent_id: self.payment_intent_id,
            payment_method_id: self.payment_method_id,
            customer_id: self.customer_id,
            amount: self.amount,
            currency: self.currency.to_string(),
            status: self.status,
            provider_name: self.provider_name,
            metadata: None,
            created_at: now,
            modified_at: now,
            connector_invoice_id: self.connector_invoice_id,
        })
    }

    async fn convert_back(
        _state: &KeyManagerState,
        item: Self::DstType,
        _key: &Secret<Vec<u8>>,
        _key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        Ok(Self {
            id: item.id,
            subscription_id: item.subscription_id,
            merchant_id: item.merchant_id,
            profile_id: item.profile_id,
            merchant_connector_id: item.merchant_connector_id,
            payment_intent_id: item.payment_intent_id,
            payment_method_id: item.payment_method_id,
            customer_id: item.customer_id,
            amount: item.amount,
            currency: item.currency,
            status: item.status,
            provider_name: item.provider_name,
            metadata: item.metadata,
            connector_invoice_id: item.connector_invoice_id,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(diesel_models::invoice::InvoiceNew::new(
            self.subscription_id,
            self.merchant_id,
            self.profile_id,
            self.merchant_connector_id,
            self.payment_intent_id,
            self.payment_method_id,
            self.customer_id,
            self.amount,
            self.currency.to_string(),
            self.status,
            self.provider_name,
            None,
            self.connector_invoice_id,
        ))
    }
}

impl Invoice {
    #[allow(clippy::too_many_arguments)]
    pub fn to_invoice(
        subscription_id: common_utils::id_type::SubscriptionId,
        merchant_id: common_utils::id_type::MerchantId,
        profile_id: common_utils::id_type::ProfileId,
        merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId,
        payment_intent_id: Option<common_utils::id_type::PaymentId>,
        payment_method_id: Option<String>,
        customer_id: common_utils::id_type::CustomerId,
        amount: MinorUnit,
        currency: String,
        status: common_enums::connector_enums::InvoiceStatus,
        provider_name: common_enums::connector_enums::Connector,
        metadata: Option<SecretSerdeValue>,
        connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
    ) -> Self {
        Self {
            id: common_utils::id_type::InvoiceId::generate(),
            subscription_id,
            merchant_id,
            profile_id,
            merchant_connector_id,
            payment_intent_id,
            payment_method_id,
            customer_id,
            amount,
            currency: currency.to_string(),
            status,
            provider_name,
            metadata,
            connector_invoice_id,
        }
    }
}

#[async_trait::async_trait]
pub trait InvoiceInterface {
    type Error;
    async fn insert_invoice_entry(
        &self,
        key_store: &MerchantKeyStore,
        invoice_new: Invoice,
    ) -> CustomResult<Invoice, Self::Error>;

    async fn find_invoice_by_invoice_id(
        &self,
        key_store: &MerchantKeyStore,
        invoice_id: String,
    ) -> CustomResult<Invoice, Self::Error>;

    async fn update_invoice_entry(
        &self,
        key_store: &MerchantKeyStore,
        invoice_id: String,
        data: InvoiceUpdate,
    ) -> CustomResult<Invoice, Self::Error>;

    async fn get_latest_invoice_for_subscription(
        &self,
        key_store: &MerchantKeyStore,
        subscription_id: String,
    ) -> CustomResult<Invoice, Self::Error>;

    async fn find_invoice_by_subscription_id_connector_invoice_id(
        &self,
        key_store: &MerchantKeyStore,
        subscription_id: String,
        connector_invoice_id: common_utils::id_type::InvoiceId,
    ) -> CustomResult<Option<Invoice>, Self::Error>;
}

pub struct InvoiceUpdate {
    pub status: Option<common_enums::connector_enums::InvoiceStatus>,
    pub payment_method_id: Option<String>,
    pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
    pub modified_at: time::PrimitiveDateTime,
    pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
    pub amount: Option<MinorUnit>,
    pub currency: Option<String>,
}

#[derive(Debug, Clone)]
pub struct AmountAndCurrencyUpdate {
    pub amount: MinorUnit,
    pub currency: String,
}

#[derive(Debug, Clone)]
pub struct ConnectorAndStatusUpdate {
    pub connector_invoice_id: common_utils::id_type::InvoiceId,
    pub status: common_enums::connector_enums::InvoiceStatus,
}

#[derive(Debug, Clone)]
pub struct PaymentAndStatusUpdate {
    pub payment_method_id: Option<Secret<String>>,
    pub payment_intent_id: Option<common_utils::id_type::PaymentId>,
    pub status: common_enums::connector_enums::InvoiceStatus,
    pub connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
}

/// Enum-based invoice update request for different scenarios
#[derive(Debug, Clone)]
pub enum InvoiceUpdateRequest {
    /// Update amount and currency
    Amount(AmountAndCurrencyUpdate),
    /// Update connector invoice ID and status
    Connector(ConnectorAndStatusUpdate),
    /// Update payment details along with status
    PaymentStatus(PaymentAndStatusUpdate),
}

impl InvoiceUpdateRequest {
    /// Create an amount and currency update request
    pub fn update_amount_and_currency(amount: MinorUnit, currency: String) -> Self {
        Self::Amount(AmountAndCurrencyUpdate { amount, currency })
    }

    /// Create a connector invoice ID and status update request
    pub fn update_connector_and_status(
        connector_invoice_id: common_utils::id_type::InvoiceId,
        status: common_enums::connector_enums::InvoiceStatus,
    ) -> Self {
        Self::Connector(ConnectorAndStatusUpdate {
            connector_invoice_id,
            status,
        })
    }

    /// Create a combined payment and status update request
    pub fn update_payment_and_status(
        payment_method_id: Option<Secret<String>>,
        payment_intent_id: Option<common_utils::id_type::PaymentId>,
        status: common_enums::connector_enums::InvoiceStatus,
        connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
    ) -> Self {
        Self::PaymentStatus(PaymentAndStatusUpdate {
            payment_method_id,
            payment_intent_id,
            status,
            connector_invoice_id,
        })
    }
}

impl From<InvoiceUpdateRequest> for InvoiceUpdate {
    fn from(request: InvoiceUpdateRequest) -> Self {
        let now = common_utils::date_time::now();

        match request {
            InvoiceUpdateRequest::Amount(update) => Self {
                status: None,
                payment_method_id: None,
                connector_invoice_id: None,
                modified_at: now,
                payment_intent_id: None,
                amount: Some(update.amount),
                currency: Some(update.currency),
            },
            InvoiceUpdateRequest::Connector(update) => Self {
                status: Some(update.status),
                payment_method_id: None,
                connector_invoice_id: Some(update.connector_invoice_id),
                modified_at: now,
                payment_intent_id: None,
                amount: None,
                currency: None,
            },
            InvoiceUpdateRequest::PaymentStatus(update) => Self {
                status: Some(update.status),
                payment_method_id: update
                    .payment_method_id
                    .as_ref()
                    .map(|id| id.peek())
                    .cloned(),
                connector_invoice_id: update.connector_invoice_id,
                modified_at: now,
                payment_intent_id: update.payment_intent_id,
                amount: None,
                currency: None,
            },
        }
    }
}

#[async_trait::async_trait]
impl super::behaviour::Conversion for InvoiceUpdate {
    type DstType = diesel_models::invoice::InvoiceUpdate;
    type NewDstType = diesel_models::invoice::InvoiceUpdate;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::invoice::InvoiceUpdate {
            status: self.status,
            payment_method_id: self.payment_method_id,
            connector_invoice_id: self.connector_invoice_id,
            modified_at: self.modified_at,
            payment_intent_id: self.payment_intent_id,
            amount: self.amount,
            currency: self.currency,
        })
    }

    async fn convert_back(
        _state: &KeyManagerState,
        item: Self::DstType,
        _key: &Secret<Vec<u8>>,
        _key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        Ok(Self {
            status: item.status,
            payment_method_id: item.payment_method_id,
            connector_invoice_id: item.connector_invoice_id,
            modified_at: item.modified_at,
            payment_intent_id: item.payment_intent_id,
            amount: item.amount,
            currency: item.currency,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(diesel_models::invoice::InvoiceUpdate {
            status: self.status,
            payment_method_id: self.payment_method_id,
            connector_invoice_id: self.connector_invoice_id,
            modified_at: self.modified_at,
            payment_intent_id: self.payment_intent_id,
            amount: self.amount,
            currency: self.currency,
        })
    }
}

impl InvoiceUpdate {
    pub fn new(
        payment_method_id: Option<String>,
        status: Option<common_enums::connector_enums::InvoiceStatus>,
        connector_invoice_id: Option<common_utils::id_type::InvoiceId>,
        payment_intent_id: Option<common_utils::id_type::PaymentId>,
        amount: Option<MinorUnit>,
        currency: Option<String>,
    ) -> Self {
        Self {
            status,
            payment_method_id,
            connector_invoice_id,
            modified_at: common_utils::date_time::now(),
            payment_intent_id,
            amount,
            currency,
        }
    }
}


// File: crates/hyperswitch_domain_models/src/errors.rs

pub mod api_error_response;


// File: crates/hyperswitch_domain_models/src/card_testing_guard_data.rs

use serde::{self, Deserialize, Serialize};

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct CardTestingGuardData {
    pub is_card_ip_blocking_enabled: bool,
    pub card_ip_blocking_cache_key: String,
    pub is_guest_user_card_blocking_enabled: bool,
    pub guest_user_card_blocking_cache_key: String,
    pub is_customer_id_blocking_enabled: bool,
    pub customer_id_blocking_cache_key: String,
    pub card_testing_guard_expiry: i32,
}


// File: crates/hyperswitch_domain_models/src/customer.rs

use common_enums::enums::MerchantStorageScheme;
#[cfg(feature = "v2")]
use common_enums::DeleteStatus;
use common_utils::{
    crypto::{self, Encryptable},
    date_time,
    encryption::Encryption,
    errors::{CustomResult, ValidationError},
    ext_traits::ValueExt,
    id_type, pii,
    types::{
        keymanager::{self, KeyManagerState, ToEncryptable},
        CreatedBy, Description,
    },
};
use diesel_models::{
    customers as storage_types, customers::CustomerUpdateInternal, query::customers as query,
};
use error_stack::ResultExt;
use masking::{ExposeOptionInterface, PeekInterface, Secret, SwitchStrategy};
use router_env::{instrument, tracing};
use rustc_hash::FxHashMap;
use time::PrimitiveDateTime;

#[cfg(feature = "v2")]
use crate::merchant_connector_account::MerchantConnectorAccountTypeDetails;
use crate::{behaviour, merchant_key_store::MerchantKeyStore, type_encryption as types};

#[cfg(feature = "v1")]
#[derive(Clone, Debug, router_derive::ToEncryption)]
pub struct Customer {
    pub customer_id: id_type::CustomerId,
    pub merchant_id: id_type::MerchantId,
    #[encrypt]
    pub name: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub email: Option<Encryptable<Secret<String, pii::EmailStrategy>>>,
    #[encrypt]
    pub phone: Option<Encryptable<Secret<String>>>,
    pub phone_country_code: Option<String>,
    pub description: Option<Description>,
    pub created_at: PrimitiveDateTime,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub modified_at: PrimitiveDateTime,
    pub connector_customer: Option<pii::SecretSerdeValue>,
    pub address_id: Option<String>,
    pub default_payment_method_id: Option<String>,
    pub updated_by: Option<String>,
    pub version: common_enums::ApiVersion,
    #[encrypt]
    pub tax_registration_id: Option<Encryptable<Secret<String>>>,
    pub created_by: Option<CreatedBy>,
    pub last_modified_by: Option<CreatedBy>,
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, router_derive::ToEncryption)]
pub struct Customer {
    pub merchant_id: id_type::MerchantId,
    #[encrypt]
    pub name: Option<Encryptable<Secret<String>>>,
    #[encrypt]
    pub email: Option<Encryptable<Secret<String, pii::EmailStrategy>>>,
    #[encrypt]
    pub phone: Option<Encryptable<Secret<String>>>,
    pub phone_country_code: Option<String>,
    pub description: Option<Description>,
    pub created_at: PrimitiveDateTime,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
    pub modified_at: PrimitiveDateTime,
    pub default_payment_method_id: Option<id_type::GlobalPaymentMethodId>,
    pub updated_by: Option<String>,
    pub merchant_reference_id: Option<id_type::CustomerId>,
    pub default_billing_address: Option<Encryption>,
    pub default_shipping_address: Option<Encryption>,
    pub id: id_type::GlobalCustomerId,
    pub version: common_enums::ApiVersion,
    pub status: DeleteStatus,
    #[encrypt]
    pub tax_registration_id: Option<Encryptable<Secret<String>>>,
    pub created_by: Option<CreatedBy>,
    pub last_modified_by: Option<CreatedBy>,
}

impl Customer {
    /// Get the unique identifier of Customer
    #[cfg(feature = "v1")]
    pub fn get_id(&self) -> &id_type::CustomerId {
        &self.customer_id
    }

    /// Get the global identifier of Customer
    #[cfg(feature = "v2")]
    pub fn get_id(&self) -> &id_type::GlobalCustomerId {
        &self.id
    }

    /// Get the connector customer ID for the specified connector label, if present
    #[cfg(feature = "v1")]
    pub fn get_connector_customer_map(
        &self,
    ) -> FxHashMap<id_type::MerchantConnectorAccountId, String> {
        use masking::PeekInterface;
        if let Some(connector_customer_value) = &self.connector_customer {
            connector_customer_value
                .peek()
                .clone()
                .parse_value("ConnectorCustomerMap")
                .unwrap_or_default()
        } else {
            FxHashMap::default()
        }
    }

    /// Get the connector customer ID for the specified connector label, if present
    #[cfg(feature = "v1")]
    pub fn get_connector_customer_id(&self, connector_label: &str) -> Option<&str> {
        use masking::PeekInterface;

        self.connector_customer
            .as_ref()
            .and_then(|connector_customer_value| {
                connector_customer_value.peek().get(connector_label)
            })
            .and_then(|connector_customer| connector_customer.as_str())
    }

    /// Get the connector customer ID for the specified merchant connector account ID, if present
    #[cfg(feature = "v2")]
    pub fn get_connector_customer_id(
        &self,
        merchant_connector_account: &MerchantConnectorAccountTypeDetails,
    ) -> Option<&str> {
        match merchant_connector_account {
            MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(account) => {
                let connector_account_id = account.get_id();
                self.connector_customer
                    .as_ref()?
                    .get(&connector_account_id)
                    .map(|connector_customer_id| connector_customer_id.as_str())
            }
            MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None,
        }
    }
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl behaviour::Conversion for Customer {
    type DstType = diesel_models::customers::Customer;
    type NewDstType = diesel_models::customers::CustomerNew;
    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::customers::Customer {
            customer_id: self.customer_id,
            merchant_id: self.merchant_id,
            name: self.name.map(Encryption::from),
            email: self.email.map(Encryption::from),
            phone: self.phone.map(Encryption::from),
            phone_country_code: self.phone_country_code,
            description: self.description,
            created_at: self.created_at,
            metadata: self.metadata,
            modified_at: self.modified_at,
            connector_customer: self.connector_customer,
            address_id: self.address_id,
            default_payment_method_id: self.default_payment_method_id,
            updated_by: self.updated_by,
            version: self.version,
            tax_registration_id: self.tax_registration_id.map(Encryption::from),
            created_by: self.created_by.map(|created_by| created_by.to_string()),
            last_modified_by: self
                .last_modified_by
                .map(|last_modified_by| last_modified_by.to_string()),
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        item: Self::DstType,
        key: &Secret<Vec<u8>>,
        _key_store_ref_id: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        let decrypted = types::crypto_operation(
            state,
            common_utils::type_name!(Self::DstType),
            types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable(
                EncryptedCustomer {
                    name: item.name.clone(),
                    phone: item.phone.clone(),
                    email: item.email.clone(),
                    tax_registration_id: item.tax_registration_id.clone(),
                },
            )),
            keymanager::Identifier::Merchant(item.merchant_id.clone()),
            key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting customer data".to_string(),
        })?;
        let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context(
            ValidationError::InvalidValue {
                message: "Failed while decrypting customer data".to_string(),
            },
        )?;

        Ok(Self {
            customer_id: item.customer_id,
            merchant_id: item.merchant_id,
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            phone_country_code: item.phone_country_code,
            description: item.description,
            created_at: item.created_at,
            metadata: item.metadata,
            modified_at: item.modified_at,
            connector_customer: item.connector_customer,
            address_id: item.address_id,
            default_payment_method_id: item.default_payment_method_id,
            updated_by: item.updated_by,
            version: item.version,
            tax_registration_id: encryptable_customer.tax_registration_id,
            created_by: item
                .created_by
                .and_then(|created_by| created_by.parse::<CreatedBy>().ok()),
            last_modified_by: item
                .last_modified_by
                .and_then(|last_modified_by| last_modified_by.parse::<CreatedBy>().ok()),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(diesel_models::customers::CustomerNew {
            customer_id: self.customer_id,
            merchant_id: self.merchant_id,
            name: self.name.map(Encryption::from),
            email: self.email.map(Encryption::from),
            phone: self.phone.map(Encryption::from),
            description: self.description,
            phone_country_code: self.phone_country_code,
            metadata: self.metadata,
            created_at: now,
            modified_at: now,
            connector_customer: self.connector_customer,
            address_id: self.address_id,
            updated_by: self.updated_by,
            version: self.version,
            tax_registration_id: self.tax_registration_id.map(Encryption::from),
            created_by: self
                .created_by
                .as_ref()
                .map(|created_by| created_by.to_string()),
            last_modified_by: self.created_by.map(|created_by| created_by.to_string()), // Same as created_by on creation
        })
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl behaviour::Conversion for Customer {
    type DstType = diesel_models::customers::Customer;
    type NewDstType = diesel_models::customers::CustomerNew;
    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::customers::Customer {
            id: self.id,
            merchant_reference_id: self.merchant_reference_id,
            merchant_id: self.merchant_id,
            name: self.name.map(Encryption::from),
            email: self.email.map(Encryption::from),
            phone: self.phone.map(Encryption::from),
            phone_country_code: self.phone_country_code,
            description: self.description,
            created_at: self.created_at,
            metadata: self.metadata,
            modified_at: self.modified_at,
            connector_customer: self.connector_customer,
            default_payment_method_id: self.default_payment_method_id,
            updated_by: self.updated_by,
            default_billing_address: self.default_billing_address,
            default_shipping_address: self.default_shipping_address,
            version: self.version,
            status: self.status,
            tax_registration_id: self.tax_registration_id.map(Encryption::from),
            created_by: self.created_by.map(|created_by| created_by.to_string()),
            last_modified_by: self
                .last_modified_by
                .map(|last_modified_by| last_modified_by.to_string()),
        })
    }

    async fn convert_back(
        state: &KeyManagerState,
        item: Self::DstType,
        key: &Secret<Vec<u8>>,
        _key_store_ref_id: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        let decrypted = types::crypto_operation(
            state,
            common_utils::type_name!(Self::DstType),
            types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable(
                EncryptedCustomer {
                    name: item.name.clone(),
                    phone: item.phone.clone(),
                    email: item.email.clone(),
                    tax_registration_id: item.tax_registration_id.clone(),
                },
            )),
            keymanager::Identifier::Merchant(item.merchant_id.clone()),
            key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting customer data".to_string(),
        })?;
        let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context(
            ValidationError::InvalidValue {
                message: "Failed while decrypting customer data".to_string(),
            },
        )?;

        Ok(Self {
            id: item.id,
            merchant_reference_id: item.merchant_reference_id,
            merchant_id: item.merchant_id,
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            phone_country_code: item.phone_country_code,
            description: item.description,
            created_at: item.created_at,
            metadata: item.metadata,
            modified_at: item.modified_at,
            connector_customer: item.connector_customer,
            default_payment_method_id: item.default_payment_method_id,
            updated_by: item.updated_by,
            default_billing_address: item.default_billing_address,
            default_shipping_address: item.default_shipping_address,
            version: item.version,
            status: item.status,
            tax_registration_id: encryptable_customer.tax_registration_id,
            created_by: item
                .created_by
                .and_then(|created_by| created_by.parse::<CreatedBy>().ok()),
            last_modified_by: item
                .last_modified_by
                .and_then(|last_modified_by| last_modified_by.parse::<CreatedBy>().ok()),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(diesel_models::customers::CustomerNew {
            id: self.id,
            merchant_reference_id: self.merchant_reference_id,
            merchant_id: self.merchant_id,
            name: self.name.map(Encryption::from),
            email: self.email.map(Encryption::from),
            phone: self.phone.map(Encryption::from),
            description: self.description,
            phone_country_code: self.phone_country_code,
            metadata: self.metadata,
            default_payment_method_id: None,
            created_at: now,
            modified_at: now,
            connector_customer: self.connector_customer,
            updated_by: self.updated_by,
            default_billing_address: self.default_billing_address,
            default_shipping_address: self.default_shipping_address,
            version: common_types::consts::API_VERSION,
            status: self.status,
            tax_registration_id: self.tax_registration_id.map(Encryption::from),
            created_by: self
                .created_by
                .as_ref()
                .map(|created_by| created_by.to_string()),
            last_modified_by: self.created_by.map(|created_by| created_by.to_string()), // Same as created_by on creation
        })
    }
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug)]
pub struct CustomerGeneralUpdate {
    pub name: crypto::OptionalEncryptableName,
    pub email: Box<crypto::OptionalEncryptableEmail>,
    pub phone: Box<crypto::OptionalEncryptablePhone>,
    pub description: Option<Description>,
    pub phone_country_code: Option<String>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub connector_customer: Box<Option<common_types::customers::ConnectorCustomerMap>>,
    pub default_billing_address: Option<Encryption>,
    pub default_shipping_address: Option<Encryption>,
    pub default_payment_method_id: Option<Option<id_type::GlobalPaymentMethodId>>,
    pub status: Option<DeleteStatus>,
    pub tax_registration_id: crypto::OptionalEncryptableSecretString,
    pub last_modified_by: Option<String>,
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug)]
pub enum CustomerUpdate {
    Update(Box<CustomerGeneralUpdate>),
    ConnectorCustomer {
        connector_customer: Option<common_types::customers::ConnectorCustomerMap>,
        last_modified_by: Option<String>,
    },
    UpdateDefaultPaymentMethod {
        default_payment_method_id: Option<Option<id_type::GlobalPaymentMethodId>>,
        last_modified_by: Option<String>,
    },
}

#[cfg(feature = "v2")]
impl From<CustomerUpdate> for CustomerUpdateInternal {
    fn from(customer_update: CustomerUpdate) -> Self {
        match customer_update {
            CustomerUpdate::Update(update) => {
                let CustomerGeneralUpdate {
                    name,
                    email,
                    phone,
                    description,
                    phone_country_code,
                    metadata,
                    connector_customer,
                    default_billing_address,
                    default_shipping_address,
                    default_payment_method_id,
                    status,
                    tax_registration_id,
                    last_modified_by,
                } = *update;
                Self {
                    name: name.map(Encryption::from),
                    email: email.map(Encryption::from),
                    phone: phone.map(Encryption::from),
                    description,
                    phone_country_code,
                    metadata,
                    connector_customer: *connector_customer,
                    modified_at: date_time::now(),
                    default_billing_address,
                    default_shipping_address,
                    default_payment_method_id,
                    updated_by: None,
                    status,
                    tax_registration_id: tax_registration_id.map(Encryption::from),
                    last_modified_by,
                }
            }
            CustomerUpdate::ConnectorCustomer {
                connector_customer,
                last_modified_by,
            } => Self {
                connector_customer,
                name: None,
                email: None,
                phone: None,
                description: None,
                phone_country_code: None,
                metadata: None,
                modified_at: date_time::now(),
                default_payment_method_id: None,
                updated_by: None,
                default_billing_address: None,
                default_shipping_address: None,
                status: None,
                tax_registration_id: None,
                last_modified_by,
            },
            CustomerUpdate::UpdateDefaultPaymentMethod {
                default_payment_method_id,
                last_modified_by,
            } => Self {
                default_payment_method_id,
                modified_at: date_time::now(),
                name: None,
                email: None,
                phone: None,
                description: None,
                phone_country_code: None,
                metadata: None,
                connector_customer: None,
                updated_by: None,
                default_billing_address: None,
                default_shipping_address: None,
                status: None,
                tax_registration_id: None,
                last_modified_by,
            },
        }
    }
}

#[cfg(feature = "v1")]
#[derive(Clone, Debug)]
pub enum CustomerUpdate {
    Update {
        name: crypto::OptionalEncryptableName,
        email: crypto::OptionalEncryptableEmail,
        phone: Box<crypto::OptionalEncryptablePhone>,
        description: Option<Description>,
        phone_country_code: Option<String>,
        metadata: Box<Option<pii::SecretSerdeValue>>,
        connector_customer: Box<Option<pii::SecretSerdeValue>>,
        address_id: Option<String>,
        tax_registration_id: crypto::OptionalEncryptableSecretString,
        last_modified_by: Option<String>,
    },
    ConnectorCustomer {
        connector_customer: Option<pii::SecretSerdeValue>,
        last_modified_by: Option<String>,
    },
    UpdateDefaultPaymentMethod {
        default_payment_method_id: Option<Option<String>>,
        last_modified_by: Option<String>,
    },
}

#[cfg(feature = "v1")]
impl From<CustomerUpdate> for CustomerUpdateInternal {
    fn from(customer_update: CustomerUpdate) -> Self {
        match customer_update {
            CustomerUpdate::Update {
                name,
                email,
                phone,
                description,
                phone_country_code,
                metadata,
                connector_customer,
                address_id,
                tax_registration_id,
                last_modified_by,
            } => Self {
                name: name.map(Encryption::from),
                email: email.map(Encryption::from),
                phone: phone.map(Encryption::from),
                description,
                phone_country_code,
                metadata: *metadata,
                connector_customer: *connector_customer,
                modified_at: date_time::now(),
                address_id,
                default_payment_method_id: None,
                updated_by: None,
                tax_registration_id: tax_registration_id.map(Encryption::from),
                last_modified_by,
            },
            CustomerUpdate::ConnectorCustomer {
                connector_customer,
                last_modified_by,
            } => Self {
                connector_customer,
                modified_at: date_time::now(),
                name: None,
                email: None,
                phone: None,
                description: None,
                phone_country_code: None,
                metadata: None,
                default_payment_method_id: None,
                updated_by: None,
                address_id: None,
                tax_registration_id: None,
                last_modified_by,
            },
            CustomerUpdate::UpdateDefaultPaymentMethod {
                default_payment_method_id,
                last_modified_by,
            } => Self {
                default_payment_method_id,
                modified_at: date_time::now(),
                name: None,
                email: None,
                phone: None,
                description: None,
                phone_country_code: None,
                metadata: None,
                connector_customer: None,
                updated_by: None,
                address_id: None,
                tax_registration_id: None,
                last_modified_by,
            },
        }
    }
}

pub struct CustomerListConstraints {
    pub limit: u16,
    pub offset: Option<u32>,
    pub customer_id: Option<id_type::CustomerId>,
    pub time_range: Option<common_utils::types::TimeRange>,
}

impl From<CustomerListConstraints> for query::CustomerListConstraints {
    fn from(value: CustomerListConstraints) -> Self {
        Self {
            limit: i64::from(value.limit),
            offset: value.offset.map(i64::from),
            customer_id: value.customer_id,
            time_range: value.time_range,
        }
    }
}

#[async_trait::async_trait]
pub trait CustomerInterface
where
    Customer: behaviour::Conversion<
        DstType = storage_types::Customer,
        NewDstType = storage_types::CustomerNew,
    >,
{
    type Error;
    #[cfg(feature = "v1")]
    async fn delete_customer_by_customer_id_merchant_id(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> CustomResult<bool, Self::Error>;

    #[cfg(feature = "v1")]
    async fn find_customer_optional_by_customer_id_merchant_id(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Option<Customer>, Self::Error>;

    #[cfg(feature = "v1")]
    async fn find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Option<Customer>, Self::Error>;

    #[cfg(feature = "v2")]
    async fn find_optional_by_merchant_id_merchant_reference_id(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Option<Customer>, Self::Error>;

    #[cfg(feature = "v1")]
    #[allow(clippy::too_many_arguments)]
    async fn update_customer_by_customer_id_merchant_id(
        &self,
        customer_id: id_type::CustomerId,
        merchant_id: id_type::MerchantId,
        customer: Customer,
        customer_update: CustomerUpdate,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;

    #[cfg(feature = "v1")]
    async fn find_customer_by_customer_id_merchant_id(
        &self,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;

    #[cfg(feature = "v2")]
    async fn find_customer_by_merchant_reference_id_merchant_id(
        &self,
        merchant_reference_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;

    async fn list_customers_by_merchant_id(
        &self,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        constraints: CustomerListConstraints,
    ) -> CustomResult<Vec<Customer>, Self::Error>;

    async fn list_customers_by_merchant_id_with_count(
        &self,
        merchant_id: &id_type::MerchantId,
        key_store: &MerchantKeyStore,
        constraints: CustomerListConstraints,
    ) -> CustomResult<(Vec<Customer>, usize), Self::Error>;

    async fn insert_customer(
        &self,
        customer_data: Customer,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;

    #[cfg(feature = "v2")]
    #[allow(clippy::too_many_arguments)]
    async fn update_customer_by_global_id(
        &self,
        id: &id_type::GlobalCustomerId,
        customer: Customer,
        customer_update: CustomerUpdate,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;

    #[cfg(feature = "v2")]
    async fn find_customer_by_global_id(
        &self,
        id: &id_type::GlobalCustomerId,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
    ) -> CustomResult<Customer, Self::Error>;
}

#[cfg(feature = "v1")]
#[instrument]
pub async fn update_connector_customer_in_customers(
    connector_label: &str,
    customer: Option<&Customer>,
    connector_customer_id: Option<String>,
) -> Option<CustomerUpdate> {
    let mut connector_customer_map = customer
        .and_then(|customer| customer.connector_customer.clone().expose_option())
        .and_then(|connector_customer| connector_customer.as_object().cloned())
        .unwrap_or_default();

    let updated_connector_customer_map = connector_customer_id.map(|connector_customer_id| {
        let connector_customer_value = serde_json::Value::String(connector_customer_id);
        connector_customer_map.insert(connector_label.to_string(), connector_customer_value);
        connector_customer_map
    });

    updated_connector_customer_map
        .map(serde_json::Value::Object)
        .map(
            |connector_customer_value| CustomerUpdate::ConnectorCustomer {
                connector_customer: Some(pii::SecretSerdeValue::new(connector_customer_value)),
                last_modified_by: None,
            },
        )
}

#[cfg(feature = "v2")]
#[instrument]
pub async fn update_connector_customer_in_customers(
    merchant_connector_account: &MerchantConnectorAccountTypeDetails,
    customer: Option<&Customer>,
    connector_customer_id: Option<String>,
) -> Option<CustomerUpdate> {
    match merchant_connector_account {
        MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(account) => {
            connector_customer_id.map(|new_conn_cust_id| {
                let connector_account_id = account.get_id().clone();
                let mut connector_customer_map = customer
                    .and_then(|customer| customer.connector_customer.clone())
                    .unwrap_or_default();
                connector_customer_map.insert(connector_account_id, new_conn_cust_id);
                CustomerUpdate::ConnectorCustomer {
                    connector_customer: Some(connector_customer_map),
                    last_modified_by: None,
                }
            })
        }
        // TODO: Construct connector_customer for MerchantConnectorDetails if required by connector.
        MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            todo!("Handle connector_customer construction for MerchantConnectorDetails");
        }
    }
}


// File: crates/hyperswitch_domain_models/src/router_response_types.rs

pub mod disputes;
pub mod fraud_check;
pub mod revenue_recovery;
pub mod subscriptions;
use std::collections::HashMap;

use api_models::payments::AddressDetails;
use common_utils::{pii, request::Method, types::MinorUnit};
pub use disputes::{
    AcceptDisputeResponse, DefendDisputeResponse, DisputeSyncResponse, FetchDisputesResponse,
    SubmitEvidenceResponse,
};
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};

use crate::{
    errors::api_error_response::ApiErrorResponse,
    router_request_types::{authentication::AuthNFlowType, ResponseId},
    vault::PaymentMethodVaultingData,
};

#[derive(Debug, Clone, serde::Serialize)]
pub struct RefundsResponseData {
    pub connector_refund_id: String,
    pub refund_status: common_enums::RefundStatus,
    // pub amount_received: Option<i32>, // Calculation for amount received not in place yet
}

#[derive(Debug, Clone, Serialize)]
pub struct ConnectorCustomerResponseData {
    pub connector_customer_id: String,
    pub name: Option<String>,
    pub email: Option<String>,
    pub billing_address: Option<AddressDetails>,
}

impl ConnectorCustomerResponseData {
    pub fn new_with_customer_id(connector_customer_id: String) -> Self {
        Self::new(connector_customer_id, None, None, None)
    }
    pub fn new(
        connector_customer_id: String,
        name: Option<String>,
        email: Option<String>,
        billing_address: Option<AddressDetails>,
    ) -> Self {
        Self {
            connector_customer_id,
            name,
            email,
            billing_address,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub enum PaymentsResponseData {
    TransactionResponse {
        resource_id: ResponseId,
        redirection_data: Box<Option<RedirectForm>>,
        mandate_reference: Box<Option<MandateReference>>,
        connector_metadata: Option<serde_json::Value>,
        network_txn_id: Option<String>,
        connector_response_reference_id: Option<String>,
        incremental_authorization_allowed: Option<bool>,
        charges: Option<common_types::payments::ConnectorChargeResponseData>,
    },
    MultipleCaptureResponse {
        // pending_capture_id_list: Vec<String>,
        capture_sync_response_list: HashMap<String, CaptureSyncResponse>,
    },
    SessionResponse {
        session_token: api_models::payments::SessionToken,
    },
    SessionTokenResponse {
        session_token: String,
    },
    TransactionUnresolvedResponse {
        resource_id: ResponseId,
        //to add more info on cypto response, like `unresolved` reason(overpaid, underpaid, delayed)
        reason: Option<api_models::enums::UnresolvedResponseReason>,
        connector_response_reference_id: Option<String>,
    },
    TokenizationResponse {
        token: String,
    },

    ConnectorCustomerResponse(ConnectorCustomerResponseData),

    ThreeDSEnrollmentResponse {
        enrolled_v2: bool,
        related_transaction_id: Option<String>,
    },
    PreProcessingResponse {
        pre_processing_id: PreprocessingResponseId,
        connector_metadata: Option<serde_json::Value>,
        session_token: Option<api_models::payments::SessionToken>,
        connector_response_reference_id: Option<String>,
    },
    IncrementalAuthorizationResponse {
        status: common_enums::AuthorizationStatus,
        connector_authorization_id: Option<String>,
        error_code: Option<String>,
        error_message: Option<String>,
    },
    PostProcessingResponse {
        session_token: Option<api_models::payments::OpenBankingSessionToken>,
    },
    PaymentResourceUpdateResponse {
        status: common_enums::PaymentResourceUpdateStatus,
    },
    PaymentsCreateOrderResponse {
        order_id: String,
    },
}

#[derive(Debug, Clone)]
pub struct GiftCardBalanceCheckResponseData {
    pub balance: MinorUnit,
    pub currency: common_enums::Currency,
}

#[derive(Debug, Clone)]
pub struct TaxCalculationResponseData {
    pub order_tax_amount: MinorUnit,
}

#[derive(Serialize, Debug, Clone, serde::Deserialize)]
pub struct MandateReference {
    pub connector_mandate_id: Option<String>,
    pub payment_method_id: Option<String>,
    pub mandate_metadata: Option<pii::SecretSerdeValue>,
    pub connector_mandate_request_reference_id: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
pub enum CaptureSyncResponse {
    Success {
        resource_id: ResponseId,
        status: common_enums::AttemptStatus,
        connector_response_reference_id: Option<String>,
        amount: Option<MinorUnit>,
    },
    Error {
        code: String,
        message: String,
        reason: Option<String>,
        status_code: u16,
        amount: Option<MinorUnit>,
    },
}

impl CaptureSyncResponse {
    pub fn get_amount_captured(&self) -> Option<MinorUnit> {
        match self {
            Self::Success { amount, .. } | Self::Error { amount, .. } => *amount,
        }
    }
    pub fn get_connector_response_reference_id(&self) -> Option<String> {
        match self {
            Self::Success {
                connector_response_reference_id,
                ..
            } => connector_response_reference_id.clone(),
            Self::Error { .. } => None,
        }
    }
}
impl PaymentsResponseData {
    pub fn get_connector_metadata(&self) -> Option<masking::Secret<serde_json::Value>> {
        match self {
            Self::TransactionResponse {
                connector_metadata, ..
            }
            | Self::PreProcessingResponse {
                connector_metadata, ..
            } => connector_metadata.clone().map(masking::Secret::new),
            _ => None,
        }
    }
    pub fn get_network_transaction_id(&self) -> Option<String> {
        match self {
            Self::TransactionResponse { network_txn_id, .. } => network_txn_id.clone(),
            _ => None,
        }
    }

    pub fn get_connector_transaction_id(
        &self,
    ) -> Result<String, error_stack::Report<ApiErrorResponse>> {
        match self {
            Self::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(txn_id),
                ..
            } => Ok(txn_id.to_string()),
            _ => Err(ApiErrorResponse::MissingRequiredField {
                field_name: "ConnectorTransactionId",
            }
            .into()),
        }
    }

    pub fn merge_transaction_responses(
        auth_response: &Self,
        capture_response: &Self,
    ) -> Result<Self, error_stack::Report<ApiErrorResponse>> {
        match (auth_response, capture_response) {
            (
                Self::TransactionResponse {
                    resource_id: _,
                    redirection_data: auth_redirection_data,
                    mandate_reference: auth_mandate_reference,
                    connector_metadata: auth_connector_metadata,
                    network_txn_id: auth_network_txn_id,
                    connector_response_reference_id: auth_connector_response_reference_id,
                    incremental_authorization_allowed: auth_incremental_auth_allowed,
                    charges: auth_charges,
                },
                Self::TransactionResponse {
                    resource_id: capture_resource_id,
                    redirection_data: capture_redirection_data,
                    mandate_reference: capture_mandate_reference,
                    connector_metadata: capture_connector_metadata,
                    network_txn_id: capture_network_txn_id,
                    connector_response_reference_id: capture_connector_response_reference_id,
                    incremental_authorization_allowed: capture_incremental_auth_allowed,
                    charges: capture_charges,
                },
            ) => Ok(Self::TransactionResponse {
                resource_id: capture_resource_id.clone(),
                redirection_data: Box::new(
                    capture_redirection_data
                        .clone()
                        .or_else(|| *auth_redirection_data.clone()),
                ),
                mandate_reference: Box::new(
                    auth_mandate_reference
                        .clone()
                        .or_else(|| *capture_mandate_reference.clone()),
                ),
                connector_metadata: capture_connector_metadata
                    .clone()
                    .or(auth_connector_metadata.clone()),
                network_txn_id: capture_network_txn_id
                    .clone()
                    .or(auth_network_txn_id.clone()),
                connector_response_reference_id: capture_connector_response_reference_id
                    .clone()
                    .or(auth_connector_response_reference_id.clone()),
                incremental_authorization_allowed: (*capture_incremental_auth_allowed)
                    .or(*auth_incremental_auth_allowed),
                charges: auth_charges.clone().or(capture_charges.clone()),
            }),
            _ => Err(ApiErrorResponse::NotSupported {
                message: "Invalid Flow ".to_owned(),
            }
            .into()),
        }
    }

    #[cfg(feature = "v2")]
    pub fn get_updated_connector_token_details(
        &self,
        original_connector_mandate_request_reference_id: Option<String>,
    ) -> Option<diesel_models::ConnectorTokenDetails> {
        if let Self::TransactionResponse {
            mandate_reference, ..
        } = self
        {
            mandate_reference.clone().map(|mandate_ref| {
                let connector_mandate_id = mandate_ref.connector_mandate_id;
                let connector_mandate_request_reference_id = mandate_ref
                    .connector_mandate_request_reference_id
                    .or(original_connector_mandate_request_reference_id);

                diesel_models::ConnectorTokenDetails {
                    connector_mandate_id,
                    connector_token_request_reference_id: connector_mandate_request_reference_id,
                }
            })
        } else {
            None
        }
    }

    pub fn get_mandate_reference(&self) -> Option<MandateReference> {
        if let Self::TransactionResponse {
            mandate_reference, ..
        } = self
        {
            mandate_reference.as_ref().clone()
        } else {
            None
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub enum PreprocessingResponseId {
    PreProcessingId(String),
    ConnectorTransactionId(String),
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize, serde::Deserialize)]
pub enum RedirectForm {
    Form {
        endpoint: String,
        method: Method,
        form_fields: HashMap<String, String>,
    },
    Html {
        html_data: String,
    },
    BarclaycardAuthSetup {
        access_token: String,
        ddc_url: String,
        reference_id: String,
    },
    BarclaycardConsumerAuth {
        access_token: String,
        step_up_url: String,
    },
    BlueSnap {
        payment_fields_token: String, // payment-field-token
    },
    CybersourceAuthSetup {
        access_token: String,
        ddc_url: String,
        reference_id: String,
    },
    CybersourceConsumerAuth {
        access_token: String,
        step_up_url: String,
    },
    DeutschebankThreeDSChallengeFlow {
        acs_url: String,
        creq: String,
    },
    Payme,
    Braintree {
        client_token: String,
        card_token: String,
        bin: String,
        acs_url: String,
    },
    Nmi {
        amount: String,
        currency: common_enums::Currency,
        public_key: masking::Secret<String>,
        customer_vault_id: String,
        order_id: String,
    },
    Mifinity {
        initialization_token: String,
    },
    WorldpayDDCForm {
        endpoint: url::Url,
        method: Method,
        form_fields: HashMap<String, String>,
        collection_id: Option<String>,
    },
}

impl From<(url::Url, Method)> for RedirectForm {
    fn from((mut redirect_url, method): (url::Url, Method)) -> Self {
        let form_fields = HashMap::from_iter(
            redirect_url
                .query_pairs()
                .map(|(key, value)| (key.to_string(), value.to_string())),
        );

        // Do not include query params in the endpoint
        redirect_url.set_query(None);

        Self::Form {
            endpoint: redirect_url.to_string(),
            method,
            form_fields,
        }
    }
}

impl From<RedirectForm> for diesel_models::payment_attempt::RedirectForm {
    fn from(redirect_form: RedirectForm) -> Self {
        match redirect_form {
            RedirectForm::Form {
                endpoint,
                method,
                form_fields,
            } => Self::Form {
                endpoint,
                method,
                form_fields,
            },
            RedirectForm::Html { html_data } => Self::Html { html_data },
            RedirectForm::BarclaycardAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            } => Self::BarclaycardAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            },
            RedirectForm::BarclaycardConsumerAuth {
                access_token,
                step_up_url,
            } => Self::BarclaycardConsumerAuth {
                access_token,
                step_up_url,
            },
            RedirectForm::BlueSnap {
                payment_fields_token,
            } => Self::BlueSnap {
                payment_fields_token,
            },
            RedirectForm::CybersourceAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            } => Self::CybersourceAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            },
            RedirectForm::CybersourceConsumerAuth {
                access_token,
                step_up_url,
            } => Self::CybersourceConsumerAuth {
                access_token,
                step_up_url,
            },
            RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => {
                Self::DeutschebankThreeDSChallengeFlow { acs_url, creq }
            }
            RedirectForm::Payme => Self::Payme,
            RedirectForm::Braintree {
                client_token,
                card_token,
                bin,
                acs_url,
            } => Self::Braintree {
                client_token,
                card_token,
                bin,
                acs_url,
            },
            RedirectForm::Nmi {
                amount,
                currency,
                public_key,
                customer_vault_id,
                order_id,
            } => Self::Nmi {
                amount,
                currency,
                public_key,
                customer_vault_id,
                order_id,
            },
            RedirectForm::Mifinity {
                initialization_token,
            } => Self::Mifinity {
                initialization_token,
            },
            RedirectForm::WorldpayDDCForm {
                endpoint,
                method,
                form_fields,
                collection_id,
            } => Self::WorldpayDDCForm {
                endpoint: common_utils::types::Url::wrap(endpoint),
                method,
                form_fields,
                collection_id,
            },
        }
    }
}

impl From<diesel_models::payment_attempt::RedirectForm> for RedirectForm {
    fn from(redirect_form: diesel_models::payment_attempt::RedirectForm) -> Self {
        match redirect_form {
            diesel_models::payment_attempt::RedirectForm::Form {
                endpoint,
                method,
                form_fields,
            } => Self::Form {
                endpoint,
                method,
                form_fields,
            },
            diesel_models::payment_attempt::RedirectForm::Html { html_data } => {
                Self::Html { html_data }
            }
            diesel_models::payment_attempt::RedirectForm::BarclaycardAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            } => Self::BarclaycardAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            },
            diesel_models::payment_attempt::RedirectForm::BarclaycardConsumerAuth {
                access_token,
                step_up_url,
            } => Self::BarclaycardConsumerAuth {
                access_token,
                step_up_url,
            },
            diesel_models::payment_attempt::RedirectForm::BlueSnap {
                payment_fields_token,
            } => Self::BlueSnap {
                payment_fields_token,
            },
            diesel_models::payment_attempt::RedirectForm::CybersourceAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            } => Self::CybersourceAuthSetup {
                access_token,
                ddc_url,
                reference_id,
            },
            diesel_models::payment_attempt::RedirectForm::CybersourceConsumerAuth {
                access_token,
                step_up_url,
            } => Self::CybersourceConsumerAuth {
                access_token,
                step_up_url,
            },
            diesel_models::RedirectForm::DeutschebankThreeDSChallengeFlow { acs_url, creq } => {
                Self::DeutschebankThreeDSChallengeFlow { acs_url, creq }
            }
            diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme,
            diesel_models::payment_attempt::RedirectForm::Braintree {
                client_token,
                card_token,
                bin,
                acs_url,
            } => Self::Braintree {
                client_token,
                card_token,
                bin,
                acs_url,
            },
            diesel_models::payment_attempt::RedirectForm::Nmi {
                amount,
                currency,
                public_key,
                customer_vault_id,
                order_id,
            } => Self::Nmi {
                amount,
                currency,
                public_key,
                customer_vault_id,
                order_id,
            },
            diesel_models::payment_attempt::RedirectForm::Mifinity {
                initialization_token,
            } => Self::Mifinity {
                initialization_token,
            },
            diesel_models::payment_attempt::RedirectForm::WorldpayDDCForm {
                endpoint,
                method,
                form_fields,
                collection_id,
            } => Self::WorldpayDDCForm {
                endpoint: endpoint.into_inner(),
                method,
                form_fields,
                collection_id,
            },
        }
    }
}

#[derive(Default, Clone, Debug)]
pub struct UploadFileResponse {
    pub provider_file_id: String,
}
#[derive(Clone, Debug)]
pub struct RetrieveFileResponse {
    pub file_data: Vec<u8>,
}

#[cfg(feature = "payouts")]
#[derive(Clone, Debug, Default)]
pub struct PayoutsResponseData {
    pub status: Option<common_enums::PayoutStatus>,
    pub connector_payout_id: Option<String>,
    pub payout_eligible: Option<bool>,
    pub should_add_next_step_to_process_tracker: bool,
    pub error_code: Option<String>,
    pub error_message: Option<String>,
    pub payout_connector_metadata: Option<pii::SecretSerdeValue>,
}

#[derive(Debug, Clone)]
pub struct VerifyWebhookSourceResponseData {
    pub verify_webhook_status: VerifyWebhookStatus,
}

#[derive(Debug, Clone)]
pub enum VerifyWebhookStatus {
    SourceVerified,
    SourceNotVerified,
}

#[derive(Debug, Clone)]
pub struct MandateRevokeResponseData {
    pub mandate_status: common_enums::MandateStatus,
}

#[derive(Debug, Clone)]
pub enum AuthenticationResponseData {
    PreAuthVersionCallResponse {
        maximum_supported_3ds_version: common_utils::types::SemanticVersion,
    },
    PreAuthThreeDsMethodCallResponse {
        threeds_server_transaction_id: String,
        three_ds_method_data: Option<String>,
        three_ds_method_url: Option<String>,
        connector_metadata: Option<serde_json::Value>,
    },
    PreAuthNResponse {
        threeds_server_transaction_id: String,
        maximum_supported_3ds_version: common_utils::types::SemanticVersion,
        connector_authentication_id: String,
        three_ds_method_data: Option<String>,
        three_ds_method_url: Option<String>,
        message_version: common_utils::types::SemanticVersion,
        connector_metadata: Option<serde_json::Value>,
        directory_server_id: Option<String>,
    },
    AuthNResponse {
        authn_flow_type: AuthNFlowType,
        authentication_value: Option<masking::Secret<String>>,
        trans_status: common_enums::TransactionStatus,
        connector_metadata: Option<serde_json::Value>,
        ds_trans_id: Option<String>,
        eci: Option<String>,
        challenge_code: Option<String>,
        challenge_cancel: Option<String>,
        challenge_code_reason: Option<String>,
        message_extension: Option<pii::SecretSerdeValue>,
    },
    PostAuthNResponse {
        trans_status: common_enums::TransactionStatus,
        authentication_value: Option<masking::Secret<String>>,
        eci: Option<String>,
        challenge_cancel: Option<String>,
        challenge_code_reason: Option<String>,
    },
}

/// Represents details of a payment method.
#[derive(Debug, Clone)]
pub struct PaymentMethodDetails {
    /// Indicates whether mandates are supported by this payment method.
    pub mandates: common_enums::FeatureStatus,
    /// Indicates whether refund is supported by this payment method.
    pub refunds: common_enums::FeatureStatus,
    /// List of supported capture methods
    pub supported_capture_methods: Vec<common_enums::CaptureMethod>,
    /// Payment method specific features
    pub specific_features: Option<api_models::feature_matrix::PaymentMethodSpecificFeatures>,
}

/// list of payment method types and metadata related to them
pub type PaymentMethodTypeMetadata = HashMap<common_enums::PaymentMethodType, PaymentMethodDetails>;

/// list of payment methods, payment method types and metadata related to them
pub type SupportedPaymentMethods = HashMap<common_enums::PaymentMethod, PaymentMethodTypeMetadata>;

#[derive(Debug, Clone)]
pub struct ConnectorInfo {
    /// Display name of the Connector
    pub display_name: &'static str,
    /// Description of the connector.
    pub description: &'static str,
    /// Connector Type
    pub connector_type: common_enums::HyperswitchConnectorCategory,
    /// Integration status of the connector
    pub integration_status: common_enums::ConnectorIntegrationStatus,
}

pub trait SupportedPaymentMethodsExt {
    fn add(
        &mut self,
        payment_method: common_enums::PaymentMethod,
        payment_method_type: common_enums::PaymentMethodType,
        payment_method_details: PaymentMethodDetails,
    );
}

impl SupportedPaymentMethodsExt for SupportedPaymentMethods {
    fn add(
        &mut self,
        payment_method: common_enums::PaymentMethod,
        payment_method_type: common_enums::PaymentMethodType,
        payment_method_details: PaymentMethodDetails,
    ) {
        if let Some(payment_method_data) = self.get_mut(&payment_method) {
            payment_method_data.insert(payment_method_type, payment_method_details);
        } else {
            let mut payment_method_type_metadata = PaymentMethodTypeMetadata::new();
            payment_method_type_metadata.insert(payment_method_type, payment_method_details);

            self.insert(payment_method, payment_method_type_metadata);
        }
    }
}

#[derive(Debug, Clone)]
pub enum VaultResponseData {
    ExternalVaultCreateResponse {
        session_id: masking::Secret<String>,
        client_secret: masking::Secret<String>,
    },
    ExternalVaultInsertResponse {
        connector_vault_id: VaultIdType,
        fingerprint_id: String,
    },
    ExternalVaultRetrieveResponse {
        vault_data: PaymentMethodVaultingData,
    },
    ExternalVaultDeleteResponse {
        connector_vault_id: String,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VaultIdType {
    SingleVaultId(String),
    MultiVauldIds(MultiVaultIdType),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MultiVaultIdType {
    Card {
        tokenized_card_number: Option<masking::Secret<String>>,
        tokenized_card_expiry_year: Option<masking::Secret<String>>,
        tokenized_card_expiry_month: Option<masking::Secret<String>>,
        tokenized_card_cvc: Option<masking::Secret<String>>,
    },
    NetworkToken {
        tokenized_network_token: Option<masking::Secret<String>>,
        tokenized_network_token_exp_year: Option<masking::Secret<String>>,
        tokenized_network_token_exp_month: Option<masking::Secret<String>>,
        tokenized_cryptogram: Option<masking::Secret<String>>,
    },
}

impl VaultIdType {
    pub fn get_single_vault_id(&self) -> Result<String, error_stack::Report<ApiErrorResponse>> {
        match self {
            Self::SingleVaultId(vault_id) => Ok(vault_id.to_string()),
            Self::MultiVauldIds(_) => Err(ApiErrorResponse::MissingRequiredField {
                field_name: "SingleVaultId",
            }
            .into()),
        }
    }

    #[cfg(feature = "v1")]
    pub fn get_auth_vault_token_data(
        &self,
    ) -> Result<
        api_models::authentication::AuthenticationVaultTokenData,
        error_stack::Report<ApiErrorResponse>,
    > {
        match self.clone() {
            Self::MultiVauldIds(multi_vault_data) => match multi_vault_data {
                MultiVaultIdType::Card {
                    tokenized_card_number,
                    tokenized_card_expiry_year,
                    tokenized_card_expiry_month,
                    tokenized_card_cvc,
                } => Ok(
                    api_models::authentication::AuthenticationVaultTokenData::CardData {
                        tokenized_card_number,
                        tokenized_card_expiry_month,
                        tokenized_card_expiry_year,
                        tokenized_card_cvc,
                    },
                ),
                MultiVaultIdType::NetworkToken {
                    tokenized_network_token,
                    tokenized_network_token_exp_month,
                    tokenized_network_token_exp_year,
                    tokenized_cryptogram,
                } => Ok(
                    api_models::authentication::AuthenticationVaultTokenData::NetworkTokenData {
                        tokenized_network_token,
                        tokenized_expiry_month: tokenized_network_token_exp_month,
                        tokenized_expiry_year: tokenized_network_token_exp_year,
                        tokenized_cryptogram,
                    },
                ),
            },
            Self::SingleVaultId(_) => Err(ApiErrorResponse::InternalServerError)
                .attach_printable("Unexpected Behaviour, Multi Token Data is missing"),
        }
    }
}

impl Default for VaultResponseData {
    fn default() -> Self {
        Self::ExternalVaultInsertResponse {
            connector_vault_id: VaultIdType::SingleVaultId(String::new()),
            fingerprint_id: String::new(),
        }
    }
}


// File: crates/hyperswitch_domain_models/src/gsm.rs

use common_utils::{errors::ValidationError, ext_traits::StringExt};
use serde::{self, Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct GatewayStatusMap {
    pub connector: String,
    pub flow: String,
    pub sub_flow: String,
    pub code: String,
    pub message: String,
    pub status: String,
    pub router_error: Option<String>,
    pub unified_code: Option<String>,
    pub unified_message: Option<String>,
    pub error_category: Option<common_enums::ErrorCategory>,
    pub feature_data: common_types::domain::GsmFeatureData,
    pub feature: common_enums::GsmFeature,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GatewayStatusMappingUpdate {
    pub status: Option<String>,
    pub router_error: Option<Option<String>>,
    pub decision: Option<common_enums::GsmDecision>,
    pub step_up_possible: Option<bool>,
    pub unified_code: Option<String>,
    pub unified_message: Option<String>,
    pub error_category: Option<common_enums::ErrorCategory>,
    pub clear_pan_possible: Option<bool>,
    pub feature_data: Option<common_types::domain::GsmFeatureData>,
    pub feature: Option<common_enums::GsmFeature>,
}

impl TryFrom<GatewayStatusMap> for diesel_models::gsm::GatewayStatusMappingNew {
    type Error = error_stack::Report<ValidationError>;

    fn try_from(value: GatewayStatusMap) -> Result<Self, Self::Error> {
        Ok(Self {
            connector: value.connector.to_string(),
            flow: value.flow,
            sub_flow: value.sub_flow,
            code: value.code,
            message: value.message,
            status: value.status.clone(),
            router_error: value.router_error,
            decision: value.feature_data.get_decision().to_string(),
            step_up_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|retry_feature_data| retry_feature_data.is_step_up_possible())
                .unwrap_or(false),
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            clear_pan_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|retry_feature_data| retry_feature_data.is_clear_pan_possible())
                .unwrap_or(false),
            feature_data: Some(value.feature_data),
            feature: Some(value.feature),
        })
    }
}

impl TryFrom<GatewayStatusMappingUpdate> for diesel_models::gsm::GatewayStatusMappingUpdate {
    type Error = error_stack::Report<ValidationError>;

    fn try_from(value: GatewayStatusMappingUpdate) -> Result<Self, Self::Error> {
        Ok(Self {
            status: value.status,
            router_error: value.router_error,
            decision: value.decision.map(|gsm_decision| gsm_decision.to_string()),
            step_up_possible: value.step_up_possible,
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            clear_pan_possible: value.clear_pan_possible,
            feature_data: value.feature_data,
            feature: value.feature,
        })
    }
}

impl TryFrom<diesel_models::gsm::GatewayStatusMap> for GatewayStatusMap {
    type Error = ValidationError;

    fn try_from(item: diesel_models::gsm::GatewayStatusMap) -> Result<Self, Self::Error> {
        let decision =
            StringExt::<common_enums::GsmDecision>::parse_enum(item.decision, "GsmDecision")
                .map_err(|_| ValidationError::InvalidValue {
                    message: "Failed to parse GsmDecision".to_string(),
                })?;
        let db_feature_data = item.feature_data;

        // The only case where `FeatureData` can be null is for legacy records
        // (i.e., records created before `FeatureData` and related features were introduced).
        // At that time, the only supported feature was `Retry`, so it's safe to default to it.
        let feature_data = match db_feature_data {
            Some(common_types::domain::GsmFeatureData::Retry(data)) => {
                common_types::domain::GsmFeatureData::Retry(data)
            }
            None => common_types::domain::GsmFeatureData::Retry(
                common_types::domain::RetryFeatureData {
                    step_up_possible: item.step_up_possible,
                    clear_pan_possible: item.clear_pan_possible,
                    alternate_network_possible: false,
                    decision,
                },
            ),
        };

        let feature = item.feature.unwrap_or(common_enums::GsmFeature::Retry);
        Ok(Self {
            connector: item.connector,
            flow: item.flow,
            sub_flow: item.sub_flow,
            code: item.code,
            message: item.message,
            status: item.status,
            router_error: item.router_error,
            unified_code: item.unified_code,
            unified_message: item.unified_message,
            error_category: item.error_category,
            feature_data,
            feature,
        })
    }
}


// File: crates/hyperswitch_domain_models/src/revenue_recovery.rs

use api_models::{payments as api_payments, webhooks};
use common_enums::enums as common_enums;
use common_types::primitive_wrappers;
use common_utils::{id_type, pii, types as util_types};
use time::PrimitiveDateTime;

use crate::{
    payments,
    router_response_types::revenue_recovery::{
        BillingConnectorInvoiceSyncResponse, BillingConnectorPaymentsSyncResponse,
    },
    ApiModelToDieselModelConvertor,
};

/// Recovery payload is unified struct constructed from billing connectors
#[derive(Debug)]
pub struct RevenueRecoveryAttemptData {
    /// transaction amount against invoice, accepted in minor unit.
    pub amount: util_types::MinorUnit,
    /// currency of the transaction
    pub currency: common_enums::Currency,
    /// merchant reference id at billing connector. ex: invoice_id
    pub merchant_reference_id: id_type::PaymentReferenceId,
    /// transaction id reference at payment connector
    pub connector_transaction_id: Option<util_types::ConnectorTransactionId>,
    /// error code sent by billing connector.
    pub error_code: Option<String>,
    /// error message sent by billing connector.
    pub error_message: Option<String>,
    /// mandate token at payment processor end.
    pub processor_payment_method_token: String,
    /// customer id at payment connector for which mandate is attached.
    pub connector_customer_id: String,
    /// Payment gateway identifier id at billing processor.
    pub connector_account_reference_id: String,
    /// timestamp at which transaction has been created at billing connector
    pub transaction_created_at: Option<PrimitiveDateTime>,
    /// transaction status at billing connector equivalent to payment attempt status.
    pub status: common_enums::AttemptStatus,
    /// payment method of payment attempt.
    pub payment_method_type: common_enums::PaymentMethod,
    /// payment method sub type of the payment attempt.
    pub payment_method_sub_type: common_enums::PaymentMethodType,
    /// This field can be returned for both approved and refused Mastercard payments.
    /// This code provides additional information about the type of transaction or the reason why the payment failed.
    /// If the payment failed, the network advice code gives guidance on if and when you can retry the payment.
    pub network_advice_code: Option<String>,
    /// For card errors resulting from a card issuer decline, a brand specific 2, 3, or 4 digit code which indicates the reason the authorization failed.
    pub network_decline_code: Option<String>,
    /// A string indicating how to proceed with an network error if payment gateway provide one. This is used to understand the network error code better.
    pub network_error_message: Option<String>,
    /// Number of attempts made for an invoice
    pub retry_count: Option<u16>,
    /// Time when next invoice will be generated which will be equal to the end time of the current invoice
    pub invoice_next_billing_time: Option<PrimitiveDateTime>,
    /// Time at which the invoice created
    pub invoice_billing_started_at_time: Option<PrimitiveDateTime>,
    /// stripe specific id used to validate duplicate attempts in revenue recovery flow
    pub charge_id: Option<String>,
    /// Additional card details
    pub card_info: api_payments::AdditionalCardInfo,
}

/// This is unified struct for Revenue Recovery Invoice Data and it is constructed from billing connectors
#[derive(Debug, Clone)]
pub struct RevenueRecoveryInvoiceData {
    /// invoice amount at billing connector
    pub amount: util_types::MinorUnit,
    /// currency of the amount.
    pub currency: common_enums::Currency,
    /// merchant reference id at billing connector. ex: invoice_id
    pub merchant_reference_id: id_type::PaymentReferenceId,
    /// billing address id of the invoice
    pub billing_address: Option<api_payments::Address>,
    /// Retry count of the invoice
    pub retry_count: Option<u16>,
    /// Ending date of the invoice or the Next billing time of the Subscription
    pub next_billing_at: Option<PrimitiveDateTime>,
    /// Invoice Starting Time
    pub billing_started_at: Option<PrimitiveDateTime>,
    /// metadata of the merchant
    pub metadata: Option<pii::SecretSerdeValue>,
    /// Allow partial authorization for this payment
    pub enable_partial_authorization: Option<primitive_wrappers::EnablePartialAuthorizationBool>,
}

#[derive(Clone, Debug)]
pub struct RecoveryPaymentIntent {
    pub payment_id: id_type::GlobalPaymentId,
    pub status: common_enums::IntentStatus,
    pub feature_metadata: Option<api_payments::FeatureMetadata>,
    pub merchant_id: id_type::MerchantId,
    pub merchant_reference_id: Option<id_type::PaymentReferenceId>,
    pub invoice_amount: util_types::MinorUnit,
    pub invoice_currency: common_enums::Currency,
    pub created_at: Option<PrimitiveDateTime>,
    pub billing_address: Option<api_payments::Address>,
}

#[derive(Clone, Debug)]
pub struct RecoveryPaymentAttempt {
    pub attempt_id: id_type::GlobalAttemptId,
    pub attempt_status: common_enums::AttemptStatus,
    pub feature_metadata: Option<api_payments::PaymentAttemptFeatureMetadata>,
    pub amount: util_types::MinorUnit,
    pub network_advice_code: Option<String>,
    pub network_decline_code: Option<String>,
    pub error_code: Option<String>,
    pub created_at: PrimitiveDateTime,
}

impl RecoveryPaymentAttempt {
    pub fn get_attempt_triggered_by(&self) -> Option<common_enums::TriggeredBy> {
        self.feature_metadata.as_ref().and_then(|metadata| {
            metadata
                .revenue_recovery
                .as_ref()
                .map(|recovery| recovery.attempt_triggered_by)
        })
    }
}

impl From<&RevenueRecoveryInvoiceData> for api_payments::AmountDetails {
    fn from(data: &RevenueRecoveryInvoiceData) -> Self {
        let amount = api_payments::AmountDetailsSetter {
            order_amount: data.amount.into(),
            currency: data.currency,
            shipping_cost: None,
            order_tax_amount: None,
            skip_external_tax_calculation: common_enums::TaxCalculationOverride::Skip,
            skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::Skip,
            surcharge_amount: None,
            tax_on_surcharge: None,
        };
        Self::new(amount)
    }
}

impl From<&RevenueRecoveryInvoiceData> for api_payments::PaymentsCreateIntentRequest {
    fn from(data: &RevenueRecoveryInvoiceData) -> Self {
        let amount_details = api_payments::AmountDetails::from(data);
        Self {
            amount_details,
            merchant_reference_id: Some(data.merchant_reference_id.clone()),
            routing_algorithm_id: None,
            // Payments in the revenue recovery flow are always recurring transactions,
            // so capture method will be always automatic.
            capture_method: Some(common_enums::CaptureMethod::Automatic),
            authentication_type: Some(common_enums::AuthenticationType::NoThreeDs),
            billing: data.billing_address.clone(),
            shipping: None,
            customer_id: None,
            customer_present: Some(common_enums::PresenceOfCustomerDuringPayment::Absent),
            description: None,
            return_url: None,
            setup_future_usage: Some(common_enums::FutureUsage::OffSession),
            apply_mit_exemption: None,
            statement_descriptor: None,
            order_details: None,
            allowed_payment_method_types: None,
            metadata: data.metadata.clone(),
            connector_metadata: None,
            feature_metadata: None,
            payment_link_enabled: None,
            payment_link_config: None,
            request_incremental_authorization: None,
            session_expiry: None,
            frm_metadata: None,
            request_external_three_ds_authentication: None,
            force_3ds_challenge: None,
            merchant_connector_details: None,
            enable_partial_authorization: data.enable_partial_authorization,
        }
    }
}

impl From<&BillingConnectorInvoiceSyncResponse> for RevenueRecoveryInvoiceData {
    fn from(data: &BillingConnectorInvoiceSyncResponse) -> Self {
        Self {
            amount: data.amount,
            currency: data.currency,
            merchant_reference_id: data.merchant_reference_id.clone(),
            billing_address: data.billing_address.clone(),
            retry_count: data.retry_count,
            next_billing_at: data.ends_at,
            billing_started_at: data.created_at,
            metadata: None,
            enable_partial_authorization: None,
        }
    }
}

impl
    From<(
        &BillingConnectorPaymentsSyncResponse,
        &RevenueRecoveryInvoiceData,
    )> for RevenueRecoveryAttemptData
{
    fn from(
        data: (
            &BillingConnectorPaymentsSyncResponse,
            &RevenueRecoveryInvoiceData,
        ),
    ) -> Self {
        let billing_connector_payment_details = data.0;
        let invoice_details = data.1;
        Self {
            amount: billing_connector_payment_details.amount,
            currency: billing_connector_payment_details.currency,
            merchant_reference_id: billing_connector_payment_details
                .merchant_reference_id
                .clone(),
            connector_transaction_id: billing_connector_payment_details
                .connector_transaction_id
                .clone(),
            error_code: billing_connector_payment_details.error_code.clone(),
            error_message: billing_connector_payment_details.error_message.clone(),
            processor_payment_method_token: billing_connector_payment_details
                .processor_payment_method_token
                .clone(),
            connector_customer_id: billing_connector_payment_details
                .connector_customer_id
                .clone(),
            connector_account_reference_id: billing_connector_payment_details
                .connector_account_reference_id
                .clone(),
            transaction_created_at: billing_connector_payment_details.transaction_created_at,
            status: billing_connector_payment_details.status,
            payment_method_type: billing_connector_payment_details.payment_method_type,
            payment_method_sub_type: billing_connector_payment_details.payment_method_sub_type,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            retry_count: invoice_details.retry_count,
            invoice_next_billing_time: invoice_details.next_billing_at,
            charge_id: billing_connector_payment_details.charge_id.clone(),
            invoice_billing_started_at_time: invoice_details.billing_started_at,
            card_info: billing_connector_payment_details.card_info.clone(),
        }
    }
}

impl From<&RevenueRecoveryAttemptData> for api_payments::PaymentAttemptAmountDetails {
    fn from(data: &RevenueRecoveryAttemptData) -> Self {
        Self {
            net_amount: data.amount,
            amount_to_capture: None,
            surcharge_amount: None,
            tax_on_surcharge: None,
            amount_capturable: data.amount,
            shipping_cost: None,
            order_tax_amount: None,
        }
    }
}

impl From<&RevenueRecoveryAttemptData> for Option<api_payments::RecordAttemptErrorDetails> {
    fn from(data: &RevenueRecoveryAttemptData) -> Self {
        data.error_code
            .as_ref()
            .zip(data.error_message.clone())
            .map(|(code, message)| api_payments::RecordAttemptErrorDetails {
                code: code.to_string(),
                message: message.to_string(),
                network_advice_code: data.network_advice_code.clone(),
                network_decline_code: data.network_decline_code.clone(),
                network_error_message: data.network_error_message.clone(),
            })
    }
}

impl From<&payments::PaymentIntent> for RecoveryPaymentIntent {
    fn from(payment_intent: &payments::PaymentIntent) -> Self {
        Self {
            payment_id: payment_intent.id.clone(),
            status: payment_intent.status,
            feature_metadata: payment_intent
                .feature_metadata
                .clone()
                .map(|feature_metadata| feature_metadata.convert_back()),
            merchant_reference_id: payment_intent.merchant_reference_id.clone(),
            invoice_amount: payment_intent.amount_details.order_amount,
            invoice_currency: payment_intent.amount_details.currency,
            billing_address: payment_intent
                .billing_address
                .clone()
                .map(|address| api_payments::Address::from(address.into_inner())),
            merchant_id: payment_intent.merchant_id.clone(),
            created_at: Some(payment_intent.created_at),
        }
    }
}

impl From<&payments::payment_attempt::PaymentAttempt> for RecoveryPaymentAttempt {
    fn from(payment_attempt: &payments::payment_attempt::PaymentAttempt) -> Self {
        Self {
            attempt_id: payment_attempt.id.clone(),
            attempt_status: payment_attempt.status,
            feature_metadata: payment_attempt
                .feature_metadata
                .clone()
                .map(
                    |feature_metadata| api_payments::PaymentAttemptFeatureMetadata {
                        revenue_recovery: feature_metadata.revenue_recovery.map(|recovery| {
                            api_payments::PaymentAttemptRevenueRecoveryData {
                                attempt_triggered_by: recovery.attempt_triggered_by,
                                charge_id: recovery.charge_id,
                            }
                        }),
                    },
                ),
            amount: payment_attempt.amount_details.get_net_amount(),
            network_advice_code: payment_attempt
                .error
                .clone()
                .and_then(|error| error.network_advice_code),
            network_decline_code: payment_attempt
                .error
                .clone()
                .and_then(|error| error.network_decline_code),
            error_code: payment_attempt
                .error
                .as_ref()
                .map(|error| error.code.clone()),
            created_at: payment_attempt.created_at,
        }
    }
}


// File: crates/hyperswitch_domain_models/src/authentication.rs

use common_utils::{
    crypto::Encryptable, encryption::Encryption, errors::CustomResult, pii,
    types::keymanager::ToEncryptable,
};
use masking::Secret;
use rustc_hash::FxHashMap;
use serde_json::Value;

#[cfg(feature = "v1")]
#[derive(Clone, Debug, router_derive::ToEncryption, serde::Serialize)]
pub struct Authentication {
    pub authentication_id: common_utils::id_type::AuthenticationId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub authentication_connector: Option<String>,
    pub connector_authentication_id: Option<String>,
    pub authentication_data: Option<Value>,
    pub payment_method_id: String,
    pub authentication_type: Option<common_enums::DecoupledAuthenticationType>,
    pub authentication_status: common_enums::AuthenticationStatus,
    pub authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: time::PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub modified_at: time::PrimitiveDateTime,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
    pub connector_metadata: Option<Value>,
    pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
    pub threeds_server_transaction_id: Option<String>,
    pub cavv: Option<String>,
    pub authentication_flow_type: Option<String>,
    pub message_version: Option<common_utils::types::SemanticVersion>,
    pub eci: Option<String>,
    pub trans_status: Option<common_enums::TransactionStatus>,
    pub acquirer_bin: Option<String>,
    pub acquirer_merchant_id: Option<String>,
    pub three_ds_method_data: Option<String>,
    pub three_ds_method_url: Option<String>,
    pub acs_url: Option<String>,
    pub challenge_request: Option<String>,
    pub acs_reference_number: Option<String>,
    pub acs_trans_id: Option<String>,
    pub acs_signed_content: Option<String>,
    pub profile_id: common_utils::id_type::ProfileId,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub ds_trans_id: Option<String>,
    pub directory_server_id: Option<String>,
    pub acquirer_country_code: Option<String>,
    pub service_details: Option<Value>,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub authentication_client_secret: Option<String>,
    pub force_3ds_challenge: Option<bool>,
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    pub return_url: Option<String>,
    pub amount: Option<common_utils::types::MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    #[encrypt(ty = Value)]
    pub billing_address: Option<Encryptable<crate::address::Address>>,
    #[encrypt(ty = Value)]
    pub shipping_address: Option<Encryptable<crate::address::Address>>,
    pub browser_info: Option<Value>,
    pub email: Option<Encryptable<Secret<String, pii::EmailStrategy>>>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct PgRedirectResponseForAuthentication {
    pub authentication_id: common_utils::id_type::AuthenticationId,
    pub status: common_enums::TransactionStatus,
    pub gateway_id: String,
    pub customer_id: Option<common_utils::id_type::CustomerId>,
    pub amount: Option<common_utils::types::MinorUnit>,
}