File: crates/router/src/core/customers.rs
 Public functions: 8

use common_utils::{
    crypto::Encryptable,
    errors::ReportSwitchExt,
    ext_traits::AsyncExt,
    id_type, pii, type_name,
    types::{
        keymanager::{Identifier, KeyManagerState, ToEncryptable},
        Description,
    },
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::payment_methods as payment_methods_domain;
use masking::{ExposeInterface, Secret, SwitchStrategy};
use payment_methods::controller::PaymentMethodsController;
use router_env::{instrument, tracing};

#[cfg(feature = "v2")]
use crate::core::payment_methods::cards::create_encrypted_data;
#[cfg(feature = "v1")]
use crate::utils::CustomerAddress;
use crate::{
    core::{
        errors::{self, StorageErrorExt},
        payment_methods::{cards, network_tokenization},
    },
    db::StorageInterface,
    pii::PeekInterface,
    routes::{metrics, SessionState},
    services,
    types::{
        api::customers,
        domain::{self, types},
        storage::{self, enums},
        transformers::ForeignFrom,
    },
};

pub const REDACTED: &str = "Redacted";

#[instrument(skip(state))]
pub async fn create_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    customer_data: customers::CustomerRequest,
    connector_customer_details: Option<Vec<payment_methods_domain::ConnectorCustomerDetails>>,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db: &dyn StorageInterface = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let merchant_reference_id = customer_data.get_merchant_reference_id();

    let merchant_id = merchant_context.get_merchant_account().get_id();

    let merchant_reference_id_customer = MerchantReferenceIdForCustomer {
        merchant_reference_id: merchant_reference_id.as_ref(),
        merchant_id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    // We first need to validate whether the customer with the given customer id already exists
    // this may seem like a redundant db call, as the insert_customer will anyway return this error
    //
    // Consider a scenario where the address is inserted and then when inserting the customer,
    // it errors out, now the address that was inserted is not deleted

    merchant_reference_id_customer
        .verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(db)
        .await?;

    let domain_customer = customer_data
        .create_domain_model_from_request(
            &connector_customer_details,
            db,
            &merchant_reference_id,
            &merchant_context,
            key_manager_state,
            &state,
        )
        .await?;

    let customer = db
        .insert_customer(
            domain_customer,
            key_manager_state,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::CustomersErrorResponse::CustomerAlreadyExists)?;

    customer_data.generate_response(&customer)
}

#[async_trait::async_trait]
trait CustomerCreateBridge {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse>;
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerCreateBridge for customers::CustomerRequest {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
        // Setting default billing address to Db
        let address = self.get_address();
        let merchant_id = merchant_context.get_merchant_account().get_id();
        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let customer_billing_address_struct = AddressStructForDbEntry {
            address: address.as_ref(),
            customer_data: self,
            merchant_id,
            customer_id: merchant_reference_id.as_ref(),
            storage_scheme: merchant_context.get_merchant_account().storage_scheme,
            key_store: merchant_context.get_merchant_key_store(),
            key_manager_state,
            state,
        };

        let address_from_db = customer_billing_address_struct
            .encrypt_customer_address_and_set_to_db(db)
            .await?;

        let encrypted_data = types::crypto_operation(
            key_manager_state,
            type_name!(domain::Customer),
            types::CryptoOperation::BatchEncrypt(
                domain::FromRequestEncryptableCustomer::to_encryptable(
                    domain::FromRequestEncryptableCustomer {
                        name: self.name.clone(),
                        email: self.email.clone().map(|a| a.expose().switch_strategy()),
                        phone: self.phone.clone(),
                        tax_registration_id: self.tax_registration_id.clone(),
                    },
                ),
            ),
            Identifier::Merchant(
                merchant_context
                    .get_merchant_key_store()
                    .merchant_id
                    .clone(),
            ),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .switch()
        .attach_printable("Failed while encrypting Customer")?;

        let encryptable_customer =
            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                .change_context(errors::CustomersErrorResponse::InternalServerError)?;

        let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
            let mut map = serde_json::Map::new();
            for details in details_vec {
                let merchant_connector_id =
                    details.merchant_connector_id.get_string_repr().to_string();
                let connector_customer_id = details.connector_customer_id.clone();
                map.insert(merchant_connector_id, connector_customer_id.into());
            }
            pii::SecretSerdeValue::new(serde_json::Value::Object(map))
        });

        Ok(domain::Customer {
            customer_id: merchant_reference_id
                .to_owned()
                .ok_or(errors::CustomersErrorResponse::InternalServerError)?,
            merchant_id: merchant_id.to_owned(),
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            description: self.description.clone(),
            phone_country_code: self.phone_country_code.clone(),
            metadata: self.metadata.clone(),
            connector_customer,
            address_id: address_from_db.clone().map(|addr| addr.address_id),
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            default_payment_method_id: None,
            updated_by: None,
            version: common_types::consts::API_VERSION,
            tax_registration_id: encryptable_customer.tax_registration_id,
        })
    }

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse> {
        let address = self.get_address();
        Ok(services::ApplicationResponse::Json(
            customers::CustomerResponse::foreign_from((customer.clone(), address)),
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl CustomerCreateBridge for customers::CustomerRequest {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        _db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
        let default_customer_billing_address = self.get_default_customer_billing_address();
        let encrypted_customer_billing_address = default_customer_billing_address
            .async_map(|billing_address| {
                create_encrypted_data(
                    key_state,
                    merchant_context.get_merchant_key_store(),
                    billing_address,
                )
            })
            .await
            .transpose()
            .change_context(errors::CustomersErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt default customer billing address")?;
        let default_customer_shipping_address = self.get_default_customer_shipping_address();
        let encrypted_customer_shipping_address = default_customer_shipping_address
            .async_map(|shipping_address| {
                create_encrypted_data(
                    key_state,
                    merchant_context.get_merchant_key_store(),
                    shipping_address,
                )
            })
            .await
            .transpose()
            .change_context(errors::CustomersErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt default customer shipping address")?;

        let merchant_id = merchant_context.get_merchant_account().get_id().clone();
        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let encrypted_data = types::crypto_operation(
            key_state,
            type_name!(domain::Customer),
            types::CryptoOperation::BatchEncrypt(
                domain::FromRequestEncryptableCustomer::to_encryptable(
                    domain::FromRequestEncryptableCustomer {
                        name: Some(self.name.clone()),
                        email: Some(self.email.clone().expose().switch_strategy()),
                        phone: self.phone.clone(),
                        tax_registration_id: self.tax_registration_id.clone(),
                    },
                ),
            ),
            Identifier::Merchant(
                merchant_context
                    .get_merchant_key_store()
                    .merchant_id
                    .clone(),
            ),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .switch()
        .attach_printable("Failed while encrypting Customer")?;

        let encryptable_customer =
            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                .change_context(errors::CustomersErrorResponse::InternalServerError)?;

        let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
            let map: std::collections::HashMap<_, _> = details_vec
                .iter()
                .map(|details| {
                    (
                        details.merchant_connector_id.clone(),
                        details.connector_customer_id.to_string(),
                    )
                })
                .collect();
            common_types::customers::ConnectorCustomerMap::new(map)
        });

        Ok(domain::Customer {
            id: id_type::GlobalCustomerId::generate(&state.conf.cell_information.id),
            merchant_reference_id: merchant_reference_id.to_owned(),
            merchant_id,
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            description: self.description.clone(),
            phone_country_code: self.phone_country_code.clone(),
            metadata: self.metadata.clone(),
            connector_customer,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            default_payment_method_id: None,
            updated_by: None,
            default_billing_address: encrypted_customer_billing_address.map(Into::into),
            default_shipping_address: encrypted_customer_shipping_address.map(Into::into),
            version: common_types::consts::API_VERSION,
            status: common_enums::DeleteStatus::Active,
            tax_registration_id: encryptable_customer.tax_registration_id,
        })
    }

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse> {
        Ok(services::ApplicationResponse::Json(
            customers::CustomerResponse::foreign_from(customer.clone()),
        ))
    }
}

#[cfg(feature = "v1")]
struct AddressStructForDbEntry<'a> {
    address: Option<&'a api_models::payments::AddressDetails>,
    customer_data: &'a customers::CustomerRequest,
    merchant_id: &'a id_type::MerchantId,
    customer_id: Option<&'a id_type::CustomerId>,
    storage_scheme: common_enums::MerchantStorageScheme,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
    state: &'a SessionState,
}

#[cfg(feature = "v1")]
impl AddressStructForDbEntry<'_> {
    async fn encrypt_customer_address_and_set_to_db(
        &self,
        db: &dyn StorageInterface,
    ) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
        let encrypted_customer_address = self
            .address
            .async_map(|addr| async {
                self.customer_data
                    .get_domain_address(
                        self.state,
                        addr.clone(),
                        self.merchant_id,
                        self.customer_id
                            .ok_or(errors::CustomersErrorResponse::InternalServerError)?, // should we raise error since in v1 appilcation is supposed to have this id or generate it at this point.
                        self.key_store.key.get_inner().peek(),
                        self.storage_scheme,
                    )
                    .await
                    .switch()
                    .attach_printable("Failed while encrypting address")
            })
            .await
            .transpose()?;

        encrypted_customer_address
            .async_map(|encrypt_add| async {
                db.insert_address_for_customers(self.key_manager_state, encrypt_add, self.key_store)
                    .await
                    .switch()
                    .attach_printable("Failed while inserting new address")
            })
            .await
            .transpose()
    }
}

struct MerchantReferenceIdForCustomer<'a> {
    merchant_reference_id: Option<&'a id_type::CustomerId>,
    merchant_id: &'a id_type::MerchantId,
    merchant_account: &'a domain::MerchantAccount,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
}

