File: crates/hyperswitch_domain_models/src/merchant_connector_account.rs
 Public functions: 29
 Public structs: 8

#[cfg(feature = "v2")]
use std::collections::HashMap;

use common_utils::{
    crypto::Encryptable,
    date_time,
    encryption::Encryption,
    errors::{CustomResult, ValidationError},
    ext_traits::ValueExt,
    id_type, pii, type_name,
    types::keymanager::{Identifier, KeyManagerState, ToEncryptable},
};
#[cfg(feature = "v2")]
use diesel_models::merchant_connector_account::{
    BillingAccountReference as DieselBillingAccountReference,
    MerchantConnectorAccountFeatureMetadata as DieselMerchantConnectorAccountFeatureMetadata,
    RevenueRecoveryMetadata as DieselRevenueRecoveryMetadata,
};
use diesel_models::{enums, merchant_connector_account::MerchantConnectorAccountUpdateInternal};
use error_stack::ResultExt;
use masking::{PeekInterface, Secret};
use rustc_hash::FxHashMap;
use serde_json::Value;

use super::behaviour;
#[cfg(feature = "v2")]
use crate::errors::api_error_response;
use crate::{
    mandates::CommonMandateReference,
    router_data,
    type_encryption::{crypto_operation, CryptoOperation},
};

#[cfg(feature = "v1")]
#[derive(Clone, Debug, router_derive::ToEncryption)]
pub struct MerchantConnectorAccount {
    pub merchant_id: id_type::MerchantId,
    pub connector_name: String,
    #[encrypt]
    pub connector_account_details: Encryptable<Secret<Value>>,
    pub test_mode: Option<bool>,
    pub disabled: Option<bool>,
    pub merchant_connector_id: id_type::MerchantConnectorAccountId,
    pub payment_methods_enabled: Option<Vec<pii::SecretSerdeValue>>,
    pub connector_type: enums::ConnectorType,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub frm_configs: Option<Vec<pii::SecretSerdeValue>>,
    pub connector_label: Option<String>,
    pub business_country: Option<enums::CountryAlpha2>,
    pub business_label: Option<String>,
    pub business_sub_label: Option<String>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub connector_webhook_details: Option<pii::SecretSerdeValue>,
    pub profile_id: id_type::ProfileId,
    pub applepay_verified_domains: Option<Vec<String>>,
    pub pm_auth_config: Option<pii::SecretSerdeValue>,
    pub status: enums::ConnectorStatus,
    #[encrypt]
    pub connector_wallets_details: Option<Encryptable<Secret<Value>>>,
    #[encrypt]
    pub additional_merchant_data: Option<Encryptable<Secret<Value>>>,
    pub version: common_enums::ApiVersion,
}

#[cfg(feature = "v1")]
impl MerchantConnectorAccount {
    pub fn get_id(&self) -> id_type::MerchantConnectorAccountId {
        self.merchant_connector_id.clone()
    }
    pub fn get_connector_account_details(
        &self,
    ) -> error_stack::Result<router_data::ConnectorAuthType, common_utils::errors::ParsingError>
    {
        self.connector_account_details
            .get_inner()
            .clone()
            .parse_value("ConnectorAuthType")
    }

    pub fn get_connector_wallets_details(&self) -> Option<Secret<Value>> {
        self.connector_wallets_details.as_deref().cloned()
    }

    pub fn get_connector_test_mode(&self) -> Option<bool> {
        self.test_mode
    }

    pub fn get_connector_name_as_string(&self) -> String {
        self.connector_name.clone()
    }

    pub fn get_metadata(&self) -> Option<Secret<Value>> {
        self.metadata.clone()
    }
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug)]
pub enum MerchantConnectorAccountTypeDetails {
    MerchantConnectorAccount(Box<MerchantConnectorAccount>),
    MerchantConnectorDetails(common_types::domain::MerchantConnectorAuthDetails),
}

#[cfg(feature = "v2")]
impl MerchantConnectorAccountTypeDetails {
    pub fn get_connector_account_details(
        &self,
    ) -> error_stack::Result<router_data::ConnectorAuthType, common_utils::errors::ParsingError>
    {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                merchant_connector_account
                    .connector_account_details
                    .peek()
                    .clone()
                    .parse_value("ConnectorAuthType")
            }
            Self::MerchantConnectorDetails(merchant_connector_details) => {
                merchant_connector_details
                    .merchant_connector_creds
                    .peek()
                    .clone()
                    .parse_value("ConnectorAuthType")
            }
        }
    }

    pub fn is_disabled(&self) -> bool {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                merchant_connector_account.disabled.unwrap_or(false)
            }
            Self::MerchantConnectorDetails(_) => false,
        }
    }

    pub fn get_metadata(&self) -> Option<Secret<Value>> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                merchant_connector_account.metadata.to_owned()
            }
            Self::MerchantConnectorDetails(_) => None,
        }
    }

    pub fn get_id(&self) -> Option<id_type::MerchantConnectorAccountId> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                Some(merchant_connector_account.id.clone())
            }
            Self::MerchantConnectorDetails(_) => None,
        }
    }

    pub fn get_mca_id(&self) -> Option<id_type::MerchantConnectorAccountId> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                Some(merchant_connector_account.get_id())
            }
            Self::MerchantConnectorDetails(_) => None,
        }
    }

    pub fn get_connector_name(&self) -> Option<common_enums::connector_enums::Connector> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                Some(merchant_connector_account.connector_name)
            }
            Self::MerchantConnectorDetails(merchant_connector_details) => {
                Some(merchant_connector_details.connector_name)
            }
        }
    }

    pub fn get_inner_db_merchant_connector_account(&self) -> Option<&MerchantConnectorAccount> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                Some(merchant_connector_account)
            }
            Self::MerchantConnectorDetails(_) => None,
        }
    }
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, router_derive::ToEncryption)]
pub struct MerchantConnectorAccount {
    pub id: id_type::MerchantConnectorAccountId,
    pub merchant_id: id_type::MerchantId,
    pub connector_name: common_enums::connector_enums::Connector,
    #[encrypt]
    pub connector_account_details: Encryptable<Secret<Value>>,
    pub disabled: Option<bool>,
    pub payment_methods_enabled: Option<Vec<common_types::payment_methods::PaymentMethodsEnabled>>,
    pub connector_type: enums::ConnectorType,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub frm_configs: Option<Vec<pii::SecretSerdeValue>>,
    pub connector_label: Option<String>,
    pub created_at: time::PrimitiveDateTime,
    pub modified_at: time::PrimitiveDateTime,
    pub connector_webhook_details: Option<pii::SecretSerdeValue>,
    pub profile_id: id_type::ProfileId,
    pub applepay_verified_domains: Option<Vec<String>>,
    pub pm_auth_config: Option<pii::SecretSerdeValue>,
    pub status: enums::ConnectorStatus,
    #[encrypt]
    pub connector_wallets_details: Option<Encryptable<Secret<Value>>>,
    #[encrypt]
    pub additional_merchant_data: Option<Encryptable<Secret<Value>>>,
    pub version: common_enums::ApiVersion,
    pub feature_metadata: Option<MerchantConnectorAccountFeatureMetadata>,
}

#[cfg(feature = "v2")]
impl MerchantConnectorAccount {
    pub fn get_retry_threshold(&self) -> Option<u16> {
        self.feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.revenue_recovery.as_ref())
            .map(|recovery| recovery.billing_connector_retry_threshold)
    }

    pub fn get_id(&self) -> id_type::MerchantConnectorAccountId {
        self.id.clone()
    }

    pub fn get_metadata(&self) -> Option<pii::SecretSerdeValue> {
        self.metadata.clone()
    }

    pub fn is_disabled(&self) -> bool {
        self.disabled.unwrap_or(false)
    }

    pub fn get_connector_account_details(
        &self,
    ) -> error_stack::Result<router_data::ConnectorAuthType, common_utils::errors::ParsingError>
    {
        use common_utils::ext_traits::ValueExt;

        self.connector_account_details
            .get_inner()
            .clone()
            .parse_value("ConnectorAuthType")
    }

    pub fn get_connector_wallets_details(&self) -> Option<Secret<Value>> {
        self.connector_wallets_details.as_deref().cloned()
    }

    pub fn get_connector_test_mode(&self) -> Option<bool> {
        todo!()
    }

    pub fn get_connector_name_as_string(&self) -> String {
        self.connector_name.clone().to_string()
    }

    pub fn get_payment_merchant_connector_account_id_using_account_reference_id(
        &self,
        account_reference_id: String,
    ) -> Option<id_type::MerchantConnectorAccountId> {
        self.feature_metadata.as_ref().and_then(|metadata| {
            metadata.revenue_recovery.as_ref().and_then(|recovery| {
                recovery
                    .mca_reference
                    .billing_to_recovery
                    .get(&account_reference_id)
                    .cloned()
            })
        })
    }
    pub fn get_account_reference_id_using_payment_merchant_connector_account_id(
        &self,
        payment_merchant_connector_account_id: id_type::MerchantConnectorAccountId,
    ) -> Option<String> {
        self.feature_metadata.as_ref().and_then(|metadata| {
            metadata.revenue_recovery.as_ref().and_then(|recovery| {
                recovery
                    .mca_reference
                    .recovery_to_billing
                    .get(&payment_merchant_connector_account_id)
                    .cloned()
            })
        })
    }
}

