File: crates/external_services/src/grpc_client/dynamic_routing/elimination_based_client.rs

use api_models::routing::{
    EliminationAnalyserConfig as EliminationConfig, RoutableConnectorChoice,
    RoutableConnectorChoiceWithBucketName,
};
use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom};
pub use elimination_rate::{
    elimination_analyser_client::EliminationAnalyserClient, EliminationBucketConfig,
    EliminationRequest, EliminationResponse, InvalidateBucketRequest, InvalidateBucketResponse,
    LabelWithBucketName, UpdateEliminationBucketRequest, UpdateEliminationBucketResponse,
};
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};
#[allow(
    missing_docs,
    unused_qualifications,
    clippy::unwrap_used,
    clippy::as_conversions,
    clippy::use_self
)]
pub mod elimination_rate {
    tonic::include_proto!("elimination");
}

use super::{Client, DynamicRoutingError, DynamicRoutingResult};
use crate::grpc_client::{self, GrpcHeaders};

/// The trait Elimination Based Routing would have the functions required to support performance, calculation and invalidation bucket
#[async_trait::async_trait]
pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync {
    /// To perform the elimination based routing for the list of connectors
    async fn perform_elimination_routing(
        &self,
        id: String,
        params: String,
        labels: Vec<RoutableConnectorChoice>,
        configs: Option<EliminationConfig>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<EliminationResponse>;
    /// To update the bucket size and ttl for list of connectors with its respective bucket name
    async fn update_elimination_bucket_config(
        &self,
        id: String,
        params: String,
        report: Vec<RoutableConnectorChoiceWithBucketName>,
        config: Option<EliminationConfig>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<UpdateEliminationBucketResponse>;
    /// To invalidate the previous id's bucket
    async fn invalidate_elimination_bucket(
        &self,
        id: String,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<InvalidateBucketResponse>;
}

#[async_trait::async_trait]
impl EliminationBasedRouting for EliminationAnalyserClient<Client> {
    #[instrument(skip_all)]
    async fn perform_elimination_routing(
        &self,
        id: String,
        params: String,
        label_input: Vec<RoutableConnectorChoice>,
        configs: Option<EliminationConfig>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<EliminationResponse> {
        let labels = label_input
            .into_iter()
            .map(|conn_choice| conn_choice.to_string())
            .collect::<Vec<_>>();

        let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?;

        let request = grpc_client::create_grpc_request(
            EliminationRequest {
                id,
                params,
                labels,
                config,
            },
            headers,
        );

        let response = self
            .clone()
            .get_elimination_status(request)
            .await
            .change_context(DynamicRoutingError::EliminationRateRoutingFailure(
                "Failed to perform the elimination analysis".to_string(),
            ))?
            .into_inner();

        logger::info!(dynamic_routing_response=?response);

        Ok(response)
    }

    #[instrument(skip_all)]
    async fn update_elimination_bucket_config(
        &self,
        id: String,
        params: String,
        report: Vec<RoutableConnectorChoiceWithBucketName>,
        configs: Option<EliminationConfig>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<UpdateEliminationBucketResponse> {
        let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?;

        let labels_with_bucket_name = report
            .into_iter()
            .map(|conn_choice_with_bucket| LabelWithBucketName {
                label: conn_choice_with_bucket
                    .routable_connector_choice
                    .to_string(),
                bucket_name: conn_choice_with_bucket.bucket_name,
            })
            .collect::<Vec<_>>();

        let request = grpc_client::create_grpc_request(
            UpdateEliminationBucketRequest {
                id,
                params,
                labels_with_bucket_name,
                config,
            },
            headers,
        );

        let response = self
            .clone()
            .update_elimination_bucket(request)
            .await
            .change_context(DynamicRoutingError::EliminationRateRoutingFailure(
                "Failed to update the elimination bucket".to_string(),
            ))?
            .into_inner();

        logger::info!(dynamic_routing_response=?response);

        Ok(response)
    }

    #[instrument(skip_all)]
    async fn invalidate_elimination_bucket(
        &self,
        id: String,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<InvalidateBucketResponse> {
        let request = grpc_client::create_grpc_request(InvalidateBucketRequest { id }, headers);

        let response = self
            .clone()
            .invalidate_bucket(request)
            .await
            .change_context(DynamicRoutingError::EliminationRateRoutingFailure(
                "Failed to invalidate the elimination bucket".to_string(),
            ))?
            .into_inner();

        logger::info!(dynamic_routing_response=?response);

        Ok(response)
    }
}

impl ForeignTryFrom<EliminationConfig> for EliminationBucketConfig {
    type Error = error_stack::Report<DynamicRoutingError>;
    fn foreign_try_from(config: EliminationConfig) -> Result<Self, Self::Error> {
        Ok(Self {
            bucket_size: config
                .bucket_size
                .get_required_value("bucket_size")
                .change_context(DynamicRoutingError::MissingRequiredField {
                    field: "bucket_size".to_string(),
                })?,
            bucket_leak_interval_in_secs: config
                .bucket_leak_interval_in_secs
                .get_required_value("bucket_leak_interval_in_secs")
                .change_context(DynamicRoutingError::MissingRequiredField {
                    field: "bucket_leak_interval_in_secs".to_string(),
                })?,
        })
    }
}


pub async fn pg_connection_write<T: crate::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    crate::errors::StorageError,
> {
    // Since all writes should happen to master DB only choose master DB.
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(crate::errors::StorageError::DatabaseConnectionError)
}


impl Store {
    /// # Panics
    ///
    /// Panics if there is a failure while obtaining the HashiCorp client using the provided configuration.
    /// This panic indicates a critical failure in setting up external services, and the application cannot proceed without a valid HashiCorp client.
    pub async fn new(config: &crate::Settings, test_transaction: bool, tenant: &Tenant) -> Self {
        let redis_conn = crate::connection::redis_connection(config).await;
        Self {
            master_pool: diesel_make_pg_pool(
                config.master_database.get_inner(),
                test_transaction,
                &tenant.schema,
            )
            .await,
            redis_conn: Arc::new(RedisConnectionPool::clone(
                &redis_conn,
                &tenant.redis_key_prefix,
            )),
            config: StoreConfig {
                drainer_stream_name: config.drainer.stream_name.clone(),
                drainer_num_partitions: config.drainer.num_partitions,
                use_legacy_version: config.redis.use_legacy_version,
            },
            request_id: None,
        }
    }

    pub fn use_legacy_version(&self) -> bool {
        self.config.use_legacy_version
    }
}


impl PaymentIntentUpdate {
    pub fn is_confirm_operation(&self) -> bool {
        match self {
            Self::Update(value) => value.is_confirm_operation,
            _ => false,
        }
    }
}


impl api::Payment for Noon {}


### Refactors

- **connector:** [itau] refactor error reason and code mapping for itau ([#5718](https://github.com/juspay/hyperswitch/pull/5718)) ([`f024ffb`](https://github.com/juspay/hyperswitch/commit/f024ffbdb86862175694602755c7b619c734e6b2))
- **customer_v2:** Fixed customer_v2 create panic issue ([#5699](https://github.com/juspay/hyperswitch/pull/5699)) ([`c555a88`](https://github.com/juspay/hyperswitch/commit/c555a88c6730a1216aa291bc7f7a38e3df08c469))
- **cypress_tests:** Handle api keys check in api key list call ([#5719](https://github.com/juspay/hyperswitch/pull/5719)) ([`f33e1bb`](https://github.com/juspay/hyperswitch/commit/f33e1bb65c9ea9bed782e7f9049fd216db057c6b))
- **router:** Add domain type for merchant_connector_account id ([#5685](https://github.com/juspay/hyperswitch/pull/5685)) ([`771f48c`](https://github.com/juspay/hyperswitch/commit/771f48cfe0ec6d8625ca3ff3095f5d9806915779))

**Full Changelog:** [`2024.08.28.0...2024.08.29.0`](https://github.com/juspay/hyperswitch/compare/2024.08.28.0...2024.08.29.0)

- - -

## 2024.08.28.0

### Features

- **connector:** [NOVALNET] Add template code ([#5670](https://github.com/juspay/hyperswitch/pull/5670)) ([`c3c9b27`](https://github.com/juspay/hyperswitch/commit/c3c9b2740b8e47ad75c74c886c30472a747e7119))
- **core:** Add mTLS certificates for each request ([#5636](https://github.com/juspay/hyperswitch/pull/5636)) ([`716d76c`](https://github.com/juspay/hyperswitch/commit/716d76c53e07327cd07844dd8b40f5be18c0df4b))
- **euclid:** Add a new variant in payment type i.e ppt_mandate ([#5681](https://github.com/juspay/hyperswitch/pull/5681)) ([`350aeb3`](https://github.com/juspay/hyperswitch/commit/350aeb378c41836a3b1bc7b172592d68b8e6dd17))
- **openapi:** Add open api routes for routing v2 ([#5686](https://github.com/juspay/hyperswitch/pull/5686)) ([`6bb9767`](https://github.com/juspay/hyperswitch/commit/6bb97671e7df7bfbcc2be71ad9dbccda7e1bc485))
- **user:** Business email update ([#5674](https://github.com/juspay/hyperswitch/pull/5674)) ([`b90ae90`](https://github.com/juspay/hyperswitch/commit/b90ae90c668d4134d526f05d2cc7f988a11ed496))
- Add test_mode for quickly testing payout links ([#5669](https://github.com/juspay/hyperswitch/pull/5669)) ([`406256c`](https://github.com/juspay/hyperswitch/commit/406256c067392a56b4bbc7ef2a164156ce140d9c))

### Bug Fixes

- **routing:** Fix routing routes to deserialise correctly ([#5724](https://github.com/juspay/hyperswitch/pull/5724)) ([`346d2d7`](https://github.com/juspay/hyperswitch/commit/346d2d7ad647dd66f2016207ecaaec3fe365beb1))

### Refactors

- **open_banking:** Added merchant data update in mca update ([#5655](https://github.com/juspay/hyperswitch/pull/5655)) ([`4585e16`](https://github.com/juspay/hyperswitch/commit/4585e16245dd49d8c0b877cda148524afe395009))
- Introduce a domain type for profile ID ([#5687](https://github.com/juspay/hyperswitch/pull/5687)) ([`b63d723`](https://github.com/juspay/hyperswitch/commit/b63d723b8bd2cfc146db762be4a11be64a72d196))

### Miscellaneous Tasks

- **config:** Add production connector-configs for netcetera external 3ds flow ([#5698](https://github.com/juspay/hyperswitch/pull/5698)) ([`012e5f9`](https://github.com/juspay/hyperswitch/commit/012e5f971e408a32eecb84281fa734ee12676c29))

**Full Changelog:** [`2024.08.27.0...2024.08.28.0`](https://github.com/juspay/hyperswitch/compare/2024.08.27.0...2024.08.28.0)

- - -

## 2024.08.27.0

### Features

- **business_profile:** Add tax_connector_id column in business_profile table ([#5576](https://github.com/juspay/hyperswitch/pull/5576)) ([`963a254`](https://github.com/juspay/hyperswitch/commit/963a2547e87fc7a4e8ed55627d3e7b9da2022f21))
- **customer_v2:** Added list customer v2 end point ([#5517](https://github.com/juspay/hyperswitch/pull/5517)) ([`8b4d096`](https://github.com/juspay/hyperswitch/commit/8b4d0967ae3c0451d4c040f66807edbb696033b2))
- **payment_methods_v2:** Payment methods v2 API models ([#5564](https://github.com/juspay/hyperswitch/pull/5564)) ([`e98ff95`](https://github.com/juspay/hyperswitch/commit/e98ff95b10d3990f443f8553c1a39fec9f682f96))
- Populate payment method details in payments response ([#5661](https://github.com/juspay/hyperswitch/pull/5661)) ([`32db5dd`](https://github.com/juspay/hyperswitch/commit/32db5dd1ca929d9600d9558346b52c0b753f2e53))

### Refactors

- **euclid:** Make the disabled node's relation as negative ([#5701](https://github.com/juspay/hyperswitch/pull/5701)) ([`e904d0f`](https://github.com/juspay/hyperswitch/commit/e904d0fa0dc01767f131dc18e6533bbd2549c8b9))
- **payments_response:** Remove setter from payments response ([#5676](https://github.com/juspay/hyperswitch/pull/5676)) ([`800da6a`](https://github.com/juspay/hyperswitch/commit/800da6a16a3d05ff5ae0b5557c80f7784e81a749))

### Miscellaneous Tasks

- Create v2 route for organization ([#5679](https://github.com/juspay/hyperswitch/pull/5679)) ([`0eaadc4`](https://github.com/juspay/hyperswitch/commit/0eaadc42b77e0f27cc4bb26c7e04d7c4f762b6d9))

### Build System / Dependencies

- **deps:** Bump `diesel` to `2.2.3` and `sqlx` to `0.8.1` ([#5688](https://github.com/juspay/hyperswitch/pull/5688)) ([`138134d`](https://github.com/juspay/hyperswitch/commit/138134dfb617aa679f496f6be533d9c8af7f7c06))

**Full Changelog:** [`2024.08.26.0...2024.08.27.0`](https://github.com/juspay/hyperswitch/compare/2024.08.26.0...2024.08.27.0)

- - -

## 2024.08.26.0

### Features

- **connector:** [Adyen] add dispute flows for adyen connector ([#5514](https://github.com/juspay/hyperswitch/pull/5514)) ([`ad9f91b`](https://github.com/juspay/hyperswitch/commit/ad9f91b37cc39c8fb594b48ac60c5e945a0f561f))

**Full Changelog:** [`2024.08.23.0...2024.08.26.0`](https://github.com/juspay/hyperswitch/compare/2024.08.23.0...2024.08.26.0)

- - -

## 2024.08.23.0

### Features

- **customer_v2:** Add route for customer retrieve v2 ([#5516](https://github.com/juspay/hyperswitch/pull/5516)) ([`914cab0`](https://github.com/juspay/hyperswitch/commit/914cab0d7b5a757e58b4ffe175a30f05ed1aeb1c))
- **router:** [cybersource] add disable_avs and disable_cvn flag in connector metadata ([#5667](https://github.com/juspay/hyperswitch/pull/5667)) ([`36cc0cc`](https://github.com/juspay/hyperswitch/commit/36cc0ccbe69dc8f43c4cdd6daaea5e07beea8514))

**Full Changelog:** [`2024.08.22.1...2024.08.23.0`](https://github.com/juspay/hyperswitch/compare/2024.08.22.1...2024.08.23.0)

- - -

## 2024.08.22.1

### Features

- **global_id:** Create a `GlobalId` domain type ([#5644](https://github.com/juspay/hyperswitch/pull/5644)) ([`d14c788`](https://github.com/juspay/hyperswitch/commit/d14c7887e9af705ca2c0b9876d904146bd2a6bea))
- **router:** Collect customer address details based on business profile config regardless of connector required fields ([#5418](https://github.com/juspay/hyperswitch/pull/5418)) ([`bda29cb`](https://github.com/juspay/hyperswitch/commit/bda29cb1b5c8654e67703b59e96e7e2aa5580abd))
- **user:** Add list org, merchant and profile api ([#5662](https://github.com/juspay/hyperswitch/pull/5662)) ([`98cbf2e`](https://github.com/juspay/hyperswitch/commit/98cbf2e71a6112a82ef3509d17e328fc463d4827))
- **user_role:** Add update by lineage DB function ([#5651](https://github.com/juspay/hyperswitch/pull/5651)) ([`ca72fed`](https://github.com/juspay/hyperswitch/commit/ca72fedae82194abb7216854c7dd61c64d57b1d6))
- Add new routes for profile level list apis ([#5589](https://github.com/juspay/hyperswitch/pull/5589)) ([`d3521e7`](https://github.com/juspay/hyperswitch/commit/d3521e7e76b327d88ec5506302102e66f014cb95))

### Bug Fixes

- **opensearch:** Added date-suffixed indexes for opensearch locally ([#5635](https://github.com/juspay/hyperswitch/pull/5635)) ([`da5102e`](https://github.com/juspay/hyperswitch/commit/da5102efe299582484709d24a04395f44a5c7b05))
- **router:** [Adyen] prevent partial submission of billing address and add required fields for all payment methods ([#5660](https://github.com/juspay/hyperswitch/pull/5660)) ([`6d60617`](https://github.com/juspay/hyperswitch/commit/6d606179dcb037c848b05c023a61e0509b81aecf))

### Refactors

- **core:** Refactor fallback routing behaviour in payments for v2 ([#5642](https://github.com/juspay/hyperswitch/pull/5642)) ([`22743ac`](https://github.com/juspay/hyperswitch/commit/22743ac37009e11b7518c5ab013e88360c658c34))
- **router:** Add connector_transaction_id, send response body and use admin_api_auth_with_merchant_id for payments manual update flow ([#5658](https://github.com/juspay/hyperswitch/pull/5658)) ([`5be0c2b`](https://github.com/juspay/hyperswitch/commit/5be0c2bfd28e5a898842e1e24b51b41439aa92b3))
- Update v2 resource update endpoints to use `PUT` method instead of `POST` ([#5657](https://github.com/juspay/hyperswitch/pull/5657)) ([`7f10678`](https://github.com/juspay/hyperswitch/commit/7f10678c36c57d75a5cd4420e3d7509916f13e39))

### Documentation

- **README:** Adding Contributors guide ([#5184](https://github.com/juspay/hyperswitch/pull/5184)) ([`b88d930`](https://github.com/juspay/hyperswitch/commit/b88d93023159a56987cdbd21983af397d7a13110))
- Adding redirect url details ([#5507](https://github.com/juspay/hyperswitch/pull/5507)) ([`c36c5d6`](https://github.com/juspay/hyperswitch/commit/c36c5d69a7cbfdb8c62641e99c530752c3802653))

**Full Changelog:** [`2024.08.22.0...2024.08.22.1`](https://github.com/juspay/hyperswitch/compare/2024.08.22.0...2024.08.22.1)

- - -

## 2024.08.22.0

### Features

- **business_profile:** Introduce business profile v2 update endpoint ([#5641](https://github.com/juspay/hyperswitch/pull/5641)) ([`beb4fb0`](https://github.com/juspay/hyperswitch/commit/beb4fb050fb70e948d03f6c51e40bb906ae155ef))
- **core:** Add localization support for unified error messages ([#5624](https://github.com/juspay/hyperswitch/pull/5624)) ([`1f0ee3c`](https://github.com/juspay/hyperswitch/commit/1f0ee3cae0764367d59ca36e3f379e244e613813))
- **payments:** Add support for aggregates in payments ([#5654](https://github.com/juspay/hyperswitch/pull/5654)) ([`9f3b2fb`](https://github.com/juspay/hyperswitch/commit/9f3b2fba3eee4dc4d0f029afbca6d8fa3859c0e0))
- Use admin_api_key auth along with merchant_id for connector list, retrieve and update apis ([#5613](https://github.com/juspay/hyperswitch/pull/5613)) ([`b60ced0`](https://github.com/juspay/hyperswitch/commit/b60ced02ffba21624a9491a63fcde1c04cfa0b06))

### Bug Fixes

- **core:** [Payouts] remove fetching profile_id for mca creation ([#5656](https://github.com/juspay/hyperswitch/pull/5656)) ([`1e64ed7`](https://github.com/juspay/hyperswitch/commit/1e64ed79bc656df684ee0ed8d0c4a0ecd219eb75))
- **router:** Fix merchant_connector_account retrieve and delete v2 routes ([#5653](https://github.com/juspay/hyperswitch/pull/5653)) ([`7234119`](https://github.com/juspay/hyperswitch/commit/723411905a81c44302cb9bf5ec58d3495ecc2864))

### Miscellaneous Tasks

- Generate openapi specs for organization endpoint for v1 and v2 ([#5648](https://github.com/juspay/hyperswitch/pull/5648)) ([`1d08c7b`](https://github.com/juspay/hyperswitch/commit/1d08c7b932f2c039db71d8492b0be966df9702d9))

**Full Changelog:** [`2024.08.21.0...2024.08.22.0`](https://github.com/juspay/hyperswitch/compare/2024.08.21.0...2024.08.22.0)

- - -

## 2024.08.21.0

### Features

- **business_profile_v2:** Business profile v2 create and retrieve endpoint ([#5606](https://github.com/juspay/hyperswitch/pull/5606)) ([`6e7b38a`](https://github.com/juspay/hyperswitch/commit/6e7b38a622d1399260f9a144e07a58bc6a7a6655))
- **core:** [Payouts] add merchant_connector_id to payout_attempt and show in response ([#5214](https://github.com/juspay/hyperswitch/pull/5214)) ([`4cc389a`](https://github.com/juspay/hyperswitch/commit/4cc389aa7061304ba517e3ec612d1f793ab35e67))
- **users:** Role info api with parent tags ([#5536](https://github.com/juspay/hyperswitch/pull/5536)) ([`eb7678f`](https://github.com/juspay/hyperswitch/commit/eb7678f756c32a9ec71df8e18ccacb61114e9fab))

### Bug Fixes

- **api-reference:** Fix api paths for `merchant_connector_account` in api-reference-v2 ([#5645](https://github.com/juspay/hyperswitch/pull/5645)) ([`ac7d8c5`](https://github.com/juspay/hyperswitch/commit/ac7d8c572ce34cb120c9123fd2748ba86049b6f1))

### Refactors

- **core:** Make p24 billing_details optional ([#5638](https://github.com/juspay/hyperswitch/pull/5638)) ([`4651147`](https://github.com/juspay/hyperswitch/commit/465114796349c219336aa8016e72356013871c96))

**Full Changelog:** [`2024.08.15.0...2024.08.21.0`](https://github.com/juspay/hyperswitch/compare/2024.08.15.0...2024.08.21.0)

- - -

## 2024.08.15.0

### Features

- **connector:** [Paybox] add paybox connector ([#5575](https://github.com/juspay/hyperswitch/pull/5575)) ([`e4f4fba`](https://github.com/juspay/hyperswitch/commit/e4f4fbafe643b25078ebab6d054c6fa41a081f1a))
- **customer_v2:** Customer v2 refactor customer v2 update endpoint ([#5490](https://github.com/juspay/hyperswitch/pull/5490)) ([`17703fe`](https://github.com/juspay/hyperswitch/commit/17703fe2cbd693cf4f417c061df3c42c63ebc745))
- **payout_link:** Return total_count in filtered payouts list API response ([#5538](https://github.com/juspay/hyperswitch/pull/5538)) ([`34f648e`](https://github.com/juspay/hyperswitch/commit/34f648e29ba91601c9d78dc95ab405dc8d6ba97e))
- **users:** Add support for profile user delete ([#5541](https://github.com/juspay/hyperswitch/pull/5541)) ([`19a9180`](https://github.com/juspay/hyperswitch/commit/19a9180925cf1a73aac74b5dfe5b68ba658718be))

### Bug Fixes

- **cypress:**
  - Fix naming convention in iatapay ideal ([#5618](https://github.com/juspay/hyperswitch/pull/5618)) ([`fe19127`](https://github.com/juspay/hyperswitch/commit/fe19127135ef0ffb702b64bb3d04ea8015109f6e))
  - Fix hooks in cypress for 3ds ([#5617](https://github.com/juspay/hyperswitch/pull/5617)) ([`d43648b`](https://github.com/juspay/hyperswitch/commit/d43648b9f5f87ce2e46e9e2dc49327c8a6af0f7d))
- **opensearch:** Sorted the global search results in descending order ([#5623](https://github.com/juspay/hyperswitch/pull/5623)) ([`27096e7`](https://github.com/juspay/hyperswitch/commit/27096e71e451c7b30d2ec0be8aea68f226984fe4))
- **payout:** Use mca_connector_id from ConnectorData to fetch mca_account ([#5620](https://github.com/juspay/hyperswitch/pull/5620)) ([`92a07cf`](https://github.com/juspay/hyperswitch/commit/92a07cf5e4fcce6bf83f1802e07b1dd0b9cf67e2))
- **pm_auth:** Fixed deserialize logic in pm_auth core ([#5615](https://github.com/juspay/hyperswitch/pull/5615)) ([`f66b09f`](https://github.com/juspay/hyperswitch/commit/f66b09fed50bb89cd512d01af22a51107a0c9869))
- **router:** Allow payments update for requires_payment_method and requires_confirmation intent status only ([#5616](https://github.com/juspay/hyperswitch/pull/5616)) ([`805540a`](https://github.com/juspay/hyperswitch/commit/805540a1d02ec6ce61e21c66349dbb0fb3403e69))

### Refactors

- **connector:**
  - Added amount conversion framework for Ebanx ([#5452](https://github.com/juspay/hyperswitch/pull/5452)) ([`67d580c`](https://github.com/juspay/hyperswitch/commit/67d580c0ebcfc070ad6ff5cab4079ca447541ee4))
  - Added amount conversion framework for multisafepay ([#4982](https://github.com/juspay/hyperswitch/pull/4982)) ([`556dd10`](https://github.com/juspay/hyperswitch/commit/556dd103dbdf46bb84b247547dd7cca3d2eec91c))
  - Add amount conversion framework to Globepay ([#5470](https://github.com/juspay/hyperswitch/pull/5470)) ([`66cc0a3`](https://github.com/juspay/hyperswitch/commit/66cc0a3b395d95bce56c63c4a561b7b42122d28e))
  - Add amount conversion framework to Forte ([#5461](https://github.com/juspay/hyperswitch/pull/5461)) ([`2249010`](https://github.com/juspay/hyperswitch/commit/2249010ceb9fc03249d8feb428f2209b2d2ee9f4))
- **redis:** Add redis commands required for success rate based routing ([#5610](https://github.com/juspay/hyperswitch/pull/5610)) ([`340714c`](https://github.com/juspay/hyperswitch/commit/340714ce1b8359bb908ec3873c88822a2c0054f0))
- **router:**
  - Add api_version and make profile_id mandatory in mca v2 ([#5602](https://github.com/juspay/hyperswitch/pull/5602)) ([`56791c2`](https://github.com/juspay/hyperswitch/commit/56791c27437f5df331447c617c7b2b8041775e86))
  - Use the saved billing details in the recurring payments ([#5631](https://github.com/juspay/hyperswitch/pull/5631)) ([`5fa7b14`](https://github.com/juspay/hyperswitch/commit/5fa7b147aa81832dcda767efa5c02f2e82131f73))
- **webhook_events:** Allow listing unique webhook events based on profile ID ([#5598](https://github.com/juspay/hyperswitch/pull/5598)) ([`8bcda2c`](https://github.com/juspay/hyperswitch/commit/8bcda2cea480083179bd071e8ff466103e61efc1))

### Miscellaneous Tasks

- Wasm changes required for plaid ([#5633](https://github.com/juspay/hyperswitch/pull/5633)) ([`8fa51b7`](https://github.com/juspay/hyperswitch/commit/8fa51b7b1cdf4347dc5778859597aa9ed1691790))

**Full Changelog:** [`2024.08.14.0...2024.08.15.0`](https://github.com/juspay/hyperswitch/compare/2024.08.14.0...2024.08.15.0)

- - -

## 2024.08.14.0

### Features

- **connector:** Create Taxjar connector ([#5597](https://github.com/juspay/hyperswitch/pull/5597)) ([`0ab0aa1`](https://github.com/juspay/hyperswitch/commit/0ab0aa1a94fe98719d51ff89d27935a30cb33721))

### Bug Fixes

- **router:** Return missing required field error when a domain is missing during apple pay session call ([#5596](https://github.com/juspay/hyperswitch/pull/5596)) ([`751ba15`](https://github.com/juspay/hyperswitch/commit/751ba15482f6e4aed403ff947eea01b75093087e))

### Refactors

- **connector:** Add amount conversion framework for ACI ([#5456](https://github.com/juspay/hyperswitch/pull/5456)) ([`93aa522`](https://github.com/juspay/hyperswitch/commit/93aa522929fc8538894bd4bc934cb17a793d2e24))
- **routing:** Refactor fallback routing apis for v2 ([#5592](https://github.com/juspay/hyperswitch/pull/5592)) ([`051086f`](https://github.com/juspay/hyperswitch/commit/051086f7b657600efe2d6a41023572610ef41ccb))

### Documentation


pub trait PaymentMetricAnalytics: LoadRow<PaymentMetricRow> {}


pub struct CoinbasePaymentResponseData {
    pub id: String,
    pub code: String,
    pub name: Option<Secret<String>>,
    pub utxo: Option<bool>,
    pub pricing: HashMap<String, OverpaymentAbsoluteThreshold>,
    pub fee_rate: Option<f64>,
    pub logo_url: Option<String>,
    pub metadata: Option<Metadata>,
    pub payments: Vec<PaymentElement>,
    pub resource: Option<String>,
    pub timeline: Vec<Timeline>,
    pub pwcb_only: bool,
    pub created_at: String,
    pub expires_at: String,
    pub hosted_url: String,
    pub brand_color: String,
    pub description: Option<String>,
    pub confirmed_at: Option<String>,
    pub fees_settled: Option<bool>,
    pub pricing_type: String,
    pub redirects: Redirects,
    pub support_email: pii::Email,
    pub brand_logo_url: String,
    pub offchain_eligible: Option<bool>,
    pub organization_name: String,
    pub payment_threshold: Option<PaymentThreshold>,
    pub coinbase_managed_merchant: Option<bool>,
}


pub struct GsmDeleteRequest {
    /// The connector through which payment has gone through
    pub connector: String,
    /// The flow in which the code and message occurred for a connector
    pub flow: String,
    /// The sub_flow in which the code and message occurred  for a connector
    pub sub_flow: String,
    /// code received from the connector
    pub code: String,
    /// message received from the connector
    pub message: String,
}


pub struct ExtendedAuthorizationAppliedBool(bool);


impl api::Payment for Nexinets {}


pub struct CybersourceAuthorizationOptions {
    initiator: Option<CybersourcePaymentInitiator>,
    merchant_intitiated_transaction: Option<MerchantInitiatedTransaction>,
    ignore_avs_result: Option<bool>,
    ignore_cv_result: Option<bool>,
}


File: crates/analytics/src/payment_intents/metrics/successful_smart_retries.rs

use std::collections::HashSet;

use api_models::{
    analytics::{
        payment_intents::{
            PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier,
        },
        Granularity, TimeRange,
    },
    enums::IntentStatus,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::PaymentIntentMetricRow;
use crate::{
    enums::AuthInfo,
    query::{
        Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql,
        Window,
    },
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(super) struct SuccessfulSmartRetries;

#[async_trait::async_trait]
impl<T> super::PaymentIntentMetric<T> for SuccessfulSmartRetries
where
    T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentIntentDimensions],
        auth: &AuthInfo,
        filters: &PaymentIntentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>>
    {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentIntent);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        query_builder
            .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt)
            .switch()?;
        query_builder
            .add_custom_filter_clause("status", IntentStatus::Succeeded, FilterTypes::Equal)
            .switch()?;
        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }
        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<PaymentIntentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentIntentMetricsBucketIdentifier::new(
                        i.status.as_ref().map(|i| i.0),
                        i.currency.as_ref().map(|i| i.0),
                        i.profile_id.clone(),
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


pub async fn get_with_payment_id_if_present(
        conn: &PgPooledConn,
        payment_id: common_utils::id_type::PaymentId,
        merchant_id: common_utils::id_type::MerchantId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::payment_id
                .eq(payment_id)
                .and(dsl::merchant_id.eq(merchant_id)),
        )
        .await
    }


File: crates/router/src/core/fraud_check.rs
 Public functions: 12

use std::fmt::Debug;

use api_models::{self, enums as api_enums};
use common_enums::CaptureMethod;
use error_stack::ResultExt;
use masking::PeekInterface;
use router_env::{
    logger,
    tracing::{self, instrument},
};

use self::{
    flows::{self as frm_flows, FeatureFrm},
    types::{
        self as frm_core_types, ConnectorDetailsCore, FrmConfigsObject, FrmData, FrmInfo,
        PaymentDetails, PaymentToFrmData,
    },
};
use super::errors::{ConnectorErrorExt, RouterResponse};
use crate::{
    core::{
        errors::{self, RouterResult},
        payments::{self, flows::ConstructFlowSpecificData, operations::BoxedOperation},
    },
    db::StorageInterface,
    routes::{app::ReqState, SessionState},
    services,
    types::{
        self as oss_types,
        api::{
            fraud_check as frm_api, routing::FrmRoutingAlgorithm, Connector,
            FraudCheckConnectorData, Fulfillment,
        },
        domain, fraud_check as frm_types,
        storage::{
            enums::{
                AttemptStatus, FraudCheckLastStep, FraudCheckStatus, FraudCheckType, FrmSuggestion,
                IntentStatus,
            },
            fraud_check::{FraudCheck, FraudCheckUpdate},
            PaymentIntent,
        },
    },
    utils::ValueExt,
};
pub mod flows;
pub mod operation;
pub mod types;

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn call_frm_service<D: Clone, F, Req, OperationData>(
    state: &SessionState,
    payment_data: &OperationData,
    frm_data: &mut FrmData,
    merchant_context: &domain::MerchantContext,
    customer: &Option<domain::Customer>,
) -> RouterResult<oss_types::RouterData<F, Req, frm_types::FraudCheckResponseData>>
where
    F: Send + Clone,

    OperationData: payments::OperationSessionGetters<D> + Send + Sync + Clone,

    // To create connector flow specific interface data
    FrmData: ConstructFlowSpecificData<F, Req, frm_types::FraudCheckResponseData>,
    oss_types::RouterData<F, Req, frm_types::FraudCheckResponseData>: FeatureFrm<F, Req> + Send,

    // To construct connector flow specific api
    dyn Connector: services::api::ConnectorIntegration<F, Req, frm_types::FraudCheckResponseData>,
{
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn call_frm_service<D: Clone, F, Req, OperationData>(
    state: &SessionState,
    payment_data: &OperationData,
    frm_data: &mut FrmData,
    merchant_context: &domain::MerchantContext,
    customer: &Option<domain::Customer>,
) -> RouterResult<oss_types::RouterData<F, Req, frm_types::FraudCheckResponseData>>
where
    F: Send + Clone,

    OperationData: payments::OperationSessionGetters<D> + Send + Sync + Clone,

    // To create connector flow specific interface data
    FrmData: ConstructFlowSpecificData<F, Req, frm_types::FraudCheckResponseData>,
    oss_types::RouterData<F, Req, frm_types::FraudCheckResponseData>: FeatureFrm<F, Req> + Send,

    // To construct connector flow specific api
    dyn Connector: services::api::ConnectorIntegration<F, Req, frm_types::FraudCheckResponseData>,
{
    let merchant_connector_account = payments::construct_profile_id_and_get_mca(
        state,
        merchant_context,
        payment_data,
        &frm_data.connector_details.connector_name,
        None,
        false,
    )
    .await?;

    frm_data
        .payment_attempt
        .connector_transaction_id
        .clone_from(&payment_data.get_payment_attempt().connector_transaction_id);

    let mut router_data = frm_data
        .construct_router_data(
            state,
            &frm_data.connector_details.connector_name,
            merchant_context,
            customer,
            &merchant_connector_account,
            None,
            None,
        )
        .await?;

    router_data.status = payment_data.get_payment_attempt().status;
    if matches!(
        frm_data.fraud_check.frm_transaction_type,
        FraudCheckType::PreFrm
    ) && matches!(
        frm_data.fraud_check.last_step,
        FraudCheckLastStep::CheckoutOrSale
    ) {
        frm_data.fraud_check.last_step = FraudCheckLastStep::TransactionOrRecordRefund
    }

    let connector =
        FraudCheckConnectorData::get_connector_by_name(&frm_data.connector_details.connector_name)?;
    let router_data_res = router_data
        .decide_frm_flows(
            state,
            &connector,
            payments::CallConnectorAction::Trigger,
            merchant_context,
        )
        .await?;

    Ok(router_data_res)
}

#[cfg(feature = "v2")]
pub async fn should_call_frm<F, D>(
    _merchant_context: &domain::MerchantContext,
    _payment_data: &D,
    _state: &SessionState,
) -> RouterResult<(
    bool,
    Option<FrmRoutingAlgorithm>,
    Option<common_utils::id_type::ProfileId>,
    Option<FrmConfigsObject>,
)>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    // Frm routing algorithm is not present in the merchant account
    // it has to be fetched from the business profile
    todo!()
}

#[cfg(feature = "v1")]
pub async fn should_call_frm<F, D>(
    merchant_context: &domain::MerchantContext,
    payment_data: &D,
    state: &SessionState,
) -> RouterResult<(
    bool,
    Option<FrmRoutingAlgorithm>,
    Option<common_utils::id_type::ProfileId>,
    Option<FrmConfigsObject>,
)>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    use common_utils::ext_traits::OptionExt;
    use masking::ExposeInterface;

    let db = &*state.store;
    match merchant_context
        .get_merchant_account()
        .frm_routing_algorithm
        .clone()
    {
        Some(frm_routing_algorithm_value) => {
            let frm_routing_algorithm_struct: FrmRoutingAlgorithm = frm_routing_algorithm_value
                .clone()
                .parse_value("FrmRoutingAlgorithm")
                .change_context(errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "frm_routing_algorithm",
                })
                .attach_printable("Data field not found in frm_routing_algorithm")?;

            let profile_id = payment_data
                .get_payment_intent()
                .profile_id
                .as_ref()
                .get_required_value("profile_id")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("profile_id is not set in payment_intent")?
                .clone();

            #[cfg(feature = "v1")]
            let merchant_connector_account_from_db_option = db
                .find_merchant_connector_account_by_profile_id_connector_name(
                    &state.into(),
                    &profile_id,
                    &frm_routing_algorithm_struct.data,
                    merchant_context.get_merchant_key_store(),
                )
                .await
                .map_err(|error| {
                    logger::error!(
                        "{:?}",
                        error.change_context(
                            errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                                id: merchant_context
                                    .get_merchant_account()
                                    .get_id()
                                    .get_string_repr()
                                    .to_owned(),
                            }
                        )
                    )
                })
                .ok();
            let enabled_merchant_connector_account_from_db_option =
                merchant_connector_account_from_db_option.and_then(|mca| {
                    if mca.disabled.unwrap_or(false) {
                        logger::info!("No eligible connector found for FRM");
                        None
                    } else {
                        Some(mca)
                    }
                });

            #[cfg(feature = "v2")]
            let merchant_connector_account_from_db_option: Option<
                domain::MerchantConnectorAccount,
            > = {
                let _ = key_store;
                let _ = frm_routing_algorithm_struct;
                let _ = profile_id;
                todo!()
            };

            match enabled_merchant_connector_account_from_db_option {
                Some(merchant_connector_account_from_db) => {
                    let frm_configs_option = merchant_connector_account_from_db
                        .frm_configs
                        .ok_or(errors::ApiErrorResponse::MissingRequiredField {
                            field_name: "frm_configs",
                        })
                        .ok();
                    match frm_configs_option {
                        Some(frm_configs_value) => {
                            let frm_configs_struct: Vec<api_models::admin::FrmConfigs> = frm_configs_value
                                .into_iter()
                                .map(|config| { config
                                    .expose()
                                    .parse_value("FrmConfigs")
                                    .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                                            field_name: "frm_configs".to_string(),
                                            expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","flow": "post"}]}]"#.to_string(),
                                        })
                                })
                                .collect::<Result<Vec<_>, _>>()?;

                            let mut is_frm_connector_enabled = false;
                            let mut is_frm_pm_enabled = false;
                            let connector = payment_data.get_payment_attempt().connector.clone();
                            let filtered_frm_config = frm_configs_struct
                                .iter()
                                .filter(|frm_config| match (&connector, &frm_config.gateway) {
                                    (Some(current_connector), Some(configured_connector)) => {
                                        let is_enabled =
                                            *current_connector == configured_connector.to_string();
                                        if is_enabled {
                                            is_frm_connector_enabled = true;
                                        }
                                        is_enabled
                                    }
                                    (None, _) | (_, None) => true,
                                })
                                .collect::<Vec<_>>();
                            let filtered_payment_methods = filtered_frm_config
                                .iter()
                                .map(|frm_config| {
                                    let filtered_frm_config_by_pm = frm_config
                                        .payment_methods
                                        .iter()
                                        .filter(|frm_config_pm| {
                                            match (
                                                payment_data.get_payment_attempt().payment_method,
                                                frm_config_pm.payment_method,
                                            ) {
                                                (
                                                    Some(current_pm),
                                                    Some(configured_connector_pm),
                                                ) => {
                                                    let is_enabled = current_pm.to_string()
                                                        == configured_connector_pm.to_string();
                                                    if is_enabled {
                                                        is_frm_pm_enabled = true;
                                                    }
                                                    is_enabled
                                                }
                                                (None, _) | (_, None) => true,
                                            }
                                        })
                                        .collect::<Vec<_>>();
                                    filtered_frm_config_by_pm
                                })
                                .collect::<Vec<_>>()
                                .concat();
                            let is_frm_enabled = is_frm_connector_enabled && is_frm_pm_enabled;
                            logger::debug!(
                                "is_frm_connector_enabled {:?}, is_frm_pm_enabled:  {:?}, is_frm_enabled :{:?}",
                                is_frm_connector_enabled,
                                is_frm_pm_enabled,
                                is_frm_enabled
                            );
                            // filtered_frm_config...
                            // Panic Safety: we are first checking if the object is present... only if present, we try to fetch index 0
                            let frm_configs_object = FrmConfigsObject {
                                frm_enabled_gateway: filtered_frm_config
                                    .first()
                                    .and_then(|c| c.gateway),
                                frm_enabled_pm: filtered_payment_methods
                                    .first()
                                    .and_then(|pm| pm.payment_method),
                                // flow type should be consumed from payment_method.flow. To provide backward compatibility, if we don't find it there, we consume it from payment_method.payment_method_types[0].flow_type.
                                frm_preferred_flow_type: filtered_payment_methods
                                    .first()
                                    .and_then(|pm| pm.flow.clone())
                                    .or(filtered_payment_methods.first().and_then(|pm| {
                                        pm.payment_method_types.as_ref().and_then(|pmt| {
                                            pmt.first().map(|pmts| pmts.flow.clone())
                                        })
                                    }))
                                    .ok_or(errors::ApiErrorResponse::InvalidDataFormat {
                                            field_name: "frm_configs".to_string(),
                                            expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","flow": "post"}]}]"#.to_string(),
                                    })?,
                            };
                            logger::debug!(
                                "frm_routing_configs: {:?} {:?} {:?} {:?}",
                                frm_routing_algorithm_struct,
                                profile_id,
                                frm_configs_object,
                                is_frm_enabled
                            );
                            Ok((
                                is_frm_enabled,
                                Some(frm_routing_algorithm_struct),
                                Some(profile_id),
                                Some(frm_configs_object),
                            ))
                        }
                        None => {
                            logger::error!("Cannot find frm_configs for FRM provider");
                            Ok((false, None, None, None))
                        }
                    }
                }
                None => {
                    logger::error!("Cannot find merchant connector account for FRM provider");
                    Ok((false, None, None, None))
                }
            }
        }
        _ => Ok((false, None, None, None)),
    }
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn make_frm_data_and_fraud_check_operation<F, D>(
    _db: &dyn StorageInterface,
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: D,
    frm_routing_algorithm: FrmRoutingAlgorithm,
    profile_id: common_utils::id_type::ProfileId,
    frm_configs: FrmConfigsObject,
    _customer: &Option<domain::Customer>,
) -> RouterResult<FrmInfo<F, D>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    todo!()
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn make_frm_data_and_fraud_check_operation<F, D>(
    _db: &dyn StorageInterface,
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: D,
    frm_routing_algorithm: FrmRoutingAlgorithm,
    profile_id: common_utils::id_type::ProfileId,
    frm_configs: FrmConfigsObject,
    _customer: &Option<domain::Customer>,
) -> RouterResult<FrmInfo<F, D>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    let order_details = payment_data
        .get_payment_intent()
        .order_details
        .clone()
        .or_else(||
            // when the order_details are present within the meta_data, we need to take those to support backward compatibility
            payment_data.get_payment_intent().metadata.clone().and_then(|meta| {
                let order_details = meta.get("order_details").to_owned();
                order_details.map(|order| vec![masking::Secret::new(order.to_owned())])
            }))
        .map(|order_details_value| {
            order_details_value
                .into_iter()
                .map(|data| {
                    data.peek()
                        .to_owned()
                        .parse_value("OrderDetailsWithAmount")
                        .attach_printable("unable to parse OrderDetailsWithAmount")
                })
                .collect::<Result<Vec<_>, _>>()
                .unwrap_or_default()
        });

    let frm_connector_details = ConnectorDetailsCore {
        connector_name: frm_routing_algorithm.data,
        profile_id,
    };

    let payment_to_frm_data = PaymentToFrmData {
        amount: payment_data.get_amount(),
        payment_intent: payment_data.get_payment_intent().to_owned(),
        payment_attempt: payment_data.get_payment_attempt().to_owned(),
        merchant_account: merchant_context.get_merchant_account().to_owned(),
        address: payment_data.get_address().clone(),
        connector_details: frm_connector_details.clone(),
        order_details,
        frm_metadata: payment_data.get_payment_intent().frm_metadata.clone(),
    };

    let fraud_check_operation: operation::BoxedFraudCheckOperation<F, D> =
        fraud_check_operation_by_frm_preferred_flow_type(frm_configs.frm_preferred_flow_type);
    let frm_data = fraud_check_operation
        .to_get_tracker()?
        .get_trackers(state, payment_to_frm_data, frm_connector_details)
        .await?;
    Ok(FrmInfo {
        fraud_check_operation,
        frm_data,
        suggested_action: None,
    })
}

fn fraud_check_operation_by_frm_preferred_flow_type<F, D>(
    frm_preferred_flow_type: api_enums::FrmPreferredFlowTypes,
) -> operation::BoxedFraudCheckOperation<F, D>
where
    operation::FraudCheckPost: operation::FraudCheckOperation<F, D>,
    operation::FraudCheckPre: operation::FraudCheckOperation<F, D>,
{
    match frm_preferred_flow_type {
        api_enums::FrmPreferredFlowTypes::Pre => Box::new(operation::FraudCheckPre),
        api_enums::FrmPreferredFlowTypes::Post => Box::new(operation::FraudCheckPost),
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn pre_payment_frm_core<F, Req, D>(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: &mut D,
    frm_info: &mut FrmInfo<F, D>,
    frm_configs: FrmConfigsObject,
    customer: &Option<domain::Customer>,
    should_continue_transaction: &mut bool,
    should_continue_capture: &mut bool,
    operation: &BoxedOperation<'_, F, Req, D>,
) -> RouterResult<Option<FrmData>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    let mut frm_data = None;
    if is_operation_allowed(operation) {
        frm_data = if let Some(frm_data) = &mut frm_info.frm_data {
            if matches!(
                frm_configs.frm_preferred_flow_type,
                api_enums::FrmPreferredFlowTypes::Pre
            ) {
                let fraud_check_operation = &mut frm_info.fraud_check_operation;

                let frm_router_data = fraud_check_operation
                    .to_domain()?
                    .pre_payment_frm(state, payment_data, frm_data, merchant_context, customer)
                    .await?;
                let _router_data = call_frm_service::<F, frm_api::Transaction, _, D>(
                    state,
                    payment_data,
                    frm_data,
                    merchant_context,
                    customer,
                )
                .await?;
                let frm_data_updated = fraud_check_operation
                    .to_update_tracker()?
                    .update_tracker(
                        state,
                        merchant_context.get_merchant_key_store(),
                        frm_data.clone(),
                        payment_data,
                        None,
                        frm_router_data,
                    )
                    .await?;
                let frm_fraud_check = frm_data_updated.fraud_check.clone();
                payment_data.set_frm_message(frm_fraud_check.clone());
                if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) {
                    *should_continue_transaction = false;
                    frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction);
                }
                logger::debug!(
                    "frm_updated_data: {:?} {:?}",
                    frm_info.fraud_check_operation,
                    frm_info.suggested_action
                );
                Some(frm_data_updated)
            } else if matches!(
                frm_configs.frm_preferred_flow_type,
                api_enums::FrmPreferredFlowTypes::Post
            ) && !matches!(
                frm_data.fraud_check.frm_status,
                FraudCheckStatus::TransactionFailure // Incase of TransactionFailure frm status(No frm decision is taken by frm processor), if capture method is automatic we should not change it to manual.
            ) {
                *should_continue_capture = false;
                Some(frm_data.to_owned())
            } else {
                Some(frm_data.to_owned())
            }
        } else {
            None
        };
    }
    Ok(frm_data)
}

#[allow(clippy::too_many_arguments)]
pub async fn post_payment_frm_core<F, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: &domain::MerchantContext,
    payment_data: &mut D,
    frm_info: &mut FrmInfo<F, D>,
    frm_configs: FrmConfigsObject,
    customer: &Option<domain::Customer>,
    should_continue_capture: &mut bool,
) -> RouterResult<Option<FrmData>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    if let Some(frm_data) = &mut frm_info.frm_data {
        // Allow the Post flow only if the payment is authorized,
        // this logic has to be removed if we are going to call /sale or /transaction after failed transaction
        let fraud_check_operation = &mut frm_info.fraud_check_operation;
        if payment_data.get_payment_attempt().status == AttemptStatus::Authorized {
            let frm_router_data_opt = fraud_check_operation
                .to_domain()?
                .post_payment_frm(
                    state,
                    req_state.clone(),
                    payment_data,
                    frm_data,
                    merchant_context,
                    customer,
                )
                .await?;
            if let Some(frm_router_data) = frm_router_data_opt {
                let mut frm_data = fraud_check_operation
                    .to_update_tracker()?
                    .update_tracker(
                        state,
                        merchant_context.get_merchant_key_store(),
                        frm_data.to_owned(),
                        payment_data,
                        None,
                        frm_router_data.to_owned(),
                    )
                    .await?;
                let frm_fraud_check = frm_data.fraud_check.clone();
                let mut frm_suggestion = None;
                payment_data.set_frm_message(frm_fraud_check.clone());
                if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) {
                    frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction);
                } else if matches!(frm_fraud_check.frm_status, FraudCheckStatus::ManualReview) {
                    frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview);
                }
                fraud_check_operation
                    .to_domain()?
                    .execute_post_tasks(
                        state,
                        req_state,
                        &mut frm_data,
                        merchant_context,
                        frm_configs,
                        &mut frm_suggestion,
                        payment_data,
                        customer,
                        should_continue_capture,
                    )
                    .await?;
                logger::debug!("frm_post_tasks_data: {:?}", frm_data);
                let updated_frm_data = fraud_check_operation
                    .to_update_tracker()?
                    .update_tracker(
                        state,
                        merchant_context.get_merchant_key_store(),
                        frm_data.to_owned(),
                        payment_data,
                        frm_suggestion,
                        frm_router_data.to_owned(),
                    )
                    .await?;
                return Ok(Some(updated_frm_data));
            }
        }

        Ok(Some(frm_data.to_owned()))
    } else {
        Ok(None)
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn call_frm_before_connector_call<F, Req, D>(
    operation: &BoxedOperation<'_, F, Req, D>,
    merchant_context: &domain::MerchantContext,
    payment_data: &mut D,
    state: &SessionState,
    frm_info: &mut Option<FrmInfo<F, D>>,
    customer: &Option<domain::Customer>,
    should_continue_transaction: &mut bool,
    should_continue_capture: &mut bool,
) -> RouterResult<Option<FrmConfigsObject>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    let (is_frm_enabled, frm_routing_algorithm, frm_connector_label, frm_configs) =
        should_call_frm(merchant_context, payment_data, state).await?;
    if let Some((frm_routing_algorithm_val, profile_id)) =
        frm_routing_algorithm.zip(frm_connector_label)
    {
        if let Some(frm_configs) = frm_configs.clone() {
            let mut updated_frm_info = Box::pin(make_frm_data_and_fraud_check_operation(
                &*state.store,
                state,
                merchant_context,
                payment_data.to_owned(),
                frm_routing_algorithm_val,
                profile_id,
                frm_configs.clone(),
                customer,
            ))
            .await?;

            if is_frm_enabled {
                pre_payment_frm_core(
                    state,
                    merchant_context,
                    payment_data,
                    &mut updated_frm_info,
                    frm_configs,
                    customer,
                    should_continue_transaction,
                    should_continue_capture,
                    operation,
                )
                .await?;
            }
            *frm_info = Some(updated_frm_info);
        }
    }
    let fraud_capture_method = frm_info.as_ref().and_then(|frm_info| {
        frm_info
            .frm_data
            .as_ref()
            .map(|frm_data| frm_data.fraud_check.payment_capture_method)
    });
    if matches!(fraud_capture_method, Some(Some(CaptureMethod::Manual)))
        && matches!(
            payment_data.get_payment_attempt().status,
            AttemptStatus::Unresolved
        )
    {
        if let Some(info) = frm_info {
            info.suggested_action = Some(FrmSuggestion::FrmAuthorizeTransaction)
        };
        *should_continue_transaction = false;
        logger::debug!(
            "skipping connector call since payment_capture_method is already {:?}",
            fraud_capture_method
        );
    };
    logger::debug!("frm_configs: {:?} {:?}", frm_configs, is_frm_enabled);
    Ok(frm_configs)
}

pub fn is_operation_allowed<Op: Debug>(operation: &Op) -> bool {
    ![
        "PaymentSession",
        "PaymentApprove",
        "PaymentReject",
        "PaymentCapture",
        "PaymentsCancel",
    ]
    .contains(&format!("{operation:?}").as_str())
}

#[cfg(feature = "v1")]
impl From<PaymentToFrmData> for PaymentDetails {
    fn from(payment_data: PaymentToFrmData) -> Self {
        Self {
            amount: common_utils::types::MinorUnit::from(payment_data.amount).get_amount_as_i64(),
            currency: payment_data.payment_attempt.currency,
            payment_method: payment_data.payment_attempt.payment_method,
            payment_method_type: payment_data.payment_attempt.payment_method_type,
            refund_transaction_id: None,
        }
    }
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn frm_fulfillment_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: frm_core_types::FrmFulfillmentRequest,
) -> RouterResponse<frm_types::FraudCheckResponseData> {
    let db = &*state.clone().store;
    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &req.payment_id.clone(),
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
    match payment_intent.status {
        IntentStatus::Succeeded => {
            let invalid_request_error = errors::ApiErrorResponse::InvalidRequestData {
                message: "no fraud check entry found for this payment_id".to_string(),
            };
            let existing_fraud_check = db
                .find_fraud_check_by_payment_id_if_present(
                    req.payment_id.clone(),
                    merchant_context.get_merchant_account().get_id().clone(),
                )
                .await
                .change_context(invalid_request_error.to_owned())?;
            match existing_fraud_check {
                Some(fraud_check) => {
                    if (matches!(fraud_check.frm_transaction_type, FraudCheckType::PreFrm)
                        && fraud_check.last_step == FraudCheckLastStep::TransactionOrRecordRefund)
                        || (matches!(fraud_check.frm_transaction_type, FraudCheckType::PostFrm)
                            && fraud_check.last_step == FraudCheckLastStep::CheckoutOrSale)
                    {
                        Box::pin(make_fulfillment_api_call(
                            db,
                            fraud_check,
                            payment_intent,
                            state,
                            merchant_context,
                            req,
                        ))
                        .await
                    } else {
                        Err(errors::ApiErrorResponse::PreconditionFailed {message:"Frm pre/post flow hasn't terminated yet, so fulfillment cannot be called".to_string(),}.into())
                    }
                }
                None => Err(invalid_request_error.into()),
            }
        }
        _ => Err(errors::ApiErrorResponse::PreconditionFailed {
            message: "Fulfillment can be performed only for succeeded payment".to_string(),
        }
        .into()),
    }
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn make_fulfillment_api_call(
    db: &dyn StorageInterface,
    fraud_check: FraudCheck,
    payment_intent: PaymentIntent,
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: frm_core_types::FrmFulfillmentRequest,
) -> RouterResponse<frm_types::FraudCheckResponseData> {
    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &payment_intent.active_attempt.get_id(),
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
    let connector_data = FraudCheckConnectorData::get_connector_by_name(&fraud_check.frm_name)?;
    let connector_integration: services::BoxedFrmConnectorIntegrationInterface<
        Fulfillment,
        frm_types::FraudCheckFulfillmentData,
        frm_types::FraudCheckResponseData,
    > = connector_data.connector.get_connector_integration();
    let router_data = frm_flows::fulfillment_flow::construct_fulfillment_router_data(
        &state,
        &payment_intent,
        &payment_attempt,
        &merchant_context,
        fraud_check.frm_name.clone(),
        req,
    )
    .await?;
    let response = services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payment_failed_response()?;
    let fraud_check_copy = fraud_check.clone();
    let fraud_check_update = FraudCheckUpdate::ResponseUpdate {
        frm_status: fraud_check.frm_status,
        frm_transaction_id: fraud_check.frm_transaction_id,
        frm_reason: fraud_check.frm_reason,
        frm_score: fraud_check.frm_score,
        metadata: fraud_check.metadata,
        modified_at: common_utils::date_time::now(),
        last_step: FraudCheckLastStep::Fulfillment,
        payment_capture_method: fraud_check.payment_capture_method,
    };
    let _updated = db
        .update_fraud_check_response_with_attempt_id(fraud_check_copy, fraud_check_update)
        .await
        .map_err(|error| error.change_context(errors::ApiErrorResponse::PaymentNotFound))?;
    let fulfillment_response =
        response
            .response
            .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: connector_data.connector_name.clone().to_string(),
                status_code: err.status_code,
                reason: err.reason,
            })?;
    Ok(services::ApplicationResponse::Json(fulfillment_response))
}


File: crates/common_utils/src/events.rs

use serde::Serialize;

use crate::{id_type, types::TimeRange};

pub trait ApiEventMetric {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        None
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(tag = "flow_type", rename_all = "snake_case")]
pub enum ApiEventsType {
    Payout {
        payout_id: id_type::PayoutId,
    },
    #[cfg(feature = "v1")]
    Payment {
        payment_id: id_type::PaymentId,
    },
    #[cfg(feature = "v2")]
    Payment {
        payment_id: id_type::GlobalPaymentId,
    },
    #[cfg(feature = "v1")]
    Refund {
        payment_id: Option<id_type::PaymentId>,
        refund_id: String,
    },
    #[cfg(feature = "v2")]
    Refund {
        payment_id: Option<id_type::GlobalPaymentId>,
        refund_id: id_type::GlobalRefundId,
    },
    #[cfg(feature = "v1")]
    PaymentMethod {
        payment_method_id: String,
        payment_method: Option<common_enums::PaymentMethod>,
        payment_method_type: Option<common_enums::PaymentMethodType>,
    },
    #[cfg(feature = "v2")]
    PaymentMethod {
        payment_method_id: id_type::GlobalPaymentMethodId,
        payment_method_type: Option<common_enums::PaymentMethod>,
        payment_method_subtype: Option<common_enums::PaymentMethodType>,
    },
    #[cfg(feature = "v2")]
    PaymentMethodCreate,
    #[cfg(feature = "v2")]
    Customer {
        customer_id: Option<id_type::GlobalCustomerId>,
    },
    #[cfg(feature = "v1")]
    Customer {
        customer_id: id_type::CustomerId,
    },
    BusinessProfile {
        profile_id: id_type::ProfileId,
    },
    ApiKey {
        key_id: id_type::ApiKeyId,
    },
    User {
        user_id: String,
    },
    PaymentMethodList {
        payment_id: Option<String>,
    },
    #[cfg(feature = "v2")]
    PaymentMethodListForPaymentMethods {
        payment_method_id: id_type::GlobalPaymentMethodId,
    },
    #[cfg(feature = "v1")]
    Webhooks {
        connector: String,
        payment_id: Option<id_type::PaymentId>,
    },
    #[cfg(feature = "v1")]
    NetworkTokenWebhook {
        payment_method_id: Option<String>,
    },
    #[cfg(feature = "v2")]
    Webhooks {
        connector: id_type::MerchantConnectorAccountId,
        payment_id: Option<id_type::GlobalPaymentId>,
    },
    Routing,
    ResourceListAPI,
    #[cfg(feature = "v1")]
    PaymentRedirectionResponse {
        connector: Option<String>,
        payment_id: Option<id_type::PaymentId>,
    },
    #[cfg(feature = "v2")]
    PaymentRedirectionResponse {
        payment_id: id_type::GlobalPaymentId,
    },
    Gsm,
    // TODO: This has to be removed once the corresponding apiEventTypes are created
    Miscellaneous,
    Keymanager,
    RustLocker,
    ApplePayCertificatesMigration,
    FraudCheck,
    Recon,
    ExternalServiceAuth,
    Dispute {
        dispute_id: String,
    },
    Events {
        merchant_id: id_type::MerchantId,
    },
    PaymentMethodCollectLink {
        link_id: String,
    },
    Poll {
        poll_id: String,
    },
    Analytics,
    #[cfg(feature = "v2")]
    ClientSecret {
        key_id: id_type::ClientSecretId,
    },
    #[cfg(feature = "v2")]
    PaymentMethodSession {
        payment_method_session_id: id_type::GlobalPaymentMethodSessionId,
    },
    #[cfg(feature = "v2")]
    Token {
        token_id: Option<id_type::GlobalTokenId>,
    },
    ProcessTracker,
    Authentication {
        authentication_id: id_type::AuthenticationId,
    },
    ProfileAcquirer {
        profile_acquirer_id: id_type::ProfileAcquirerId,
    },
    ThreeDsDecisionRule,
    Chat,
}

impl ApiEventMetric for serde_json::Value {}
impl ApiEventMetric for () {}

#[cfg(feature = "v1")]
impl ApiEventMetric for id_type::PaymentId {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payment {
            payment_id: self.clone(),
        })
    }
}

#[cfg(feature = "v2")]
impl ApiEventMetric for id_type::GlobalPaymentId {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Payment {
            payment_id: self.clone(),
        })
    }
}

impl<Q: ApiEventMetric, E> ApiEventMetric for Result<Q, E> {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        match self {
            Ok(q) => q.get_api_event_type(),
            Err(_) => None,
        }
    }
}

// TODO: Ideally all these types should be replaced by newtype responses
impl<T> ApiEventMetric for Vec<T> {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Miscellaneous)
    }
}

#[macro_export]
macro_rules! impl_api_event_type {
    ($event: ident, ($($type:ty),+))=> {
        $(
            impl ApiEventMetric for $type {
                fn get_api_event_type(&self) -> Option<ApiEventsType> {
                    Some(ApiEventsType::$event)
                }
            }
        )+
     };
}

impl_api_event_type!(
    Miscellaneous,
    (
        String,
        id_type::MerchantId,
        (Option<i64>, Option<i64>, String),
        (Option<i64>, Option<i64>, id_type::MerchantId),
        bool
    )
);

impl<T: ApiEventMetric> ApiEventMetric for &T {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        T::get_api_event_type(self)
    }
}

impl ApiEventMetric for TimeRange {}


pub struct GenerateFingerprintResponsePayload {
    pub card_fingerprint: String,
}


File: crates/common_utils/src/validation.rs
 Public functions: 3

//! Custom validations for some shared types.

use std::{collections::HashSet, sync::LazyLock};

use error_stack::report;
use globset::Glob;
use regex::Regex;
#[cfg(feature = "logs")]
use router_env::logger;

use crate::errors::{CustomResult, ValidationError};

/// Validates a given phone number using the [phonenumber] crate
///
/// It returns a [ValidationError::InvalidValue] in case it could not parse the phone number
pub fn validate_phone_number(phone_number: &str) -> Result<(), ValidationError> {
    let _ = phonenumber::parse(None, phone_number).map_err(|e| ValidationError::InvalidValue {
        message: format!("Could not parse phone number: {phone_number}, because: {e:?}"),
    })?;

    Ok(())
}

/// Performs a simple validation against a provided email address.
pub fn validate_email(email: &str) -> CustomResult<(), ValidationError> {
    #[deny(clippy::invalid_regex)]
    static EMAIL_REGEX: LazyLock<Option<Regex>> = LazyLock::new(|| {
        #[allow(unknown_lints)]
        #[allow(clippy::manual_ok_err)]
        match Regex::new(
            r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$",
        ) {
            Ok(regex) => Some(regex),
            Err(_error) => {
                #[cfg(feature = "logs")]
                logger::error!(?_error);
                None
            }
        }
    });
    let email_regex = match EMAIL_REGEX.as_ref() {
        Some(regex) => Ok(regex),
        None => Err(report!(ValidationError::InvalidValue {
            message: "Invalid regex expression".into()
        })),
    }?;

    const EMAIL_MAX_LENGTH: usize = 319;
    if email.is_empty() || email.chars().count() > EMAIL_MAX_LENGTH {
        return Err(report!(ValidationError::InvalidValue {
            message: "Email address is either empty or exceeds maximum allowed length".into()
        }));
    }

    if !email_regex.is_match(email) {
        return Err(report!(ValidationError::InvalidValue {
            message: "Invalid email address format".into()
        }));
    }

    Ok(())
}

/// Checks whether a given domain matches against a list of valid domain glob patterns
pub fn validate_domain_against_allowed_domains(
    domain: &str,
    allowed_domains: HashSet<String>,
) -> bool {
    allowed_domains.iter().any(|allowed_domain| {
        Glob::new(allowed_domain)
            .map(|glob| glob.compile_matcher().is_match(domain))
            .map_err(|err| {
                let err_msg = format!(
                    "Invalid glob pattern for configured allowed_domain [{allowed_domain:?}]! - {err:?}",

                );
                #[cfg(feature = "logs")]
                logger::error!(err_msg);
                err_msg
            })
            .unwrap_or(false)
    })
}

#[cfg(test)]
mod tests {
    use fake::{faker::internet::en::SafeEmail, Fake};
    use proptest::{
        prop_assert,
        strategy::{Just, NewTree, Strategy},
        test_runner::TestRunner,
    };
    use test_case::test_case;

    use super::*;

    #[derive(Debug)]
    struct ValidEmail;

    impl Strategy for ValidEmail {
        type Tree = Just<String>;
        type Value = String;

        fn new_tree(&self, _runner: &mut TestRunner) -> NewTree<Self> {
            Ok(Just(SafeEmail().fake()))
        }
    }

    #[test]
    fn test_validate_email() {
        let result = validate_email("abc@example.com");
        assert!(result.is_ok());

        let result = validate_email("abc+123@example.com");
        assert!(result.is_ok());

        let result = validate_email("");
        assert!(result.is_err());
    }

    #[test_case("+40745323456" ; "Romanian valid phone number")]
    #[test_case("+34912345678" ; "Spanish valid phone number")]
    #[test_case("+41 79 123 45 67" ; "Swiss valid phone number")]
    #[test_case("+66 81 234 5678" ; "Thailand valid phone number")]
    fn test_validate_phone_number(phone_number: &str) {
        assert!(validate_phone_number(phone_number).is_ok());
    }

    #[test_case("9123456789" ; "Romanian invalid phone number")]
    fn test_invalid_phone_number(phone_number: &str) {
        let res = validate_phone_number(phone_number);
        assert!(res.is_err());
    }

    proptest::proptest! {
        /// Example of unit test
        #[test]
        fn proptest_valid_fake_email(email in ValidEmail) {
            prop_assert!(validate_email(&email).is_ok());
        }

        /// Example of unit test
        #[test]
        fn proptest_invalid_data_email(email in "\\PC*") {
            prop_assert!(validate_email(&email).is_err());
        }

        #[test]
        fn proptest_invalid_email(email in "[.+]@(.+)") {
            prop_assert!(validate_email(&email).is_err());
        }
    }
}


pub fn get_kind(&self) -> RoutingAlgorithmKind {
        match self {
            Self::Single(_) => RoutingAlgorithmKind::Single,
            Self::Priority(_) => RoutingAlgorithmKind::Priority,
            Self::VolumeSplit(_) => RoutingAlgorithmKind::VolumeSplit,
            Self::Advanced(_) => RoutingAlgorithmKind::Advanced,
            Self::ThreeDsDecisionRule(_) => RoutingAlgorithmKind::ThreeDsDecisionRule,
        }
    }


impl api::RefundSync for Gpayments {}


pub struct RefundSyncResponse {
    result: NoonRefundSyncResponseResult,
    result_code: u32,
    class_description: String,
    message: String,
}


File: crates/hyperswitch_interfaces/src/configs.rs

pub use hyperswitch_domain_models::connector_endpoints::Connectors;


pub async fn retrieve_dispute_evidence(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<String>,
) -> HttpResponse {
    let flow = Flow::RetrieveDisputeEvidence;
    let dispute_id = dispute_types::DisputeId {
        dispute_id: path.into_inner(),
    };
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        dispute_id,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            disputes::retrieve_dispute_evidence(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileDisputeRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub async fn connector_list() {}


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


impl ConnectorAuthentication {
    /// # Panics
    ///
    /// Will panic if `CONNECTOR_AUTH_FILE_PATH` env is not set
    #[allow(clippy::expect_used)]
    pub fn new() -> Self {
        // Do `export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml"`
        // before running tests in shell
        let path = env::var("CONNECTOR_AUTH_FILE_PATH")
            .expect("Connector authentication file path not set");
        toml::from_str(
            &std::fs::read_to_string(path).expect("connector authentication config file not found"),
        )
        .expect("Failed to read connector authentication config file")
    }
}


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


impl api::PaymentSession for Taxjar {}


impl UploadFile for Worldpayvantiv {}


OpenAPI Block Path: components.schemas.ApplePayRegularBillingDetails
{
  "type": "object",
  "required": [
    "label"
  ],
  "properties": {
    "label": {
      "type": "string",
      "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details"
    },
    "recurring_payment_start_date": {
      "type": "string",
      "format": "date-time",
      "description": "The date of the first payment",
      "example": "2023-09-10T23:59:59Z",
      "nullable": true
    },
    "recurring_payment_end_date": {
      "type": "string",
      "format": "date-time",
      "description": "The date of the final payment",
      "example": "2023-09-10T23:59:59Z",
      "nullable": true
    },
    "recurring_payment_interval_unit": {
      "allOf": [
        {
          "$ref": "#/components/schemas/RecurringPaymentIntervalUnit"
        }
      ],
      "nullable": true
    },
    "recurring_payment_interval_count": {
      "type": "integer",
      "format": "int32",
      "description": "The number of interval units that make up the total payment interval",
      "nullable": true
    }
  }
}


pub async fn routing_retrieve_default_config(
    state: web::Data<AppState>,
    req: HttpRequest,
    transaction_type: &enums::TransactionType,
) -> impl Responder {
    Box::pin(oss_api::server_wrap(
        Flow::RoutingRetrieveDefaultConfig,
        state,
        &req,
        (),
        |state, auth: auth::AuthenticationData, _, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            routing::retrieve_default_routing_config(
                state,
                auth.profile_id,
                merchant_context,
                transaction_type,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfileRoutingRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::RefundExecute for Moneris {}


/// Create a [`Histogram`][Histogram] f64 metric with the specified name and an optional description,
/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter].
///
/// [Histogram]: opentelemetry::metrics::Histogram
/// [Meter]: opentelemetry::metrics::Meter
#[macro_export]
macro_rules! histogram_metric_f64 {
    ($name:ident, $meter:ident) => {
        pub(crate) static $name: ::std::sync::LazyLock<
            $crate::opentelemetry::metrics::Histogram<f64>,
        > = ::std::sync::LazyLock::new(|| {
            $meter
                .f64_histogram(stringify!($name))
                .with_boundaries($crate::metrics::f64_histogram_buckets())
                .build()
        });
    };
    ($name:ident, $meter:ident, $description:literal) => {
        #[doc = $description]
        pub(crate) static $name: ::std::sync::LazyLock<
            $crate::opentelemetry::metrics::Histogram<f64>,
        > = ::std::sync::LazyLock::new(|| {
            $meter
                .f64_histogram(stringify!($name))
                .with_description($description)
                .with_boundaries($crate::metrics::f64_histogram_buckets())
                .build()
        });
    };
}


impl StructType {
    /// Generates the fields for temporary structs which consists of the fields that should be
    /// encrypted/decrypted
    fn generate_struct_fields(self, fields: &[(Field, Ident)]) -> Vec<proc_macro2::TokenStream> {
        fields
            .iter()
            .map(|(field, inner_ty)| {
                let provided_ty = get_encryption_ty_meta(field);
                let is_option = get_field_type(field.ty.clone())
                    .map(|f| f.eq("Option"))
                    .unwrap_or_default();
                let ident = &field.ident;
                let inner_ty = if let Some(ref ty) = provided_ty {
                    &ty.value
                } else {
                    inner_ty
                };
                match (self, is_option) {
                    (Self::Encrypted, true) => quote! { pub #ident: Option<Encryption> },
                    (Self::Encrypted, false) => quote! { pub #ident: Encryption },
                    (Self::Decrypted, true) => {
                        quote! { pub #ident: Option<Encryptable<Secret<#inner_ty>>> }
                    }
                    (Self::Decrypted, false) => {
                        quote! { pub #ident: Encryptable<Secret<#inner_ty>> }
                    }
                    (Self::DecryptedUpdate, _) => {
                        quote! { pub #ident: Option<Encryptable<Secret<#inner_ty>>> }
                    }
                    (Self::FromRequest, true) => {
                        quote! { pub #ident: Option<Secret<#inner_ty>> }
                    }
                    (Self::FromRequest, false) => quote! { pub #ident: Secret<#inner_ty> },
                    (Self::Updated, _) => quote! { pub #ident: Option<Secret<#inner_ty>> },
                }
            })
            .collect()
    }

    /// Generates the ToEncryptable trait implementation
    fn generate_impls(
        self,
        gen1: proc_macro2::TokenStream,
        gen2: proc_macro2::TokenStream,
        gen3: proc_macro2::TokenStream,
        impl_st: proc_macro2::TokenStream,
        inner: &[Field],
    ) -> proc_macro2::TokenStream {
        let map_length = inner.len();

        let to_encryptable_impl = inner.iter().flat_map(|field| {
            get_field_type(field.ty.clone()).map(|field_ty| {
                let is_option = field_ty.eq("Option");
                let field_ident = &field.ident;
                let field_ident_string = field_ident.as_ref().map(|s| s.to_string());

                if is_option || self == Self::Updated {
                    quote! { self.#field_ident.map(|s| map.insert(#field_ident_string.to_string(), s)) }
                } else {
                    quote! { map.insert(#field_ident_string.to_string(), self.#field_ident) }
                }
            })
        });

        let from_encryptable_impl = inner.iter().flat_map(|field| {
            get_field_type(field.ty.clone()).map(|field_ty| {
                let is_option = field_ty.eq("Option");
                let field_ident = &field.ident;
                let field_ident_string = field_ident.as_ref().map(|s| s.to_string());

                if is_option || self == Self::Updated {
                    quote! { #field_ident: map.remove(#field_ident_string) }
                } else {
                    quote! {
                        #field_ident: map.remove(#field_ident_string).ok_or(
                            error_stack::report!(common_utils::errors::ParsingError::EncodeError(
                                "Unable to convert from HashMap",
                            ))
                        )?
                    }
                }
            })
        });

        quote! {
            impl ToEncryptable<#gen1, #gen2, #gen3> for #impl_st {
                fn to_encryptable(self) -> FxHashMap<String, #gen3> {
                    let mut map = FxHashMap::with_capacity_and_hasher(#map_length, Default::default());
                    #(#to_encryptable_impl;)*
                    map
                }

                fn from_encryptable(
                    mut map: FxHashMap<String, Encryptable<#gen2>>,
                ) -> CustomResult<#gen1, common_utils::errors::ParsingError> {
                    Ok(#gen1 {
                        #(#from_encryptable_impl,)*
                    })
                }
            }
        }
    }
}


pub async fn update_payment_processor_token_schedule_time(
        state: &SessionState,
        connector_customer_id: &str,
        payment_processor_token: &str,
        schedule_time: Option<PrimitiveDateTime>,
    ) -> CustomResult<bool, errors::StorageError> {
        let updated_token =
            Self::get_connector_customer_payment_processor_tokens(state, connector_customer_id)
                .await?
                .values()
                .find(|status| {
                    status
                        .payment_processor_token_details
                        .payment_processor_token
                        == payment_processor_token
                })
                .map(|status| PaymentProcessorTokenStatus {
                    payment_processor_token_details: status.payment_processor_token_details.clone(),
                    inserted_by_attempt_id: status.inserted_by_attempt_id.clone(),
                    error_code: status.error_code.clone(),
                    daily_retry_history: status.daily_retry_history.clone(),
                    scheduled_at: schedule_time,
                    is_hard_decline: status.is_hard_decline,
                });

        match updated_token {
            Some(token) => {
                let mut tokens_map = HashMap::new();
                tokens_map.insert(
                    token
                        .payment_processor_token_details
                        .payment_processor_token
                        .clone(),
                    token.clone(),
                );
                Self::update_or_add_connector_customer_payment_processor_tokens(
                    state,
                    connector_customer_id,
                    tokens_map,
                )
                .await?;
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "Updated payment processor tokens with schedule time",
                );
                Ok(true)
            }
            None => {
                tracing::debug!(
                    connector_customer_id = connector_customer_id,
                    "payment processor tokens with not found",
                );
                Ok(false)
            }
        }
    }


Documentation: api-reference/v2/profile/merchant-connector--list.mdx
# Type: Doc File

---
openapi: get /v2/profiles/{id}/connector-accounts
---


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


impl api::PaymentsPreProcessing for Paysafe {}

impl api::PaymentsPreProcessing for for Paysafe


pub struct RefundsMetricsResponse<T> {
    pub query_data: Vec<T>,
    pub meta_data: [RefundsAnalyticsMetadata; 1],
}


impl api::MandateSetup for Payload {}


pub struct InespayPaymentsRequest {
    description: String,
    amount: StringMinorUnit,
    reference: String,
    debtor_account: Option<Secret<String>>,
    success_link_redirect: Option<String>,
    notif_url: Option<String>,
}


OpenAPI Block Path: components.schemas.MerchantAccountCreate
{
  "type": "object",
  "required": [
    "merchant_id"
  ],
  "properties": {
    "merchant_id": {
      "type": "string",
      "description": "The identifier for the Merchant Account",
      "example": "y3oqhf46pyzuxjbcn2giaqnb44",
      "maxLength": 64,
      "minLength": 1
    },
    "merchant_name": {
      "type": "string",
      "description": "Name of the Merchant Account",
      "example": "NewAge Retailer",
      "nullable": true
    },
    "merchant_details": {
      "allOf": [
        {
          "$ref": "#/components/schemas/MerchantDetails"
        }
      ],
      "nullable": true
    },
    "return_url": {
      "type": "string",
      "description": "The URL to redirect after the completion of the operation",
      "example": "https://www.example.com/success",
      "nullable": true,
      "maxLength": 255
    },
    "webhook_details": {
      "allOf": [
        {
          "$ref": "#/components/schemas/WebhookDetails"
        }
      ],
      "nullable": true
    },
    "payout_routing_algorithm": {
      "allOf": [
        {
          "$ref": "#/components/schemas/StaticRoutingAlgorithm"
        }
      ],
      "nullable": true
    },
    "sub_merchants_enabled": {
      "type": "boolean",
      "description": "A boolean value to indicate if the merchant is a sub-merchant under a master or a parent merchant. By default, its value is false.",
      "default": false,
      "example": false,
      "nullable": true
    },
    "parent_merchant_id": {
      "type": "string",
      "description": "Refers to the Parent Merchant ID if the merchant being created is a sub-merchant",
      "example": "xkkdf909012sdjki2dkh5sdf",
      "nullable": true,
      "maxLength": 255
    },
    "enable_payment_response_hash": {
      "type": "boolean",
      "description": "A boolean value to indicate if payment response hash needs to be enabled",
      "default": false,
      "example": true,
      "nullable": true
    },
    "payment_response_hash_key": {
      "type": "string",
      "description": "Refers to the hash key used for calculating the signature for webhooks and redirect response. If the value is not provided, a value is automatically generated.",
      "nullable": true
    },
    "redirect_to_merchant_with_http_post": {
      "type": "boolean",
      "description": "A boolean value to indicate if redirect to merchant with http post needs to be enabled.",
      "default": false,
      "example": true,
      "nullable": true
    },
    "metadata": {
      "type": "object",
      "description": "Metadata is useful for storing additional, unstructured information on an object",
      "nullable": true
    },
    "publishable_key": {
      "type": "string",
      "description": "API key that will be used for client side API access. A publishable key has to be always paired with a `client_secret`.\nA `client_secret` can be obtained by creating a payment with `confirm` set to false",
      "example": "AH3423bkjbkjdsfbkj",
      "nullable": true
    },
    "locker_id": {
      "type": "string",
      "description": "An identifier for the vault used to store payment method information.",
      "example": "locker_abc123",
      "nullable": true
    },
    "primary_business_details": {
      "allOf": [
        {
          "$ref": "#/components/schemas/PrimaryBusinessDetails"
        }
      ],
      "nullable": true
    },
    "frm_routing_algorithm": {
      "type": "object",
      "description": "The frm routing algorithm to be used for routing payments to desired FRM's",
      "nullable": true
    },
    "organization_id": {
      "type": "string",
      "description": "The id of the organization to which the merchant belongs to, if not passed an organization is created",
      "example": "org_q98uSGAYbjEwqs0mJwnz",
      "nullable": true,
      "maxLength": 64,
      "minLength": 1
    },
    "pm_collect_link_config": {
      "allOf": [
        {
          "$ref": "#/components/schemas/BusinessCollectLinkConfig"
        }
      ],
      "nullable": true
    },
    "product_type": {
      "allOf": [
        {
          "$ref": "#/components/schemas/MerchantProductType"
        }
      ],
      "nullable": true
    },
    "merchant_account_type": {
      "allOf": [
        {
          "$ref": "#/components/schemas/MerchantAccountRequestType"
        }
      ],
      "nullable": true
    }
  },
  "additionalProperties": false
}


pub struct FrmFulfillmentSignifydApiResponse {
    pub order_id: String,
    pub shipment_ids: Vec<String>,
}