#[cfg(feature = "v1")]
impl<'a> MerchantReferenceIdForCustomer<'a> {
    async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
        &self,
        db: &dyn StorageInterface,
    ) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
        self.merchant_reference_id
            .async_map(|cust| async {
                self.verify_if_merchant_reference_not_present_by_merchant_reference_id(cust, db)
                    .await
            })
            .await
            .transpose()
    }

    async fn verify_if_merchant_reference_not_present_by_merchant_reference_id(
        &self,
        cus: &'a id_type::CustomerId,
        db: &dyn StorageInterface,
    ) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
        match db
            .find_customer_by_customer_id_merchant_id(
                self.key_manager_state,
                cus,
                self.merchant_id,
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
        {
            Err(err) => {
                if !err.current_context().is_db_not_found() {
                    Err(err).switch()
                } else {
                    Ok(())
                }
            }
            Ok(_) => Err(report!(
                errors::CustomersErrorResponse::CustomerAlreadyExists
            )),
        }
    }
}

#[cfg(feature = "v2")]
impl<'a> MerchantReferenceIdForCustomer<'a> {
    async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
        &self,
        db: &dyn StorageInterface,
    ) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
        self.merchant_reference_id
            .async_map(|merchant_ref| async {
                self.verify_if_merchant_reference_not_present_by_merchant_reference(
                    merchant_ref,
                    db,
                )
                .await
            })
            .await
            .transpose()
    }

    async fn verify_if_merchant_reference_not_present_by_merchant_reference(
        &self,
        merchant_ref: &'a id_type::CustomerId,
        db: &dyn StorageInterface,
    ) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
        match db
            .find_customer_by_merchant_reference_id_merchant_id(
                self.key_manager_state,
                merchant_ref,
                self.merchant_id,
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
        {
            Err(err) => {
                if !err.current_context().is_db_not_found() {
                    Err(err).switch()
                } else {
                    Ok(())
                }
            }
            Ok(_) => Err(report!(
                errors::CustomersErrorResponse::CustomerAlreadyExists
            )),
        }
    }
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn retrieve_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    _profile_id: Option<id_type::ProfileId>,
    customer_id: id_type::CustomerId,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let response = db
        .find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id(
            key_manager_state,
            &customer_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?
        .ok_or(errors::CustomersErrorResponse::CustomerNotFound)?;

    let address = match &response.address_id {
        Some(address_id) => Some(api_models::payments::AddressDetails::from(
            db.find_address_by_address_id(
                key_manager_state,
                address_id,
                merchant_context.get_merchant_key_store(),
            )
            .await
            .switch()?,
        )),
        None => None,
    };
    Ok(services::ApplicationResponse::Json(
        customers::CustomerResponse::foreign_from((response, address)),
    ))
}