#[cfg(feature = "v2")]
/// Holds the payment methods enabled for a connector along with the connector name
/// This struct is a flattened representation of the payment methods enabled for a connector
#[derive(Debug)]
pub struct PaymentMethodsEnabledForConnector {
    pub payment_methods_enabled: common_types::payment_methods::RequestPaymentMethodTypes,
    pub payment_method: common_enums::PaymentMethod,
    pub connector: common_enums::connector_enums::Connector,
    pub merchant_connector_id: id_type::MerchantConnectorAccountId,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone)]
pub struct MerchantConnectorAccountFeatureMetadata {
    pub revenue_recovery: Option<RevenueRecoveryMetadata>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone)]
pub struct RevenueRecoveryMetadata {
    pub max_retry_count: u16,
    pub billing_connector_retry_threshold: u16,
    pub mca_reference: AccountReferenceMap,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ExternalVaultConnectorMetadata {
    pub proxy_url: common_utils::types::Url,
    pub certificate: Secret<String>,
}
#[cfg(feature = "v2")]
#[derive(Debug, Clone)]
pub struct AccountReferenceMap {
    pub recovery_to_billing: HashMap<id_type::MerchantConnectorAccountId, String>,
    pub billing_to_recovery: HashMap<String, id_type::MerchantConnectorAccountId>,
}

#[cfg(feature = "v2")]
impl AccountReferenceMap {
    pub fn new(
        hash_map: HashMap<id_type::MerchantConnectorAccountId, String>,
    ) -> Result<Self, api_error_response::ApiErrorResponse> {
        Self::validate(&hash_map)?;

        let recovery_to_billing = hash_map.clone();
        let mut billing_to_recovery = HashMap::new();

        for (key, value) in &hash_map {
            billing_to_recovery.insert(value.clone(), key.clone());
        }

        Ok(Self {
            recovery_to_billing,
            billing_to_recovery,
        })
    }

    fn validate(
        hash_map: &HashMap<id_type::MerchantConnectorAccountId, String>,
    ) -> Result<(), api_error_response::ApiErrorResponse> {
        let mut seen_values = std::collections::HashSet::new(); // To check uniqueness of values

        for value in hash_map.values() {
            if !seen_values.insert(value.clone()) {
                return Err(api_error_response::ApiErrorResponse::InvalidRequestData {
                    message: "Duplicate account reference IDs found in Recovery feature metadata. Each account reference ID must be unique.".to_string(),
                });
            }
        }
        Ok(())
    }
}

#[cfg(feature = "v2")]
/// Holds the payment methods enabled for a connector
pub struct FlattenedPaymentMethodsEnabled {
    pub payment_methods_enabled: Vec<PaymentMethodsEnabledForConnector>,
}

#[cfg(feature = "v2")]
impl FlattenedPaymentMethodsEnabled {
    /// This functions flattens the payment methods enabled from the connector accounts
    /// Retains the connector name and payment method in every flattened element
    pub fn from_payment_connectors_list(payment_connectors: Vec<MerchantConnectorAccount>) -> Self {
        let payment_methods_enabled_flattened_with_connector = payment_connectors
            .into_iter()
            .map(|connector| {
                (
                    connector
                        .payment_methods_enabled
                        .clone()
                        .unwrap_or_default(),
                    connector.connector_name,
                    connector.get_id(),
                )
            })
            .flat_map(
                |(payment_method_enabled, connector, merchant_connector_id)| {
                    payment_method_enabled
                        .into_iter()
                        .flat_map(move |payment_method| {
                            let request_payment_methods_enabled =
                                payment_method.payment_method_subtypes.unwrap_or_default();
                            let length = request_payment_methods_enabled.len();
                            request_payment_methods_enabled.into_iter().zip(
                                std::iter::repeat((
                                    connector,
                                    merchant_connector_id.clone(),
                                    payment_method.payment_method_type,
                                ))
                                .take(length),
                            )
                        })
                },
            )
            .map(
                |(request_payment_methods, (connector, merchant_connector_id, payment_method))| {
                    PaymentMethodsEnabledForConnector {
                        payment_methods_enabled: request_payment_methods,
                        connector,
                        payment_method,
                        merchant_connector_id,
                    }
                },
            )
            .collect();

        Self {
            payment_methods_enabled: payment_methods_enabled_flattened_with_connector,
        }
    }
}

#[cfg(feature = "v1")]
#[derive(Debug)]
pub enum MerchantConnectorAccountUpdate {
    Update {
        connector_type: Option<enums::ConnectorType>,
        connector_name: Option<String>,
        connector_account_details: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
        test_mode: Option<bool>,
        disabled: Option<bool>,
        merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
        payment_methods_enabled: Option<Vec<pii::SecretSerdeValue>>,
        metadata: Option<pii::SecretSerdeValue>,
        frm_configs: Option<Vec<pii::SecretSerdeValue>>,
        connector_webhook_details: Box<Option<pii::SecretSerdeValue>>,
        applepay_verified_domains: Option<Vec<String>>,
        pm_auth_config: Box<Option<pii::SecretSerdeValue>>,
        connector_label: Option<String>,
        status: Option<enums::ConnectorStatus>,
        connector_wallets_details: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
        additional_merchant_data: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
    },
    ConnectorWalletDetailsUpdate {
        connector_wallets_details: Encryptable<pii::SecretSerdeValue>,
    },
}

#[cfg(feature = "v2")]
#[derive(Debug)]
pub enum MerchantConnectorAccountUpdate {
    Update {
        connector_type: Option<enums::ConnectorType>,
        connector_account_details: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
        disabled: Option<bool>,
        payment_methods_enabled: Option<Vec<common_types::payment_methods::PaymentMethodsEnabled>>,
        metadata: Option<pii::SecretSerdeValue>,
        frm_configs: Option<Vec<pii::SecretSerdeValue>>,
        connector_webhook_details: Box<Option<pii::SecretSerdeValue>>,
        applepay_verified_domains: Option<Vec<String>>,
        pm_auth_config: Box<Option<pii::SecretSerdeValue>>,
        connector_label: Option<String>,
        status: Option<enums::ConnectorStatus>,
        connector_wallets_details: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
        additional_merchant_data: Box<Option<Encryptable<pii::SecretSerdeValue>>>,
        feature_metadata: Box<Option<MerchantConnectorAccountFeatureMetadata>>,
    },
    ConnectorWalletDetailsUpdate {
        connector_wallets_details: Encryptable<pii::SecretSerdeValue>,
    },
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl behaviour::Conversion for MerchantConnectorAccount {
    type DstType = diesel_models::merchant_connector_account::MerchantConnectorAccount;
    type NewDstType = diesel_models::merchant_connector_account::MerchantConnectorAccountNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(
            diesel_models::merchant_connector_account::MerchantConnectorAccount {
                merchant_id: self.merchant_id,
                connector_name: self.connector_name,
                connector_account_details: self.connector_account_details.into(),
                test_mode: self.test_mode,
                disabled: self.disabled,
                merchant_connector_id: self.merchant_connector_id.clone(),
                id: Some(self.merchant_connector_id),
                payment_methods_enabled: self.payment_methods_enabled,
                connector_type: self.connector_type,
                metadata: self.metadata,
                frm_configs: None,
                frm_config: self.frm_configs,
                business_country: self.business_country,
                business_label: self.business_label,
                connector_label: self.connector_label,
                business_sub_label: self.business_sub_label,
                created_at: self.created_at,
                modified_at: self.modified_at,
                connector_webhook_details: self.connector_webhook_details,
                profile_id: Some(self.profile_id),
                applepay_verified_domains: self.applepay_verified_domains,
                pm_auth_config: self.pm_auth_config,
                status: self.status,
                connector_wallets_details: self.connector_wallets_details.map(Encryption::from),
                additional_merchant_data: self.additional_merchant_data.map(|data| data.into()),
                version: self.version,
            },
        )
    }

    async fn convert_back(
        state: &KeyManagerState,
        other: Self::DstType,
        key: &Secret<Vec<u8>>,
        _key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError> {
        let identifier = Identifier::Merchant(other.merchant_id.clone());
        let decrypted_data = crypto_operation(
            state,
            type_name!(Self::DstType),
            CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable(
                EncryptedMerchantConnectorAccount {
                    connector_account_details: other.connector_account_details,
                    additional_merchant_data: other.additional_merchant_data,
                    connector_wallets_details: other.connector_wallets_details,
                },
            )),
            identifier.clone(),
            key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting connector account details".to_string(),
        })?;

        let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data)
            .change_context(ValidationError::InvalidValue {
                message: "Failed while decrypting connector account details".to_string(),
            })?;