#[cfg(feature = "v2")]
#[instrument(skip(state))]
pub async fn retrieve_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    id: id_type::GlobalCustomerId,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let response = db
        .find_customer_by_global_id(
            key_manager_state,
            &id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

    Ok(services::ApplicationResponse::Json(
        customers::CustomerResponse::foreign_from(response),
    ))
}

#[instrument(skip(state))]
pub async fn list_customers(
    state: SessionState,
    merchant_id: id_type::MerchantId,
    _profile_id_list: Option<Vec<id_type::ProfileId>>,
    key_store: domain::MerchantKeyStore,
    request: customers::CustomerListRequest,
) -> errors::CustomerResponse<Vec<customers::CustomerResponse>> {
    let db = state.store.as_ref();

    let customer_list_constraints = crate::db::customers::CustomerListConstraints {
        limit: request
            .limit
            .unwrap_or(crate::consts::DEFAULT_LIST_API_LIMIT),
        offset: request.offset,
    };

    let domain_customers = db
        .list_customers_by_merchant_id(
            &(&state).into(),
            &merchant_id,
            &key_store,
            customer_list_constraints,
        )
        .await
        .switch()?;

    #[cfg(feature = "v1")]
    let customers = domain_customers
        .into_iter()
        .map(|domain_customer| customers::CustomerResponse::foreign_from((domain_customer, None)))
        .collect();

    #[cfg(feature = "v2")]
    let customers = domain_customers
        .into_iter()
        .map(customers::CustomerResponse::foreign_from)
        .collect();

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

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn delete_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    id: id_type::GlobalCustomerId,
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();
    id.redact_customer_details_and_generate_response(
        db,
        &merchant_context,
        key_manager_state,
        &state,
    )
    .await
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl CustomerDeleteBridge for id_type::GlobalCustomerId {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
        let customer_orig = db
            .find_customer_by_global_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .switch()?;

        let merchant_reference_id = customer_orig.merchant_reference_id.clone();

        let customer_mandates = db.find_mandate_by_global_customer_id(self).await.switch()?;

        for mandate in customer_mandates.into_iter() {
            if mandate.mandate_status == enums::MandateStatus::Active {
                Err(errors::CustomersErrorResponse::MandateActive)?
            }
        }

        match db
            .find_payment_method_list_by_global_customer_id(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                self,
                None,
            )
            .await
        {
            // check this in review
            Ok(customer_payment_methods) => {
                for pm in customer_payment_methods.into_iter() {
                    if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
                        cards::delete_card_by_locker_id(
                            state,
                            self,
                            merchant_context.get_merchant_account().get_id(),
                        )
                        .await
                        .switch()?;
                    }
                    // No solution as of now, need to discuss this further with payment_method_v2

                    // db.delete_payment_method(
                    //     key_manager_state,
                    //     key_store,
                    //     pm,
                    // )
                    // .await
                    // .switch()?;
                }
            }
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable(
                            "failed find_payment_method_by_customer_id_merchant_id_list",
                        )
                }?
            }
        };

        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let identifier = Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        );
        let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
            key_manager_state,
            type_name!(storage::Address),
            types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
            identifier.clone(),
            key,
        )
        .await
        .and_then(|val| val.try_into_operation())
        .switch()?;

        let redacted_encrypted_email = Encryptable::new(
            redacted_encrypted_value
                .clone()
                .into_inner()
                .switch_strategy(),
            redacted_encrypted_value.clone().into_encrypted(),
        );

        let updated_customer =
            storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate {
                name: Some(redacted_encrypted_value.clone()),
                email: Box::new(Some(redacted_encrypted_email)),
                phone: Box::new(Some(redacted_encrypted_value.clone())),
                description: Some(Description::from_str_unchecked(REDACTED)),
                phone_country_code: Some(REDACTED.to_string()),
                metadata: None,
                connector_customer: Box::new(None),
                default_billing_address: None,
                default_shipping_address: None,
                default_payment_method_id: None,
                status: Some(common_enums::DeleteStatus::Redacted),
                tax_registration_id: Some(redacted_encrypted_value),
            }));

        db.update_customer_by_global_id(
            key_manager_state,
            self,
            customer_orig,
            updated_customer,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

        let response = customers::CustomerDeleteResponse {
            id: self.clone(),
            merchant_reference_id,
            customer_deleted: true,
            address_deleted: true,
            payment_methods_deleted: true,
        };
        metrics::CUSTOMER_REDACTED.add(1, &[]);
        Ok(services::ApplicationResponse::Json(response))
    }
}

#[async_trait::async_trait]
trait CustomerDeleteBridge {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse>;
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn delete_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    customer_id: id_type::CustomerId,
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();
    customer_id
        .redact_customer_details_and_generate_response(
            db,
            &merchant_context,
            key_manager_state,
            &state,
        )
        .await
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerDeleteBridge for id_type::CustomerId {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
        let customer_orig = db
            .find_customer_by_customer_id_merchant_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_account().get_id(),
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .switch()?;

        let customer_mandates = db
            .find_mandate_by_merchant_id_customer_id(
                merchant_context.get_merchant_account().get_id(),
                self,
            )
            .await
            .switch()?;

        for mandate in customer_mandates.into_iter() {
            if mandate.mandate_status == enums::MandateStatus::Active {
                Err(errors::CustomersErrorResponse::MandateActive)?
            }
        }

        match db
            .find_payment_method_by_customer_id_merchant_id_list(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                self,
                merchant_context.get_merchant_account().get_id(),
                None,
            )
            .await
        {
            // check this in review
            Ok(customer_payment_methods) => {
                for pm in customer_payment_methods.into_iter() {
                    if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
                        cards::PmCards {
                            state,
                            merchant_context,
                        }
                        .delete_card_from_locker(
                            self,
                            merchant_context.get_merchant_account().get_id(),
                            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
                        )
                        .await
                        .switch()?;

                        if let Some(network_token_ref_id) = pm.network_token_requestor_reference_id
                        {
                            network_tokenization::delete_network_token_from_locker_and_token_service(
                            state,
                            self,
                            merchant_context.get_merchant_account().get_id(),
                            pm.payment_method_id.clone(),
                            pm.network_token_locker_id,
                            network_token_ref_id,
                            merchant_context,
                        )
                        .await
                        .switch()?;
                        }
                    }

                    db.delete_payment_method_by_merchant_id_payment_method_id(
                        key_manager_state,
                        merchant_context.get_merchant_key_store(),
                        merchant_context.get_merchant_account().get_id(),
                        &pm.payment_method_id,
                    )
                    .await
                    .change_context(errors::CustomersErrorResponse::InternalServerError)
                    .attach_printable(
                        "failed to delete payment method while redacting customer details",
                    )?;
                }
            }
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable(
                            "failed find_payment_method_by_customer_id_merchant_id_list",
                        )
                }?
            }
        };

        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();
        let identifier = Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        );
        let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
            key_manager_state,
            type_name!(storage::Address),
            types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
            identifier.clone(),
            key,
        )
        .await
        .and_then(|val| val.try_into_operation())
        .switch()?;

        let redacted_encrypted_email = Encryptable::new(
            redacted_encrypted_value
                .clone()
                .into_inner()
                .switch_strategy(),
            redacted_encrypted_value.clone().into_encrypted(),
        );

        let update_address = storage::AddressUpdate::Update {
            city: Some(REDACTED.to_string()),
            country: None,
            line1: Some(redacted_encrypted_value.clone()),
            line2: Some(redacted_encrypted_value.clone()),
            line3: Some(redacted_encrypted_value.clone()),
            state: Some(redacted_encrypted_value.clone()),
            zip: Some(redacted_encrypted_value.clone()),
            first_name: Some(redacted_encrypted_value.clone()),
            last_name: Some(redacted_encrypted_value.clone()),
            phone_number: Some(redacted_encrypted_value.clone()),
            country_code: Some(REDACTED.to_string()),
            updated_by: merchant_context
                .get_merchant_account()
                .storage_scheme
                .to_string(),
            email: Some(redacted_encrypted_email),
            origin_zip: Some(redacted_encrypted_value.clone()),
        };

        match db
            .update_address_by_merchant_id_customer_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_account().get_id(),
                update_address,
                merchant_context.get_merchant_key_store(),
            )
            .await
        {
            Ok(_) => Ok(()),
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable("failed update_address_by_merchant_id_customer_id")
                }
            }
        }?;

        let updated_customer = storage::CustomerUpdate::Update {
            name: Some(redacted_encrypted_value.clone()),
            email: Some(
                types::crypto_operation(
                    key_manager_state,
                    type_name!(storage::Customer),
                    types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
                    identifier,
                    key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .switch()?,
            ),
            phone: Box::new(Some(redacted_encrypted_value.clone())),
            description: Some(Description::from_str_unchecked(REDACTED)),
            phone_country_code: Some(REDACTED.to_string()),
            metadata: Box::new(None),
            connector_customer: Box::new(None),
            address_id: None,
            tax_registration_id: Some(redacted_encrypted_value.clone()),
        };

        db.update_customer_by_customer_id_merchant_id(
            key_manager_state,
            self.clone(),
            merchant_context.get_merchant_account().get_id().to_owned(),
            customer_orig,
            updated_customer,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

        let response = customers::CustomerDeleteResponse {
            customer_id: self.clone(),
            customer_deleted: true,
            address_deleted: true,
            payment_methods_deleted: true,
        };
        metrics::CUSTOMER_REDACTED.add(1, &[]);
        Ok(services::ApplicationResponse::Json(response))
    }
}