        Ok(Self {
            merchant_id: other.merchant_id,
            connector_name: other.connector_name,
            connector_account_details: decrypted_data.connector_account_details,
            test_mode: other.test_mode,
            disabled: other.disabled,
            merchant_connector_id: other.merchant_connector_id,
            payment_methods_enabled: other.payment_methods_enabled,
            connector_type: other.connector_type,
            metadata: other.metadata,

            frm_configs: other.frm_config,
            business_country: other.business_country,
            business_label: other.business_label,
            connector_label: other.connector_label,
            business_sub_label: other.business_sub_label,
            created_at: other.created_at,
            modified_at: other.modified_at,
            connector_webhook_details: other.connector_webhook_details,
            profile_id: other
                .profile_id
                .ok_or(ValidationError::MissingRequiredField {
                    field_name: "profile_id".to_string(),
                })?,
            applepay_verified_domains: other.applepay_verified_domains,
            pm_auth_config: other.pm_auth_config,
            status: other.status,
            connector_wallets_details: decrypted_data.connector_wallets_details,
            additional_merchant_data: decrypted_data.additional_merchant_data,
            version: other.version,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(Self::NewDstType {
            merchant_id: Some(self.merchant_id),
            connector_name: Some(self.connector_name),
            connector_account_details: Some(self.connector_account_details.into()),
            test_mode: self.test_mode,
            disabled: self.disabled,
            merchant_connector_id: self.merchant_connector_id.clone(),
            id: Some(self.merchant_connector_id),
            payment_methods_enabled: self.payment_methods_enabled,
            connector_type: Some(self.connector_type),
            metadata: self.metadata,
            frm_configs: None,
            frm_config: self.frm_configs,
            business_country: self.business_country,
            business_label: self.business_label,
            connector_label: self.connector_label,
            business_sub_label: self.business_sub_label,
            created_at: now,
            modified_at: now,
            connector_webhook_details: self.connector_webhook_details,
            profile_id: Some(self.profile_id),
            applepay_verified_domains: self.applepay_verified_domains,
            pm_auth_config: self.pm_auth_config,
            status: self.status,
            connector_wallets_details: self.connector_wallets_details.map(Encryption::from),
            additional_merchant_data: self.additional_merchant_data.map(|data| data.into()),
            version: self.version,
        })
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl behaviour::Conversion for MerchantConnectorAccount {
    type DstType = diesel_models::merchant_connector_account::MerchantConnectorAccount;
    type NewDstType = diesel_models::merchant_connector_account::MerchantConnectorAccountNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(
            diesel_models::merchant_connector_account::MerchantConnectorAccount {
                id: self.id,
                merchant_id: self.merchant_id,
                connector_name: self.connector_name,
                connector_account_details: self.connector_account_details.into(),
                disabled: self.disabled,
                payment_methods_enabled: self.payment_methods_enabled,
                connector_type: self.connector_type,
                metadata: self.metadata,
                frm_config: self.frm_configs,
                connector_label: self.connector_label,
                created_at: self.created_at,
                modified_at: self.modified_at,
                connector_webhook_details: self.connector_webhook_details,
                profile_id: self.profile_id,
                applepay_verified_domains: self.applepay_verified_domains,
                pm_auth_config: self.pm_auth_config,
                status: self.status,
                connector_wallets_details: self.connector_wallets_details.map(Encryption::from),
                additional_merchant_data: self.additional_merchant_data.map(|data| data.into()),
                version: self.version,
                feature_metadata: self.feature_metadata.map(From::from),
            },
        )
    }

    async fn convert_back(
        state: &KeyManagerState,
        other: Self::DstType,
        key: &Secret<Vec<u8>>,
        _key_manager_identifier: Identifier,
    ) -> CustomResult<Self, ValidationError> {
        let identifier = Identifier::Merchant(other.merchant_id.clone());

        let decrypted_data = crypto_operation(
            state,
            type_name!(Self::DstType),
            CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable(
                EncryptedMerchantConnectorAccount {
                    connector_account_details: other.connector_account_details,
                    additional_merchant_data: other.additional_merchant_data,
                    connector_wallets_details: other.connector_wallets_details,
                },
            )),
            identifier.clone(),
            key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(ValidationError::InvalidValue {
            message: "Failed while decrypting connector account details".to_string(),
        })?;

        let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data)
            .change_context(ValidationError::InvalidValue {
                message: "Failed while decrypting connector account details".to_string(),
            })?;

        Ok(Self {
            id: other.id,
            merchant_id: other.merchant_id,
            connector_name: other.connector_name,
            connector_account_details: decrypted_data.connector_account_details,
            disabled: other.disabled,
            payment_methods_enabled: other.payment_methods_enabled,
            connector_type: other.connector_type,
            metadata: other.metadata,

            frm_configs: other.frm_config,
            connector_label: other.connector_label,
            created_at: other.created_at,
            modified_at: other.modified_at,
            connector_webhook_details: other.connector_webhook_details,
            profile_id: other.profile_id,
            applepay_verified_domains: other.applepay_verified_domains,
            pm_auth_config: other.pm_auth_config,
            status: other.status,
            connector_wallets_details: decrypted_data.connector_wallets_details,
            additional_merchant_data: decrypted_data.additional_merchant_data,
            version: other.version,
            feature_metadata: other.feature_metadata.map(From::from),
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        let now = date_time::now();
        Ok(Self::NewDstType {
            id: self.id,
            merchant_id: Some(self.merchant_id),
            connector_name: Some(self.connector_name),
            connector_account_details: Some(self.connector_account_details.into()),
            disabled: self.disabled,
            payment_methods_enabled: self.payment_methods_enabled,
            connector_type: Some(self.connector_type),
            metadata: self.metadata,
            frm_config: self.frm_configs,
            connector_label: self.connector_label,
            created_at: now,
            modified_at: now,
            connector_webhook_details: self.connector_webhook_details,
            profile_id: self.profile_id,
            applepay_verified_domains: self.applepay_verified_domains,
            pm_auth_config: self.pm_auth_config,
            status: self.status,
            connector_wallets_details: self.connector_wallets_details.map(Encryption::from),
            additional_merchant_data: self.additional_merchant_data.map(|data| data.into()),
            version: self.version,
            feature_metadata: self.feature_metadata.map(From::from),
        })
    }
}

#[cfg(feature = "v1")]
impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInternal {
    fn from(merchant_connector_account_update: MerchantConnectorAccountUpdate) -> Self {
        match merchant_connector_account_update {
            MerchantConnectorAccountUpdate::Update {
                connector_type,
                connector_name,
                connector_account_details,
                test_mode,
                disabled,
                merchant_connector_id,
                payment_methods_enabled,
                metadata,
                frm_configs,
                connector_webhook_details,
                applepay_verified_domains,
                pm_auth_config,
                connector_label,
                status,
                connector_wallets_details,
                additional_merchant_data,
            } => Self {
                connector_type,
                connector_name,
                connector_account_details: connector_account_details.map(Encryption::from),
                test_mode,
                disabled,
                merchant_connector_id,
                payment_methods_enabled,
                metadata,
                frm_configs: None,
                frm_config: frm_configs,
                modified_at: Some(date_time::now()),
                connector_webhook_details: *connector_webhook_details,
                applepay_verified_domains,
                pm_auth_config: *pm_auth_config,
                connector_label,
                status,
                connector_wallets_details: connector_wallets_details.map(Encryption::from),
                additional_merchant_data: additional_merchant_data.map(Encryption::from),
            },
            MerchantConnectorAccountUpdate::ConnectorWalletDetailsUpdate {
                connector_wallets_details,
            } => Self {
                connector_wallets_details: Some(Encryption::from(connector_wallets_details)),
                connector_type: None,
                connector_name: None,
                connector_account_details: None,
                connector_label: None,
                test_mode: None,
                disabled: None,
                merchant_connector_id: None,
                payment_methods_enabled: None,
                frm_configs: None,
                metadata: None,
                modified_at: None,
                connector_webhook_details: None,
                frm_config: None,
                applepay_verified_domains: None,
                pm_auth_config: None,
                status: None,
                additional_merchant_data: None,
            },
        }
    }
}

#[cfg(feature = "v2")]
impl From<MerchantConnectorAccountUpdate> for MerchantConnectorAccountUpdateInternal {
    fn from(merchant_connector_account_update: MerchantConnectorAccountUpdate) -> Self {
        match merchant_connector_account_update {
            MerchantConnectorAccountUpdate::Update {
                connector_type,
                connector_account_details,
                disabled,
                payment_methods_enabled,
                metadata,
                frm_configs,
                connector_webhook_details,
                applepay_verified_domains,
                pm_auth_config,
                connector_label,
                status,
                connector_wallets_details,
                additional_merchant_data,
                feature_metadata,
            } => Self {
                connector_type,
                connector_account_details: connector_account_details.map(Encryption::from),
                disabled,
                payment_methods_enabled,
                metadata,
                frm_config: frm_configs,
                modified_at: Some(date_time::now()),
                connector_webhook_details: *connector_webhook_details,
                applepay_verified_domains,
                pm_auth_config: *pm_auth_config,
                connector_label,
                status,
                connector_wallets_details: connector_wallets_details.map(Encryption::from),
                additional_merchant_data: additional_merchant_data.map(Encryption::from),
                feature_metadata: feature_metadata.map(From::from),
            },
            MerchantConnectorAccountUpdate::ConnectorWalletDetailsUpdate {
                connector_wallets_details,
            } => Self {
                connector_wallets_details: Some(Encryption::from(connector_wallets_details)),
                connector_type: None,
                connector_account_details: None,
                connector_label: None,
                disabled: None,
                payment_methods_enabled: None,
                metadata: None,
                modified_at: None,
                connector_webhook_details: None,
                frm_config: None,
                applepay_verified_domains: None,
                pm_auth_config: None,
                status: None,
                additional_merchant_data: None,
                feature_metadata: None,
            },
        }
    }
}

common_utils::create_list_wrapper!(
    MerchantConnectorAccounts,
    MerchantConnectorAccount,
    impl_functions: {
        fn filter_and_map<'a, T>(
            &'a self,
            filter: impl Fn(&'a MerchantConnectorAccount) -> bool,
            func: impl Fn(&'a MerchantConnectorAccount) -> T,
        ) -> rustc_hash::FxHashSet<T>
        where
            T: std::hash::Hash + Eq,
        {
            self.0
                .iter()
                .filter(|mca| filter(mca))
                .map(func)
                .collect::<rustc_hash::FxHashSet<_>>()
        }

        pub fn filter_by_profile<'a, T>(
            &'a self,
            profile_id: &'a id_type::ProfileId,
            func: impl Fn(&'a MerchantConnectorAccount) -> T,
        ) -> rustc_hash::FxHashSet<T>
        where
            T: std::hash::Hash + Eq,
        {
            self.filter_and_map(|mca| mca.profile_id == *profile_id, func)
        }
        #[cfg(feature = "v2")]
        pub fn get_connector_and_supporting_payment_method_type_for_session_call(
            &self,
        ) -> Vec<(&MerchantConnectorAccount, common_enums::PaymentMethodType, common_enums::PaymentMethod)> {
            // This vector is created to work around lifetimes
            let ref_vector = Vec::default();

            let connector_and_supporting_payment_method_type = self.iter().flat_map(|connector_account| {
                connector_account
                    .payment_methods_enabled.as_ref()
                    .unwrap_or(&Vec::default())
                    .iter()
                    .flat_map(|payment_method_types| payment_method_types.payment_method_subtypes.as_ref().unwrap_or(&ref_vector).iter().map(|payment_method_subtype| (payment_method_subtype, payment_method_types.payment_method_type)).collect::<Vec<_>>())
                    .filter(|(payment_method_types_enabled, _)| {
                        payment_method_types_enabled.payment_experience == Some(api_models::enums::PaymentExperience::InvokeSdkClient)
                    })
                    .map(|(payment_method_subtypes, payment_method_type)| {
                        (connector_account, payment_method_subtypes.payment_method_subtype, payment_method_type)
                    })
                    .collect::<Vec<_>>()
            }).collect();
            connector_and_supporting_payment_method_type
        }
        pub fn filter_based_on_profile_and_connector_type(
            self,
            profile_id: &id_type::ProfileId,
            connector_type: common_enums::ConnectorType,
        ) -> Self {
            self.into_iter()
                .filter(|mca| &mca.profile_id == profile_id && mca.connector_type == connector_type)
                .collect()
        }
        pub fn is_merchant_connector_account_id_in_connector_mandate_details(
            &self,
            profile_id: Option<&id_type::ProfileId>,
            connector_mandate_details: &CommonMandateReference,
        ) -> bool {
            let mca_ids = self
                .iter()
                .filter(|mca| {
                    mca.disabled.is_some_and(|disabled| !disabled)
                        && profile_id.is_some_and(|profile_id| *profile_id == mca.profile_id)
                })
                .map(|mca| mca.get_id())
                .collect::<std::collections::HashSet<_>>();

            connector_mandate_details
            .payments
            .as_ref()
            .as_ref().is_some_and(|payments| {
                payments.0.keys().any(|mca_id| mca_ids.contains(mca_id))
            })
        }
    }
);

#[cfg(feature = "v2")]
impl From<MerchantConnectorAccountFeatureMetadata>
    for DieselMerchantConnectorAccountFeatureMetadata
{
    fn from(feature_metadata: MerchantConnectorAccountFeatureMetadata) -> Self {
        let revenue_recovery = feature_metadata.revenue_recovery.map(|recovery_metadata| {
            DieselRevenueRecoveryMetadata {
                max_retry_count: recovery_metadata.max_retry_count,
                billing_connector_retry_threshold: recovery_metadata
                    .billing_connector_retry_threshold,
                billing_account_reference: DieselBillingAccountReference(
                    recovery_metadata.mca_reference.recovery_to_billing,
                ),
            }
        });
        Self { revenue_recovery }
    }
}

#[cfg(feature = "v2")]
impl From<DieselMerchantConnectorAccountFeatureMetadata>
    for MerchantConnectorAccountFeatureMetadata
{
    fn from(feature_metadata: DieselMerchantConnectorAccountFeatureMetadata) -> Self {
        let revenue_recovery = feature_metadata.revenue_recovery.map(|recovery_metadata| {
            let mut billing_to_recovery = HashMap::new();
            for (key, value) in &recovery_metadata.billing_account_reference.0 {
                billing_to_recovery.insert(value.to_string(), key.clone());
            }
            RevenueRecoveryMetadata {
                max_retry_count: recovery_metadata.max_retry_count,
                billing_connector_retry_threshold: recovery_metadata
                    .billing_connector_retry_threshold,
                mca_reference: AccountReferenceMap {
                    recovery_to_billing: recovery_metadata.billing_account_reference.0,
                    billing_to_recovery,
                },
            }
        });
        Self { revenue_recovery }
    }
}


/// Derives the boilerplate code required for using an enum with `diesel` and a PostgreSQL database.
///
/// Storage Type can either be "text" or "db_enum"
/// Choosing text will store the enum as text in the database, whereas db_enum will map it to the
/// corresponding database enum
///
/// Works in tandem with the [`DieselEnum`][DieselEnum] derive macro to achieve the desired results.
/// The enum is required to implement (or derive) the [`ToString`][ToString] and the
/// [`FromStr`][FromStr] traits for the [`DieselEnum`][DieselEnum] derive macro to be used.
///
/// [DieselEnum]: crate::DieselEnum
/// [FromStr]: ::core::str::FromStr
/// [ToString]: ::std::string::ToString
///
/// # Example
///
/// ```
/// use router_derive::{diesel_enum};
///
/// // Deriving `FromStr` and `ToString` using the `strum` crate, you can also implement it
/// // yourself if required. (Required by the DieselEnum derive macro.)
/// #[derive(strum::Display, strum::EnumString)]
/// #[derive(Debug)]
/// #[diesel_enum(storage_type = "text")]
/// enum Color {
///     Red,
///     Green,
///     Blue,
/// }
/// ```
#[proc_macro_attribute]
pub fn diesel_enum(
    args: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let args_parsed = parse_macro_input!(args as DieselEnumMeta);
    let item = syn::parse_macro_input!(item as syn::ItemEnum);

    macros::diesel::diesel_enum_attribute_macro(args_parsed, &item)
        .unwrap_or_else(|error| error.to_compile_error())
        .into()
}


pub struct ReversalInformation {
    amount_details: Amount,
    reason: String,
}


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

    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd = std::collections::HashSet::from([
            utils::PaymentMethodDataType::Card,
            utils::PaymentMethodDataType::ApplePayThirdPartySdk,
        ]);
        utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}


File: crates/router/src/core/admin.rs
 Public functions: 41
 Public structs: 1

use std::str::FromStr;

use api_models::{
    admin::{self as admin_types},
    enums as api_enums, routing as routing_types,
};
use common_enums::{MerchantAccountRequestType, MerchantAccountType, OrganizationType};
use common_utils::{
    date_time,
    ext_traits::{AsyncExt, Encode, OptionExt, ValueExt},
    fp_utils, id_type, pii, type_name,
    types::keymanager::{self as km_types, KeyManagerState, ToEncryptable},
};
#[cfg(all(any(feature = "v1", feature = "v2"), feature = "olap"))]
use diesel_models::{business_profile::CardTestingGuardConfig, organization::OrganizationBridge};
use diesel_models::{configs, payment_method};
use error_stack::{report, FutureExt, ResultExt};
use external_services::http_client::client;
use hyperswitch_domain_models::merchant_connector_account::{
    FromRequestEncryptableMerchantConnectorAccount, UpdateEncryptableMerchantConnectorAccount,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use pm_auth::types as pm_auth_types;
use uuid::Uuid;

#[cfg(any(feature = "v1", feature = "v2"))]
use crate::types::transformers::ForeignFrom;
use crate::{
    consts,
    core::{
        connector_validation::ConnectorAuthTypeAndMetadataValidation,
        disputes,
        encryption::transfer_encryption_key,
        errors::{self, RouterResponse, RouterResult, StorageErrorExt},
        payment_methods::{cards, transformers},
        payments::helpers,
        pm_auth::helpers::PaymentAuthConnectorDataExt,
        routing, utils as core_utils,
    },
    db::{AccountsStorageInterface, StorageInterface},
    logger,
    routes::{metrics, SessionState},
    services::{
        self,
        api::{self as service_api},
        authentication, pm_auth as payment_initiation_service,
    },
    types::{
        self,
        api::{self, admin},
        domain::{
            self,
            types::{self as domain_types, AsyncLift},
        },
        storage::{self, enums::MerchantStorageScheme},
        transformers::{ForeignInto, ForeignTryFrom, ForeignTryInto},
    },
    utils,
};

#[inline]
pub fn create_merchant_publishable_key() -> String {
    format!(
        "pk_{}_{}",
        router_env::env::prefix_for_env(),
        Uuid::new_v4().simple()
    )
}

pub async fn insert_merchant_configs(
    db: &dyn StorageInterface,
    merchant_id: &id_type::MerchantId,
) -> RouterResult<()> {
    db.insert_config(configs::ConfigNew {
        key: merchant_id.get_requires_cvv_key(),
        config: "true".to_string(),
    })
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Error while setting requires_cvv config")?;

    db.insert_config(configs::ConfigNew {
        key: merchant_id.get_merchant_fingerprint_secret_key(),
        config: utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs"),
    })
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Error while inserting merchant fingerprint secret")?;

    Ok(())
}

#[cfg(feature = "olap")]
fn add_publishable_key_to_decision_service(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
) {
    let state = state.clone();
    let publishable_key = merchant_context
        .get_merchant_account()
        .publishable_key
        .clone();
    let merchant_id = merchant_context.get_merchant_account().get_id().clone();

    authentication::decision::spawn_tracked_job(
        async move {
            authentication::decision::add_publishable_key(
                &state,
                publishable_key.into(),
                merchant_id,
                None,
            )
            .await
        },
        authentication::decision::ADD,
    );
}

#[cfg(feature = "olap")]
pub async fn create_organization(
    state: SessionState,
    req: api::OrganizationCreateRequest,
) -> RouterResponse<api::OrganizationResponse> {
    let db_organization = ForeignFrom::foreign_from(req);
    state
        .accounts_store
        .insert_organization(db_organization)
        .await
        .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
            message: "Organization with the given organization_name already exists".to_string(),
        })
        .attach_printable("Error when creating organization")
        .map(ForeignFrom::foreign_from)
        .map(service_api::ApplicationResponse::Json)
}

#[cfg(feature = "olap")]
pub async fn update_organization(
    state: SessionState,
    org_id: api::OrganizationId,
    req: api::OrganizationUpdateRequest,
) -> RouterResponse<api::OrganizationResponse> {
    let organization_update = diesel_models::organization::OrganizationUpdate::Update {
        organization_name: req.organization_name,
        organization_details: req.organization_details,
        metadata: req.metadata,
        platform_merchant_id: req.platform_merchant_id,
    };
    state
        .accounts_store
        .update_organization_by_org_id(&org_id.organization_id, organization_update)
        .await
        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
            message: "organization with the given id does not exist".to_string(),
        })
        .attach_printable(format!(
            "Failed to update organization with organization_id: {:?}",
            org_id.organization_id
        ))
        .map(ForeignFrom::foreign_from)
        .map(service_api::ApplicationResponse::Json)
}

#[cfg(feature = "olap")]
pub async fn get_organization(
    state: SessionState,
    org_id: api::OrganizationId,
) -> RouterResponse<api::OrganizationResponse> {
    #[cfg(all(feature = "v1", feature = "olap"))]
    {
        CreateOrValidateOrganization::new(Some(org_id.organization_id))
            .create_or_validate(state.accounts_store.as_ref())
            .await
            .map(ForeignFrom::foreign_from)
            .map(service_api::ApplicationResponse::Json)
    }
    #[cfg(all(feature = "v2", feature = "olap"))]
    {
        CreateOrValidateOrganization::new(org_id.organization_id)
            .create_or_validate(state.accounts_store.as_ref())
            .await
            .map(ForeignFrom::foreign_from)
            .map(service_api::ApplicationResponse::Json)
    }
}

#[cfg(feature = "olap")]
pub async fn create_merchant_account(
    state: SessionState,
    req: api::MerchantAccountCreate,
    org_data_from_auth: Option<authentication::AuthenticationDataWithOrg>,
) -> RouterResponse<api::MerchantAccountResponse> {
    #[cfg(feature = "keymanager_create")]
    use common_utils::{keymanager, types::keymanager::EncryptionTransferRequest};

    let db = state.store.as_ref();

    let key = services::generate_aes256_key()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to generate aes 256 key")?;

    let master_key = db.get_master_key();

    let key_manager_state: &KeyManagerState = &(&state).into();
    let merchant_id = req.get_merchant_reference_id();
    let identifier = km_types::Identifier::Merchant(merchant_id.clone());
    #[cfg(feature = "keymanager_create")]
    {
        use base64::Engine;

        use crate::consts::BASE64_ENGINE;

        if key_manager_state.enabled {
            keymanager::transfer_key_to_key_manager(
                key_manager_state,
                EncryptionTransferRequest {
                    identifier: identifier.clone(),
                    key: BASE64_ENGINE.encode(key),
                },
            )
            .await
            .change_context(errors::ApiErrorResponse::DuplicateMerchantAccount)
            .attach_printable("Failed to insert key to KeyManager")?;
        }
    }

    let key_store = domain::MerchantKeyStore {
        merchant_id: merchant_id.clone(),
        key: domain_types::crypto_operation(
            key_manager_state,
            type_name!(domain::MerchantKeyStore),
            domain_types::CryptoOperation::Encrypt(key.to_vec().into()),
            identifier.clone(),
            master_key,
        )
        .await
        .and_then(|val| val.try_into_operation())
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to decrypt data from key store")?,
        created_at: date_time::now(),
    };

    let domain_merchant_account = req
        .create_domain_model_from_request(
            &state,
            key_store.clone(),
            &merchant_id,
            org_data_from_auth,
        )
        .await?;
    let key_manager_state = &(&state).into();
    db.insert_merchant_key_store(
        key_manager_state,
        key_store.clone(),
        &master_key.to_vec().into(),
    )
    .await
    .to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?;

    let merchant_account = db
        .insert_merchant(key_manager_state, domain_merchant_account, &key_store)
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicateMerchantAccount)?;

    let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
        merchant_account.clone(),
        key_store.clone(),
    )));
    add_publishable_key_to_decision_service(&state, &merchant_context);

    insert_merchant_configs(db, &merchant_id).await?;

    Ok(service_api::ApplicationResponse::Json(
        api::MerchantAccountResponse::foreign_try_from(merchant_account)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed while generating response")?,
    ))
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
trait MerchantAccountCreateBridge {
    async fn create_domain_model_from_request(
        self,
        state: &SessionState,
        key: domain::MerchantKeyStore,
        identifier: &id_type::MerchantId,
        org_data_from_auth: Option<authentication::AuthenticationDataWithOrg>,
    ) -> RouterResult<domain::MerchantAccount>;
}