#[instrument(skip(state))]
pub async fn update_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    update_customer: customers::CustomerUpdateRequestInternal,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();
    //Add this in update call if customer can be updated anywhere else

    #[cfg(feature = "v1")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        merchant_reference_id: &update_customer.customer_id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    #[cfg(feature = "v2")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        id: &update_customer.id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    let customer = verify_id_for_update_customer
        .verify_id_and_get_customer_object(db)
        .await?;

    let updated_customer = update_customer
        .request
        .create_domain_model_from_request(
            &None,
            db,
            &merchant_context,
            key_manager_state,
            &state,
            &customer,
        )
        .await?;

    update_customer.request.generate_response(&updated_customer)
}

#[async_trait::async_trait]
trait CustomerUpdateBridge {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
        domain_customer: &'a domain::Customer,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse>;
}

#[cfg(feature = "v1")]
struct AddressStructForDbUpdate<'a> {
    update_customer: &'a customers::CustomerUpdateRequest,
    merchant_account: &'a domain::MerchantAccount,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
    state: &'a SessionState,
    domain_customer: &'a domain::Customer,
}

#[cfg(feature = "v1")]
impl AddressStructForDbUpdate<'_> {
    async fn update_address_if_sent(
        &self,
        db: &dyn StorageInterface,
    ) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
        let address = if let Some(addr) = &self.update_customer.address {
            match self.domain_customer.address_id.clone() {
                Some(address_id) => {
                    let customer_address: api_models::payments::AddressDetails = addr.clone();
                    let update_address = self
                        .update_customer
                        .get_address_update(
                            self.state,
                            customer_address,
                            self.key_store.key.get_inner().peek(),
                            self.merchant_account.storage_scheme,
                            self.merchant_account.get_id().clone(),
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while encrypting Address while Update")?;
                    Some(
                        db.update_address(
                            self.key_manager_state,
                            address_id,
                            update_address,
                            self.key_store,
                        )
                        .await
                        .switch()
                        .attach_printable(format!(
                            "Failed while updating address: merchant_id: {:?}, customer_id: {:?}",
                            self.merchant_account.get_id(),
                            self.domain_customer.customer_id
                        ))?,
                    )
                }
                None => {
                    let customer_address: api_models::payments::AddressDetails = addr.clone();

                    let address = self
                        .update_customer
                        .get_domain_address(
                            self.state,
                            customer_address,
                            self.merchant_account.get_id(),
                            &self.domain_customer.customer_id,
                            self.key_store.key.get_inner().peek(),
                            self.merchant_account.storage_scheme,
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while encrypting address")?;
                    Some(
                        db.insert_address_for_customers(
                            self.key_manager_state,
                            address,
                            self.key_store,
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while inserting new address")?,
                    )
                }
            }
        } else {


File: crates/analytics/src/payment_intents/accumulator.rs
 Public functions: 1
 Public structs: 8

use api_models::analytics::payment_intents::PaymentIntentMetricsBucketValue;
use bigdecimal::ToPrimitive;
use diesel_models::enums as storage_enums;

use super::metrics::PaymentIntentMetricRow;

#[derive(Debug, Default)]
pub struct PaymentIntentMetricsAccumulator {
    pub successful_smart_retries: CountAccumulator,
    pub total_smart_retries: CountAccumulator,
    pub smart_retried_amount: SmartRetriedAmountAccumulator,
    pub payment_intent_count: CountAccumulator,
    pub payments_success_rate: PaymentsSuccessRateAccumulator,
    pub payment_processed_amount: ProcessedAmountAccumulator,
    pub payments_distribution: PaymentsDistributionAccumulator,
}

#[derive(Debug, Default)]
pub struct ErrorDistributionRow {
    pub count: i64,
    pub total: i64,
    pub error_message: String,
}

#[derive(Debug, Default)]
pub struct ErrorDistributionAccumulator {
    pub error_vec: Vec<ErrorDistributionRow>,
}

#[derive(Debug, Default)]
#[repr(transparent)]
pub struct CountAccumulator {
    pub count: Option<i64>,
}

pub trait PaymentIntentMetricAccumulator {
    type MetricOutput;

    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow);

    fn collect(self) -> Self::MetricOutput;
}

#[derive(Debug, Default)]
pub struct SmartRetriedAmountAccumulator {
    pub amount: Option<i64>,
    pub amount_without_retries: Option<i64>,
}

#[derive(Debug, Default)]
pub struct PaymentsSuccessRateAccumulator {
    pub success: u32,
    pub success_without_retries: u32,
    pub total: u32,
}

#[derive(Debug, Default)]
pub struct ProcessedAmountAccumulator {
    pub count_with_retries: Option<i64>,
    pub total_with_retries: Option<i64>,
    pub count_without_retries: Option<i64>,
    pub total_without_retries: Option<i64>,
}

#[derive(Debug, Default)]
pub struct PaymentsDistributionAccumulator {
    pub success_without_retries: u32,
    pub failed_without_retries: u32,
    pub total: u32,
}

impl PaymentIntentMetricAccumulator for CountAccumulator {
    type MetricOutput = Option<u64>;
    #[inline]
    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
        self.count = match (self.count, metrics.count) {
            (None, None) => None,
            (None, i @ Some(_)) | (i @ Some(_), None) => i,
            (Some(a), Some(b)) => Some(a + b),
        }
    }
    #[inline]
    fn collect(self) -> Self::MetricOutput {
        self.count.and_then(|i| u64::try_from(i).ok())
    }
}

impl PaymentIntentMetricAccumulator for SmartRetriedAmountAccumulator {
    type MetricOutput = (Option<u64>, Option<u64>, Option<u64>, Option<u64>);
    #[inline]
    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
        self.amount = match (
            self.amount,
            metrics.total.as_ref().and_then(ToPrimitive::to_i64),
        ) {
            (None, None) => None,
            (None, i @ Some(_)) | (i @ Some(_), None) => i,
            (Some(a), Some(b)) => Some(a + b),
        };
        if metrics.first_attempt.unwrap_or(0) == 1 {
            self.amount_without_retries = match (
                self.amount_without_retries,
                metrics.total.as_ref().and_then(ToPrimitive::to_i64),
            ) {
                (None, None) => None,
                (None, i @ Some(_)) | (i @ Some(_), None) => i,
                (Some(a), Some(b)) => Some(a + b),
            }
        } else {
            self.amount_without_retries = Some(0);
        }
    }
    #[inline]
    fn collect(self) -> Self::MetricOutput {
        let with_retries = self.amount.and_then(|i| u64::try_from(i).ok()).or(Some(0));
        let without_retries = self
            .amount_without_retries
            .and_then(|i| u64::try_from(i).ok())
            .or(Some(0));
        (with_retries, without_retries, Some(0), Some(0))
    }
}

impl PaymentIntentMetricAccumulator for PaymentsSuccessRateAccumulator {
    type MetricOutput = (
        Option<u32>,
        Option<u32>,
        Option<u32>,
        Option<f64>,
        Option<f64>,
    );

    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
        if let Some(ref status) = metrics.status {
            if status.as_ref() == &storage_enums::IntentStatus::Succeeded {
                if let Some(success) = metrics
                    .count
                    .and_then(|success| u32::try_from(success).ok())
                {
                    self.success += success;
                    if metrics.first_attempt.unwrap_or(0) == 1 {
                        self.success_without_retries += success;
                    }
                }
            }
            if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
                && status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod
                && status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction
                && status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation
            {
                if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
                    self.total += total;
                }
            }
        }
    }

    fn collect(self) -> Self::MetricOutput {
        if self.total == 0 {
            (None, None, None, None, None)
        } else {
            let success = Some(self.success);
            let success_without_retries = Some(self.success_without_retries);
            let total = Some(self.total);

            let success_rate = match (success, total) {
                (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
                _ => None,
            };

            let success_without_retries_rate = match (success_without_retries, total) {
                (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
                _ => None,
            };

            (
                success,
                success_without_retries,
                total,
                success_rate,
                success_without_retries_rate,
            )
        }
    }
}

impl PaymentIntentMetricAccumulator for ProcessedAmountAccumulator {
    type MetricOutput = (
        Option<u64>,
        Option<u64>,
        Option<u64>,
        Option<u64>,
        Option<u64>,
        Option<u64>,
    );
    #[inline]
    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
        self.total_with_retries = match (
            self.total_with_retries,
            metrics.total.as_ref().and_then(ToPrimitive::to_i64),
        ) {
            (None, None) => None,
            (None, i @ Some(_)) | (i @ Some(_), None) => i,
            (Some(a), Some(b)) => Some(a + b),
        };

        self.count_with_retries = match (self.count_with_retries, metrics.count) {
            (None, None) => None,
            (None, i @ Some(_)) | (i @ Some(_), None) => i,
            (Some(a), Some(b)) => Some(a + b),
        };

        if metrics.first_attempt.unwrap_or(0) == 1 {
            self.total_without_retries = match (
                self.total_without_retries,
                metrics.total.as_ref().and_then(ToPrimitive::to_i64),
            ) {
                (None, None) => None,
                (None, i @ Some(_)) | (i @ Some(_), None) => i,
                (Some(a), Some(b)) => Some(a + b),
            };

            self.count_without_retries = match (self.count_without_retries, metrics.count) {
                (None, None) => None,
                (None, i @ Some(_)) | (i @ Some(_), None) => i,
                (Some(a), Some(b)) => Some(a + b),
            };
        }
    }
    #[inline]
    fn collect(self) -> Self::MetricOutput {
        let total_with_retries = u64::try_from(self.total_with_retries.unwrap_or(0)).ok();
        let count_with_retries = self.count_with_retries.and_then(|i| u64::try_from(i).ok());

        let total_without_retries = u64::try_from(self.total_without_retries.unwrap_or(0)).ok();
        let count_without_retries = self
            .count_without_retries
            .and_then(|i| u64::try_from(i).ok());

        (
            total_with_retries,
            count_with_retries,
            total_without_retries,
            count_without_retries,
            Some(0),
            Some(0),
        )
    }
}

impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator {
    type MetricOutput = (Option<f64>, Option<f64>);

    fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) {
        let first_attempt = metrics.first_attempt.unwrap_or(0);
        if let Some(ref status) = metrics.status {
            if status.as_ref() == &storage_enums::IntentStatus::Succeeded {
                if let Some(success) = metrics
                    .count
                    .and_then(|success| u32::try_from(success).ok())
                {
                    if first_attempt == 1 {
                        self.success_without_retries += success;
                    }
                }
            }
            if let Some(failed) = metrics.count.and_then(|failed| u32::try_from(failed).ok()) {
                if first_attempt == 0
                    || (first_attempt == 1
                        && status.as_ref() == &storage_enums::IntentStatus::Failed)
                {
                    self.failed_without_retries += failed;
                }
            }

            if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction
                && status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod
                && status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction
                && status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation
            {
                if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) {
                    self.total += total;
                }
            }
        }
    }

    fn collect(self) -> Self::MetricOutput {
        if self.total == 0 {
            (None, None)
        } else {
            let success_without_retries = Some(self.success_without_retries);
            let failed_without_retries = Some(self.failed_without_retries);
            let total = Some(self.total);

            let success_rate_without_retries = match (success_without_retries, total) {
                (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
                _ => None,
            };

            let failed_rate_without_retries = match (failed_without_retries, total) {
                (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
                _ => None,
            };
            (success_rate_without_retries, failed_rate_without_retries)
        }
    }
}

impl PaymentIntentMetricsAccumulator {
    pub fn collect(self) -> PaymentIntentMetricsBucketValue {
        let (
            successful_payments,
            successful_payments_without_smart_retries,
            total_payments,
            payments_success_rate,
            payments_success_rate_without_smart_retries,
        ) = self.payments_success_rate.collect();
        let (
            smart_retried_amount,
            smart_retried_amount_without_smart_retries,
            smart_retried_amount_in_usd,
            smart_retried_amount_without_smart_retries_in_usd,
        ) = self.smart_retried_amount.collect();
        let (
            payment_processed_amount,
            payment_processed_count,
            payment_processed_amount_without_smart_retries,
            payment_processed_count_without_smart_retries,
            payment_processed_amount_in_usd,
            payment_processed_amount_without_smart_retries_in_usd,
        ) = self.payment_processed_amount.collect();
        let (
            payments_success_rate_distribution_without_smart_retries,
            payments_failure_rate_distribution_without_smart_retries,
        ) = self.payments_distribution.collect();
        PaymentIntentMetricsBucketValue {
            successful_smart_retries: self.successful_smart_retries.collect(),
            total_smart_retries: self.total_smart_retries.collect(),
            smart_retried_amount,
            smart_retried_amount_in_usd,
            smart_retried_amount_without_smart_retries,
            smart_retried_amount_without_smart_retries_in_usd,
            payment_intent_count: self.payment_intent_count.collect(),
            successful_payments,
            successful_payments_without_smart_retries,
            total_payments,
            payments_success_rate,
            payments_success_rate_without_smart_retries,
            payment_processed_amount,
            payment_processed_count,
            payment_processed_amount_without_smart_retries,
            payment_processed_count_without_smart_retries,
            payments_success_rate_distribution_without_smart_retries,
            payments_failure_rate_distribution_without_smart_retries,
            payment_processed_amount_in_usd,
            payment_processed_amount_without_smart_retries_in_usd,
        }
    }
}


OpenAPI Block Path: components.schemas.OpenBankingData
{
  "oneOf": [
    {
      "type": "object",
      "required": [
        "open_banking_pis"
      ],
      "properties": {
        "open_banking_pis": {
          "type": "object"
        }
      }
    }
  ]
}


pub struct MandateCard {
    type_selection_indicator: Option<String>,
}


File: crates/hyperswitch_connectors/src/connectors/signifyd/transformers.rs

#[cfg(feature = "frm")]
pub mod api;
pub mod auth;

#[cfg(feature = "frm")]
pub use self::api::*;
pub use self::auth::*;


pub fn build(self) -> RecordMigrationStatus {
        RecordMigrationStatus {
            card_migrated: self.card_migrated,
            network_token_migrated: self.network_token_migrated,
            connector_mandate_details_migrated: self.connector_mandate_details_migrated,
            network_transaction_migrated: self.network_transaction_migrated,
        }
    }


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

use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData};
use masking::Secret;
use router::types::{self, api, storage::enums};
use test_utils::connector_auth;

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