#[cfg(all(feature = "v1", feature = "olap"))]
#[async_trait::async_trait]
impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
    async fn create_domain_model_from_request(
        self,
        state: &SessionState,
        key_store: domain::MerchantKeyStore,
        identifier: &id_type::MerchantId,
        org_data_from_auth: Option<authentication::AuthenticationDataWithOrg>,
    ) -> RouterResult<domain::MerchantAccount> {
        let db = &*state.accounts_store;
        let publishable_key = create_merchant_publishable_key();

        let primary_business_details = self.get_primary_details_as_value().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "primary_business_details",
            },
        )?;

        let webhook_details = self.webhook_details.clone().map(ForeignInto::foreign_into);

        let pm_collect_link_config = self.get_pm_link_config_as_value().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "pm_collect_link_config",
            },
        )?;

        let merchant_details = self.get_merchant_details_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "merchant_details",
            },
        )?;

        self.parse_routing_algorithm()
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "routing_algorithm",
            })
            .attach_printable("Invalid routing algorithm given")?;

        let metadata = self.get_metadata_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "metadata",
            },
        )?;

        // Get the enable payment response hash as a boolean, where the default value is true
        let enable_payment_response_hash = self.get_enable_payment_response_hash();

        let payment_response_hash_key = self.get_payment_response_hash_key();

        let parent_merchant_id = get_parent_merchant(
            state,
            self.sub_merchants_enabled,
            self.parent_merchant_id.as_ref(),
            &key_store,
        )
        .await?;

        let org_id = match (&self.organization_id, &org_data_from_auth) {
            (Some(req_org_id), Some(auth)) => {
                if req_org_id != &auth.organization_id {
                    return Err(errors::ApiErrorResponse::InvalidRequestData {
                        message: "Mismatched organization_id in request and authenticated context"
                            .to_string(),
                    }
                    .into());
                }
                Some(req_org_id.clone())
            }
            (None, Some(auth)) => Some(auth.organization_id.clone()),
            (req_org_id, _) => req_org_id.clone(),
        };

        let organization = CreateOrValidateOrganization::new(org_id)
            .create_or_validate(db)
            .await?;

        let merchant_account_type = match organization.get_organization_type() {
            OrganizationType::Standard => {
                match self.merchant_account_type.unwrap_or_default() {
                    // Allow only if explicitly Standard or not provided
                    MerchantAccountRequestType::Standard => MerchantAccountType::Standard,
                    MerchantAccountRequestType::Connected => {
                        return Err(errors::ApiErrorResponse::InvalidRequestData {
                            message:
                                "Merchant account type must be Standard for a Standard Organization"
                                    .to_string(),
                        }
                        .into());
                    }
                }
            }

            OrganizationType::Platform => {
                let accounts = state
                    .store
                    .list_merchant_accounts_by_organization_id(
                        &state.into(),
                        &organization.get_organization_id(),
                    )
                    .await
                    .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

                let platform_account_exists = accounts
                    .iter()
                    .any(|account| account.merchant_account_type == MerchantAccountType::Platform);

                if accounts.is_empty() || !platform_account_exists {
                    // First merchant in a Platform org must be Platform
                    MerchantAccountType::Platform
                } else {
                    match self.merchant_account_type.unwrap_or_default() {
                        MerchantAccountRequestType::Standard => MerchantAccountType::Standard,
                        MerchantAccountRequestType::Connected => {
                            if state.conf.platform.allow_connected_merchants {
                                MerchantAccountType::Connected
                            } else {
                                return Err(errors::ApiErrorResponse::InvalidRequestData {
                                    message: "Connected merchant accounts are not allowed"
                                        .to_string(),
                                }
                                .into());
                            }
                        }
                    }
                }
            }
        };

        let key = key_store.key.clone().into_inner();
        let key_manager_state = state.into();

        let merchant_account = async {
            Ok::<_, error_stack::Report<common_utils::errors::CryptoError>>(
                domain::MerchantAccountSetter {
                    merchant_id: identifier.clone(),
                    merchant_name: self
                        .merchant_name
                        .async_lift(|inner| async {
                            domain_types::crypto_operation(
                                &key_manager_state,
                                type_name!(domain::MerchantAccount),
                                domain_types::CryptoOperation::EncryptOptional(inner),
                                km_types::Identifier::Merchant(key_store.merchant_id.clone()),
                                key.peek(),
                            )
                            .await
                            .and_then(|val| val.try_into_optionaloperation())
                        })
                        .await?,
                    merchant_details: merchant_details
                        .async_lift(|inner| async {
                            domain_types::crypto_operation(
                                &key_manager_state,
                                type_name!(domain::MerchantAccount),
                                domain_types::CryptoOperation::EncryptOptional(inner),
                                km_types::Identifier::Merchant(key_store.merchant_id.clone()),
                                key.peek(),
                            )
                            .await
                            .and_then(|val| val.try_into_optionaloperation())
                        })
                        .await?,
                    return_url: self.return_url.map(|a| a.to_string()),
                    webhook_details,
                    routing_algorithm: Some(serde_json::json!({
                        "algorithm_id": null,
                        "timestamp": 0
                    })),
                    sub_merchants_enabled: self.sub_merchants_enabled,
                    parent_merchant_id,
                    enable_payment_response_hash,
                    payment_response_hash_key,
                    redirect_to_merchant_with_http_post: self
                        .redirect_to_merchant_with_http_post
                        .unwrap_or_default(),
                    publishable_key,
                    locker_id: self.locker_id,
                    metadata,
                    storage_scheme: MerchantStorageScheme::PostgresOnly,
                    primary_business_details,
                    created_at: date_time::now(),
                    modified_at: date_time::now(),
                    intent_fulfillment_time: None,
                    frm_routing_algorithm: self.frm_routing_algorithm,
                    #[cfg(feature = "payouts")]
                    payout_routing_algorithm: self.payout_routing_algorithm,
                    #[cfg(not(feature = "payouts"))]
                    payout_routing_algorithm: None,
                    organization_id: organization.get_organization_id(),
                    is_recon_enabled: false,
                    default_profile: None,
                    recon_status: diesel_models::enums::ReconStatus::NotRequested,
                    payment_link_config: None,
                    pm_collect_link_config,
                    version: common_types::consts::API_VERSION,
                    is_platform_account: false,
                    product_type: self.product_type,
                    merchant_account_type,
                },
            )
        }
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

        let mut domain_merchant_account = domain::MerchantAccount::from(merchant_account);

        CreateProfile::new(self.primary_business_details.clone())
            .create_profiles(state, &mut domain_merchant_account, &key_store)
            .await?;

        Ok(domain_merchant_account)
    }
}

#[cfg(feature = "olap")]
enum CreateOrValidateOrganization {
    /// Creates a new organization
    #[cfg(feature = "v1")]
    Create,
    /// Validates if this organization exists in the records
    Validate {
        organization_id: id_type::OrganizationId,
    },
}

#[cfg(feature = "olap")]
impl CreateOrValidateOrganization {
    #[cfg(all(feature = "v1", feature = "olap"))]
    /// Create an action to either create or validate the given organization_id
    /// If organization_id is passed, then validate if this organization exists
    /// If not passed, create a new organization
    fn new(organization_id: Option<id_type::OrganizationId>) -> Self {
        if let Some(organization_id) = organization_id {
            Self::Validate { organization_id }
        } else {
            Self::Create
        }
    }

    #[cfg(all(feature = "v2", feature = "olap"))]
    /// Create an action to validate the provided organization_id
    fn new(organization_id: id_type::OrganizationId) -> Self {
        Self::Validate { organization_id }
    }

    #[cfg(feature = "olap")]
    /// Apply the action, whether to create the organization or validate the given organization_id
    async fn create_or_validate(
        &self,
        db: &dyn AccountsStorageInterface,
    ) -> RouterResult<diesel_models::organization::Organization> {
        match self {
            #[cfg(feature = "v1")]
            Self::Create => {
                let new_organization = api_models::organization::OrganizationNew::new(
                    OrganizationType::Standard,
                    None,
                );
                let db_organization = ForeignFrom::foreign_from(new_organization);
                db.insert_organization(db_organization)
                    .await
                    .to_duplicate_response(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Error when creating organization")
            }
            Self::Validate { organization_id } => db
                .find_organization_by_org_id(organization_id)
                .await
                .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
                    message: "organization with the given id does not exist".to_string(),
                }),
        }
    }
}

#[cfg(all(feature = "v1", feature = "olap"))]
enum CreateProfile {
    /// Create profiles from primary business details
    /// If there is only one profile created, then set this profile as default
    CreateFromPrimaryBusinessDetails {
        primary_business_details: Vec<admin_types::PrimaryBusinessDetails>,
    },
    /// Create a default profile, set this as default profile
    CreateDefaultProfile,
}

#[cfg(all(feature = "v1", feature = "olap"))]
impl CreateProfile {
    /// Create a new profile action from the given information
    /// If primary business details exist, then create profiles from them
    /// If primary business details are empty, then create default profile
    fn new(primary_business_details: Option<Vec<admin_types::PrimaryBusinessDetails>>) -> Self {
        match primary_business_details {
            Some(primary_business_details) if !primary_business_details.is_empty() => {
                Self::CreateFromPrimaryBusinessDetails {
                    primary_business_details,
                }
            }
            _ => Self::CreateDefaultProfile,
        }
    }