#[derive(Clone, Copy)]
struct HyperswitchVaultTest;
impl ConnectorActions for HyperswitchVaultTest {}
impl utils::Connector for HyperswitchVaultTest {
    fn get_data(&self) -> api::ConnectorData {
        use router::connector::HyperswitchVault;
        utils::construct_connector_data_old(
            Box::new(&HyperswitchVault),
            types::Connector::HyperswitchVault,
            api::GetToken::Connector,
            None,
        )
    }

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

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

static CONNECTOR: HyperswitchVaultTest = HyperswitchVaultTest {};

fn get_default_payment_info() -> Option<utils::PaymentInfo> {
    None
}

fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    None
}

// Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_only_authorize_payment() {
    let response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized);
}

// Captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Partially captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(
            payment_method_details(),
            Some(types::PaymentsCaptureData {
                amount_to_capture: 50,
                ..utils::PaymentCaptureType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let authorize_response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}

// Voids a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_void_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_void_payment(
            payment_method_details(),
            Some(types::PaymentsCancelData {
                connector_transaction_id: String::from(""),
                cancellation_reason: Some("requested_by_customer".to_string()),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Void payment response");
    assert_eq!(response.status, enums::AttemptStatus::Voided);
}

// Refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Synchronizes a refund using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_manually_captured_refund() {
    let refund_response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_make_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_auto_captured_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Charged,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                capture_method: Some(enums::CaptureMethod::Automatic),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(response.status, enums::AttemptStatus::Charged,);
}

// Refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_auto_captured_payment() {
    let response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_succeeded_payment() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        refund_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
    CONNECTOR
        .make_payment_and_multiple_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await;
}