    async fn create_profiles(
        &self,
        state: &SessionState,
        merchant_account: &mut domain::MerchantAccount,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<()> {
        match self {
            Self::CreateFromPrimaryBusinessDetails {
                primary_business_details,
            } => {
                let business_profiles = Self::create_profiles_for_each_business_details(
                    state,
                    merchant_account.clone(),
                    primary_business_details,
                    key_store,
                )
                .await?;

                // Update the default business profile in merchant account
                if business_profiles.len() == 1 {
                    merchant_account.default_profile = business_profiles
                        .first()
                        .map(|business_profile| business_profile.get_id().to_owned())
                }
            }
            Self::CreateDefaultProfile => {
                let business_profile = self
                    .create_default_business_profile(state, merchant_account.clone(), key_store)
                    .await?;

                merchant_account.default_profile = Some(business_profile.get_id().to_owned());
            }
        }

        Ok(())
    }

    /// Create default profile
    async fn create_default_business_profile(
        &self,
        state: &SessionState,
        merchant_account: domain::MerchantAccount,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<domain::Profile> {
        let business_profile = create_and_insert_business_profile(
            state,
            api_models::admin::ProfileCreate::default(),
            merchant_account.clone(),
            key_store,
        )
        .await?;

        Ok(business_profile)
    }

    /// Create profile for each primary_business_details,
    /// If there is no default profile in merchant account and only one primary_business_detail
    /// is available, then create a default profile.
    async fn create_profiles_for_each_business_details(
        state: &SessionState,
        merchant_account: domain::MerchantAccount,
        primary_business_details: &Vec<admin_types::PrimaryBusinessDetails>,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<Vec<domain::Profile>> {
        let mut business_profiles_vector = Vec::with_capacity(primary_business_details.len());

        // This must ideally be run in a transaction,
        // if there is an error in inserting some profile, because of unique constraints
        // the whole query must be rolled back
        for business_profile in primary_business_details {
            let profile_name =
                format!("{}_{}", business_profile.country, business_profile.business);

            let profile_create_request = api_models::admin::ProfileCreate {
                profile_name: Some(profile_name),
                ..Default::default()
            };

            create_and_insert_business_profile(
                state,
                profile_create_request,
                merchant_account.clone(),
                key_store,
            )
            .await
            .map_err(|profile_insert_error| {
                logger::warn!("Profile already exists {profile_insert_error:?}");
            })
            .map(|business_profile| business_profiles_vector.push(business_profile))
            .ok();
        }

        Ok(business_profiles_vector)
    }
}

#[cfg(all(feature = "v2", feature = "olap"))]
#[async_trait::async_trait]
impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
    async fn create_domain_model_from_request(
        self,
        state: &SessionState,
        key_store: domain::MerchantKeyStore,
        identifier: &id_type::MerchantId,
        _org_data: Option<authentication::AuthenticationDataWithOrg>,
    ) -> RouterResult<domain::MerchantAccount> {
        let publishable_key = create_merchant_publishable_key();
        let db = &*state.accounts_store;

        let metadata = self.get_metadata_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "metadata",
            },
        )?;

        let merchant_details = self.get_merchant_details_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "merchant_details",
            },
        )?;

        let organization = CreateOrValidateOrganization::new(self.organization_id.clone())
            .create_or_validate(db)
            .await?;

        // V2 currently supports creation of Standard merchant accounts only, irrespective of organization type
        let merchant_account_type = MerchantAccountType::Standard;

        let key = key_store.key.into_inner();
        let id = identifier.to_owned();
        let key_manager_state = state.into();
        let identifier = km_types::Identifier::Merchant(id.clone());

        async {
            Ok::<_, error_stack::Report<common_utils::errors::CryptoError>>(
                domain::MerchantAccount::from(domain::MerchantAccountSetter {
                    id,
                    merchant_name: Some(
                        domain_types::crypto_operation(
                            &key_manager_state,
                            type_name!(domain::MerchantAccount),
                            domain_types::CryptoOperation::Encrypt(
                                self.merchant_name
                                    .map(|merchant_name| merchant_name.into_inner()),
                            ),
                            identifier.clone(),
                            key.peek(),
                        )
                        .await
                        .and_then(|val| val.try_into_operation())?,
                    ),
                    merchant_details: merchant_details
                        .async_lift(|inner| async {
                            domain_types::crypto_operation(
                                &key_manager_state,
                                type_name!(domain::MerchantAccount),
                                domain_types::CryptoOperation::EncryptOptional(inner),
                                identifier.clone(),
                                key.peek(),
                            )
                            .await
                            .and_then(|val| val.try_into_optionaloperation())
                        })
                        .await?,
                    publishable_key,
                    metadata,
                    storage_scheme: MerchantStorageScheme::PostgresOnly,
                    created_at: date_time::now(),
                    modified_at: date_time::now(),
                    organization_id: organization.get_organization_id(),
                    recon_status: diesel_models::enums::ReconStatus::NotRequested,
                    is_platform_account: false,
                    version: common_types::consts::API_VERSION,
                    product_type: self.product_type,
                    merchant_account_type,
                }),
            )
        }
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to encrypt merchant details")
    }
}

#[cfg(all(feature = "olap", feature = "v2"))]
pub async fn list_merchant_account(
    state: SessionState,
    organization_id: api_models::organization::OrganizationId,
) -> RouterResponse<Vec<api::MerchantAccountResponse>> {
    let merchant_accounts = state
        .store
        .list_merchant_accounts_by_organization_id(
            &(&state).into(),
            &organization_id.organization_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    let merchant_accounts = merchant_accounts
        .into_iter()
        .map(|merchant_account| {
            api::MerchantAccountResponse::foreign_try_from(merchant_account).change_context(
                errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "merchant_account",
                },
            )
        })
        .collect::<Result<Vec<_>, _>>()?;

    Ok(services::ApplicationResponse::Json(merchant_accounts))
}

#[cfg(all(feature = "olap", feature = "v1"))]
pub async fn list_merchant_account(
    state: SessionState,
    req: api_models::admin::MerchantAccountListRequest,
    org_data_from_auth: Option<authentication::AuthenticationDataWithOrg>,
) -> RouterResponse<Vec<api::MerchantAccountResponse>> {
    if let Some(auth) = org_data_from_auth {
        if auth.organization_id != req.organization_id {
            return Err(errors::ApiErrorResponse::InvalidRequestData {
                message: "Organization ID in request and authentication do not match".to_string(),
            }
            .into());
        }
    }
    let merchant_accounts = state
        .store
        .list_merchant_accounts_by_organization_id(&(&state).into(), &req.organization_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    let merchant_accounts = merchant_accounts
        .into_iter()
        .map(|merchant_account| {
            api::MerchantAccountResponse::foreign_try_from(merchant_account).change_context(
                errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "merchant_account",
                },
            )
        })
        .collect::<Result<Vec<_>, _>>()?;

    Ok(services::ApplicationResponse::Json(merchant_accounts))
}

pub async fn get_merchant_account(
    state: SessionState,
    req: api::MerchantId,
    _profile_id: Option<id_type::ProfileId>,
) -> RouterResponse<api::MerchantAccountResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();
    let key_store = db
        .get_merchant_key_store_by_merchant_id(
            key_manager_state,
            &req.merchant_id,
            &db.get_master_key().to_vec().into(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    let merchant_account = db
        .find_merchant_account_by_merchant_id(key_manager_state, &req.merchant_id, &key_store)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    Ok(service_api::ApplicationResponse::Json(
        api::MerchantAccountResponse::foreign_try_from(merchant_account)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to construct response")?,
    ))
}

#[cfg(feature = "v1")]
/// For backwards compatibility, whenever new business labels are passed in
/// primary_business_details, create a profile
pub async fn create_profile_from_business_labels(
    state: &SessionState,
    db: &dyn StorageInterface,
    key_store: &domain::MerchantKeyStore,
    merchant_id: &id_type::MerchantId,
    new_business_details: Vec<admin_types::PrimaryBusinessDetails>,
) -> RouterResult<()> {
    let key_manager_state = &state.into();
    let merchant_account = db
        .find_merchant_account_by_merchant_id(key_manager_state, merchant_id, key_store)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;

    let old_business_details = merchant_account
        .primary_business_details
        .clone()
        .parse_value::<Vec<admin_types::PrimaryBusinessDetails>>("PrimaryBusinessDetails")
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "routing_algorithm",
        })
        .attach_printable("Invalid routing algorithm given")?;

    // find the diff between two vectors
    let business_profiles_to_create = new_business_details
        .into_iter()
        .filter(|business_details| !old_business_details.contains(business_details))
        .collect::<Vec<_>>();

    for business_profile in business_profiles_to_create {
        let profile_name = format!("{}_{}", business_profile.country, business_profile.business);

        let profile_create_request = admin_types::ProfileCreate {
            profile_name: Some(profile_name),
            ..Default::default()
        };

        let profile_create_result = create_and_insert_business_profile(
            state,
            profile_create_request,
            merchant_account.clone(),
            key_store,
        )
        .await
        .map_err(|profile_insert_error| {
            // If there is any duplicate error, we need not take any action
            logger::warn!("Profile already exists {profile_insert_error:?}");
        });

        // If a profile is created, then unset the default profile
        if profile_create_result.is_ok() && merchant_account.default_profile.is_some() {
            let unset_default_profile = domain::MerchantAccountUpdate::UnsetDefaultProfile;
            db.update_merchant(
                key_manager_state,
                merchant_account.clone(),
                unset_default_profile,
                key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
        }
    }

    Ok(())
}

#[cfg(any(feature = "v1", feature = "v2", feature = "olap"))]
#[async_trait::async_trait]
trait MerchantAccountUpdateBridge {
    async fn get_update_merchant_object(
        self,
        state: &SessionState,
        merchant_id: &id_type::MerchantId,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<storage::MerchantAccountUpdate>;
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl MerchantAccountUpdateBridge for api::MerchantAccountUpdate {
    async fn get_update_merchant_object(
        self,
        state: &SessionState,
        merchant_id: &id_type::MerchantId,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<storage::MerchantAccountUpdate> {
        let key_manager_state = &state.into();
        let key = key_store.key.get_inner().peek();

        let db = state.store.as_ref();

        let primary_business_details = self.get_primary_details_as_value().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "primary_business_details",
            },
        )?;

        let pm_collect_link_config = self.get_pm_link_config_as_value().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "pm_collect_link_config",
            },
        )?;