// Synchronizes a refund using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_refund() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Cards Negative scenarios
// Creates a payment with incorrect CVC.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_cvc: Secret::new("12345".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's security code is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry month.
#[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_exp_month: Secret::new("20".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration month is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry year.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_exp_year: Secret::new("2000".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration year is invalid.".to_string(),
    );
}

// Voids a payment using automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let void_response = CONNECTOR
        .void_payment(txn_id.unwrap(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        void_response.response.unwrap_err().message,
        "You cannot cancel this PaymentIntent because it has a status of succeeded."
    );
}

// Captures a payment using invalid connector payment id.
#[actix_web::test]
async fn should_fail_capture_for_invalid_payment() {
    let capture_response = CONNECTOR
        .capture_payment("123456789".to_string(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        capture_response.response.unwrap_err().message,
        String::from("No such payment_intent: '123456789'")
    );
}

// Refunds a payment with refund amount higher than payment amount.
#[actix_web::test]
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
    let response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 150,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Refund amount (₹1.50) is greater than charge amount (₹1.00)",
    );
}

// Connector dependent test cases goes here

// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests


pub async fn fetch_producer_tasks(
    db: &dyn SchedulerInterface,
    conf: &SchedulerSettings,
) -> CustomResult<Vec<storage::ProcessTracker>, errors::ProcessTrackerError> {
    let upper = conf.producer.upper_fetch_limit;
    let lower = conf.producer.lower_fetch_limit;
    let now = common_utils::date_time::now();
    // Add these to validations
    let time_upper_limit = now.checked_add(Duration::seconds(upper)).ok_or_else(|| {
        report!(errors::ProcessTrackerError::ConfigurationError)
            .attach_printable("Error obtaining upper limit to fetch producer tasks")
    })?;
    let time_lower_limit = now.checked_sub(Duration::seconds(lower)).ok_or_else(|| {
        report!(errors::ProcessTrackerError::ConfigurationError)
            .attach_printable("Error obtaining lower limit to fetch producer tasks")
    })?;

    let mut new_tasks = db
        .find_processes_by_time_status(
            time_lower_limit,
            time_upper_limit,
            ProcessTrackerStatus::New,
            None,
        )
        .await
        .change_context(errors::ProcessTrackerError::ProcessFetchingFailed)?;
    let mut pending_tasks = db
        .find_processes_by_time_status(
            time_lower_limit,
            time_upper_limit,
            ProcessTrackerStatus::Pending,
            None,
        )
        .await
        .change_context(errors::ProcessTrackerError::ProcessFetchingFailed)?;

    if new_tasks.is_empty() {
        warn!("No new tasks found for producer to schedule");
    }
    if pending_tasks.is_empty() {
        warn!("No pending tasks found for producer to schedule");
    }

    new_tasks.append(&mut pending_tasks);

    // Safety: Assuming we won't deal with more than `u64::MAX` tasks at once
    #[allow(clippy::as_conversions)]
    metrics::TASKS_PICKED_COUNT.add(new_tasks.len() as u64, &[]);
    Ok(new_tasks)
}


pub async fn start_process_tracker<
    T: SchedulerAppState + 'static,
    U: SchedulerSessionState + 'static,
    F,
>(
    state: &T,
    scheduler_flow: SchedulerFlow,
    scheduler_settings: Arc<SchedulerSettings>,
    channel: (mpsc::Sender<()


pub async fn get_data_from_hyperswitch_ai_workflow(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: chat_api::ChatRequest,
    session_id: Option<&str>,
) -> CustomResult<ApplicationResponse<chat_api::ChatResponse>, ChatErrors> {
    let url = format!("{}/webhook", state.conf.chat.hyperswitch_ai_host);
    let request_id = state.get_request_id();
    let request_body = chat_domain::HyperswitchAiDataRequest {
        query: chat_domain::GetDataMessage {
            message: req.message,
        },
        org_id: user_from_token.org_id,
        merchant_id: user_from_token.merchant_id,
        profile_id: user_from_token.profile_id,
    };
    logger::info!("Request for AI service: {:?}", request_body);

    let mut request_builder = RequestBuilder::new()
        .method(Method::Post)
        .url(&url)
        .attach_default_headers()
        .set_body(RequestContent::Json(Box::new(request_body.clone())));

    if let Some(request_id) = request_id {
        request_builder = request_builder.header(consts::X_REQUEST_ID, &request_id);
    }
    if let Some(session_id) = session_id {
        request_builder = request_builder.header(consts::X_CHAT_SESSION_ID, session_id);
    }

    let request = request_builder.build();

    let response = http_client::send_request(
        &state.conf.proxy,
        request,
        Some(consts::REQUEST_TIME_OUT_FOR_AI_SERVICE),
    )
    .await
    .change_context(ChatErrors::InternalServerError)
    .attach_printable("Error when sending request to AI service")?
    .json::<_>()
    .await
    .change_context(ChatErrors::InternalServerError)
    .attach_printable("Error when deserializing response from AI service")?;

    Ok(ApplicationResponse::Json(response))
}