        let merchant_details = self.get_merchant_details_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "merchant_details",
            },
        )?;

        self.parse_routing_algorithm().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "routing_algorithm",
            },
        )?;

        let webhook_details = self.webhook_details.map(ForeignInto::foreign_into);

        let parent_merchant_id = get_parent_merchant(
            state,
            self.sub_merchants_enabled,
            self.parent_merchant_id.as_ref(),
            key_store,
        )
        .await?;

        // This supports changing the business profile by passing in the profile_id
        let business_profile_id_update = if let Some(ref profile_id) = self.default_profile {
            // Validate whether profile_id passed in request is valid and is linked to the merchant
            core_utils::validate_and_get_business_profile(
                state.store.as_ref(),
                key_manager_state,
                key_store,
                Some(profile_id),
                merchant_id,
            )
            .await?
            .map(|business_profile| Some(business_profile.get_id().to_owned()))
        } else {
            None
        };

        #[cfg(any(feature = "v1", feature = "v2"))]
        // In order to support backwards compatibility, if a business_labels are passed in the update
        // call, then create new profiles with the profile_name as business_label
        self.primary_business_details
            .clone()
            .async_map(|primary_business_details| async {
                let _ = create_profile_from_business_labels(
                    state,
                    db,
                    key_store,
                    merchant_id,
                    primary_business_details,
                )
                .await;
            })
            .await;

        let identifier = km_types::Identifier::Merchant(key_store.merchant_id.clone());
        Ok(storage::MerchantAccountUpdate::Update {
            merchant_name: self
                .merchant_name
                .map(Secret::new)
                .async_lift(|inner| async {
                    domain_types::crypto_operation(
                        key_manager_state,
                        type_name!(storage::MerchantAccount),
                        domain_types::CryptoOperation::EncryptOptional(inner),
                        identifier.clone(),
                        key,
                    )
                    .await
                    .and_then(|val| val.try_into_optionaloperation())
                })
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to encrypt merchant name")?,
            merchant_details: merchant_details
                .async_lift(|inner| async {
                    domain_types::crypto_operation(
                        key_manager_state,
                        type_name!(storage::MerchantAccount),
                        domain_types::CryptoOperation::EncryptOptional(inner),
                        identifier.clone(),
                        key,
                    )
                    .await
                    .and_then(|val| val.try_into_optionaloperation())
                })
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to encrypt merchant details")?,
            return_url: self.return_url.map(|a| a.to_string()),
            webhook_details,
            sub_merchants_enabled: self.sub_merchants_enabled,
            parent_merchant_id,
            enable_payment_response_hash: self.enable_payment_response_hash,
            payment_response_hash_key: self.payment_response_hash_key,
            redirect_to_merchant_with_http_post: self.redirect_to_merchant_with_http_post,
            locker_id: self.locker_id,
            metadata: self.metadata,
            publishable_key: None,
            primary_business_details,
            frm_routing_algorithm: self.frm_routing_algorithm,
            intent_fulfillment_time: None,
            #[cfg(feature = "payouts")]
            payout_routing_algorithm: self.payout_routing_algorithm,
            #[cfg(not(feature = "payouts"))]
            payout_routing_algorithm: None,
            default_profile: business_profile_id_update,
            payment_link_config: None,
            pm_collect_link_config,
            routing_algorithm: self.routing_algorithm,
        })
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl MerchantAccountUpdateBridge for api::MerchantAccountUpdate {
    async fn get_update_merchant_object(
        self,
        state: &SessionState,
        _merchant_id: &id_type::MerchantId,
        key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<storage::MerchantAccountUpdate> {
        let key_manager_state = &state.into();
        let key = key_store.key.get_inner().peek();

        let merchant_details = self.get_merchant_details_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "merchant_details",
            },
        )?;

        let metadata = self.get_metadata_as_secret().change_context(
            errors::ApiErrorResponse::InvalidDataValue {
                field_name: "metadata",
            },
        )?;


pub struct ActivePaymentsMetricsAccumulator {
    pub active_payments: CountAccumulator,
}


File: crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs
 Public structs: 10

use common_enums::enums;
use common_utils::{request::Method, types::FloatMajorUnit};
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::PaymentsAuthorizeRequestData,
};

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

impl<T> From<(FloatMajorUnit, T)> for BitpayRouterData<T> {
    fn from((amount, router_data): (FloatMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TransactionSpeed {
    Low,
    #[default]
    Medium,
    High,
}

#[derive(Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BitpayPaymentsRequest {
    price: FloatMajorUnit,
    currency: String,
    #[serde(rename = "redirectURL")]
    redirect_url: String,
    #[serde(rename = "notificationURL")]
    notification_url: String,
    transaction_speed: TransactionSpeed,
    token: Secret<String>,
    order_id: String,
}

impl TryFrom<&BitpayRouterData<&types::PaymentsAuthorizeRouterData>> for BitpayPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        get_crypto_specific_payment_data(item)
    }
}

// Auth Struct
pub struct BitpayAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for BitpayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BitpayPaymentStatus {
    #[default]
    New,
    Paid,
    Confirmed,
    Complete,
    Expired,
    Invalid,
}

impl From<BitpayPaymentStatus> for enums::AttemptStatus {
    fn from(item: BitpayPaymentStatus) -> Self {
        match item {
            BitpayPaymentStatus::New => Self::AuthenticationPending,
            BitpayPaymentStatus::Complete | BitpayPaymentStatus::Confirmed => Self::Charged,
            BitpayPaymentStatus::Expired => Self::Failure,
            _ => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ExceptionStatus {
    #[default]
    Unit,
    Bool(bool),
    String(String),
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitpayPaymentResponseData {
    pub url: Option<url::Url>,
    pub status: BitpayPaymentStatus,
    pub price: FloatMajorUnit,
    pub currency: String,
    pub amount_paid: FloatMajorUnit,
    pub invoice_time: Option<FloatMajorUnit>,
    pub rate_refresh_time: Option<i64>,
    pub expiration_time: Option<i64>,
    pub current_time: Option<i64>,
    pub id: String,
    pub order_id: Option<String>,
    pub low_fee_detected: Option<bool>,
    pub display_amount_paid: Option<String>,
    pub exception_status: ExceptionStatus,
    pub redirect_url: Option<String>,
    pub refund_address_request_pending: Option<bool>,
    pub merchant_name: Option<Secret<String>>,
    pub token: Option<Secret<String>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct BitpayPaymentsResponse {
    data: BitpayPaymentResponseData,
    facade: Option<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, BitpayPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BitpayPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let redirection_data = item
            .response
            .data
            .url
            .map(|x| RedirectForm::from((x, Method::Get)));
        let connector_id = ResponseId::ConnectorTransactionId(item.response.data.id.clone());
        let attempt_status = item.response.data.status;
        Ok(Self {
            status: enums::AttemptStatus::from(attempt_status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: connector_id,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .data
                    .order_id
                    .or(Some(item.response.data.id)),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct BitpayRefundRequest {
    pub amount: FloatMajorUnit,
}

impl<F> TryFrom<&BitpayRouterData<&types::RefundsRouterData<F>>> for BitpayRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BitpayRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount,
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Processing => Self::Pending,
            //TODO: Review mapping
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for types::RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BitpayErrorResponse {
    pub error: String,
    pub code: Option<String>,
    pub message: Option<String>,
}

fn get_crypto_specific_payment_data(
    item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<BitpayPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
    let price = item.amount;
    let currency = item.router_data.request.currency.to_string();
    let redirect_url = item.router_data.request.get_router_return_url()?;
    let notification_url = item.router_data.request.get_webhook_url()?;
    let transaction_speed = TransactionSpeed::Medium;
    let auth_type = item.router_data.connector_auth_type.clone();
    let token = match auth_type {
        ConnectorAuthType::HeaderKey { api_key } => api_key,
        _ => String::default().into(),
    };
    let order_id = item.router_data.connector_request_reference_id.clone();

    Ok(BitpayPaymentsRequest {
        price,
        currency,
        redirect_url,
        notification_url,
        transaction_speed,
        token,
        order_id,
    })
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BitpayWebhookDetails {
    pub event: Event,
    pub data: BitpayPaymentResponseData,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Event {
    pub code: i64,
    pub name: WebhookEventType,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum WebhookEventType {
    #[serde(rename = "invoice_paidInFull")]
    Paid,
    #[serde(rename = "invoice_confirmed")]
    Confirmed,
    #[serde(rename = "invoice_completed")]
    Completed,
    #[serde(rename = "invoice_expired")]
    Expired,
    #[serde(rename = "invoice_failedToConfirm")]
    Invalid,
    #[serde(rename = "invoice_declined")]
    Declined,
    #[serde(rename = "invoice_refundComplete")]
    Refunded,
    #[serde(rename = "invoice_manuallyNotified")]
    Resent,
    #[serde(other)]
    Unknown,
}