    }
}

impl UserFromStorage {
    pub fn get_user_id(&self) -> &str {
        self.0.user_id.as_str()
    }

    pub fn compare_password(&self, candidate: &Secret<String>) -> UserResult<()> {
        if let Some(password) = self.0.password.as_ref() {
            match password::is_correct_password(candidate, password) {
                Ok(true) => Ok(()),
                Ok(false) => Err(UserErrors::InvalidCredentials.into()),
                Err(e) => Err(e),
            }
        } else {
            Err(UserErrors::InvalidCredentials.into())
        }
    }

    pub fn get_name(&self) -> Secret<String> {
        self.0.name.clone()
    }

    pub fn get_email(&self) -> pii::Email {
        self.0.email.clone()
    }

    #[cfg(feature = "email")]
    pub fn get_verification_days_left(&self, state: &SessionState) -> UserResult<Option<i64>> {
        if self.0.is_verified {
            return Ok(None);
        }

        let allowed_unverified_duration =
            time::Duration::days(state.conf.email.allowed_unverified_days);

        let user_created = self.0.created_at.date();
        let last_date_for_verification = user_created
            .checked_add(allowed_unverified_duration)
            .ok_or(UserErrors::InternalServerError)?;

        let today = common_utils::date_time::now().date();
        if today >= last_date_for_verification {
            return Err(UserErrors::UnverifiedUser.into());
        }

        let days_left_for_verification = last_date_for_verification - today;
        Ok(Some(days_left_for_verification.whole_days()))
    }

    pub fn is_verified(&self) -> bool {
        self.0.is_verified
    }

    pub fn is_password_rotate_required(&self, state: &SessionState) -> UserResult<bool> {
        let last_password_modified_at =
            if let Some(last_password_modified_at) = self.0.last_password_modified_at {
                last_password_modified_at.date()
            } else {
                return Ok(true);
            };

        let password_change_duration =
            time::Duration::days(state.conf.user.password_validity_in_days.into());
        let last_date_for_password_rotate = last_password_modified_at
            .checked_add(password_change_duration)
            .ok_or(UserErrors::InternalServerError)?;

        let today = common_utils::date_time::now().date();
        let days_left_for_password_rotate = last_date_for_password_rotate - today;

        Ok(days_left_for_password_rotate.whole_days() < 0)
    }

    pub async fn get_or_create_key_store(&self, state: &SessionState) -> UserResult<UserKeyStore> {
        let master_key = state.store.get_master_key();
        let key_manager_state = &state.into();
        let key_store_result = state
            .global_store
            .get_user_key_store_by_user_id(
                key_manager_state,
                self.get_user_id(),
                &master_key.to_vec().into(),
            )
            .await;

        if let Ok(key_store) = key_store_result {
            Ok(key_store)
        } else if key_store_result
            .as_ref()
            .map_err(|e| e.current_context().is_db_not_found())
            .err()
            .unwrap_or(false)
        {
            let key = services::generate_aes256_key()
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Unable to generate aes 256 key")?;

            #[cfg(feature = "keymanager_create")]
            {
                common_utils::keymanager::transfer_key_to_key_manager(
                    key_manager_state,
                    EncryptionTransferRequest {
                        identifier: Identifier::User(self.get_user_id().to_string()),
                        key: consts::BASE64_ENGINE.encode(key),
                    },
                )
                .await
                .change_context(UserErrors::InternalServerError)?;
            }

            let key_store = UserKeyStore {
                user_id: self.get_user_id().to_string(),
                key: domain_types::crypto_operation(
                    key_manager_state,
                    type_name!(UserKeyStore),
                    domain_types::CryptoOperation::Encrypt(key.to_vec().into()),
                    Identifier::User(self.get_user_id().to_string()),
                    master_key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .change_context(UserErrors::InternalServerError)?,
                created_at: common_utils::date_time::now(),
            };

            state
                .global_store
                .insert_user_key_store(key_manager_state, key_store, &master_key.to_vec().into())
                .await
                .change_context(UserErrors::InternalServerError)
        } else {
            Err(key_store_result
                .err()
                .map(|e| e.change_context(UserErrors::InternalServerError))
                .unwrap_or(UserErrors::InternalServerError.into()))
        }
    }

    pub fn get_totp_status(&self) -> TotpStatus {
        self.0.totp_status
    }

    pub fn get_recovery_codes(&self) -> Option<Vec<Secret<String>>> {
        self.0.totp_recovery_codes.clone()
    }

    pub async fn decrypt_and_get_totp_secret(
        &self,
        state: &SessionState,
    ) -> UserResult<Option<Secret<String>>> {
        if self.0.totp_secret.is_none() {
            return Ok(None);
        }
        let key_manager_state = &state.into();
        let user_key_store = state
            .global_store
            .get_user_key_store_by_user_id(
                key_manager_state,
                self.get_user_id(),
                &state.store.get_master_key().to_vec().into(),
            )
            .await
            .change_context(UserErrors::InternalServerError)?;

        Ok(domain_types::crypto_operation::<String, masking::WithType>(
            key_manager_state,
            type_name!(storage_user::User),
            domain_types::CryptoOperation::DecryptOptional(self.0.totp_secret.clone()),
            Identifier::User(user_key_store.user_id.clone()),
            user_key_store.key.peek(),
        )
        .await
        .and_then(|val| val.try_into_optionaloperation())
        .change_context(UserErrors::InternalServerError)?
        .map(Encryptable::into_inner))
    }
}

impl ForeignFrom<UserStatus> for user_role_api::UserStatus {
    fn foreign_from(value: UserStatus) -> Self {
        match value {
            UserStatus::Active => Self::Active,
            UserStatus::InvitationSent => Self::InvitationSent,
        }
    }
}

#[derive(Clone)]
pub struct RoleName(String);

impl RoleName {
    pub fn new(name: String) -> UserResult<Self> {
        let is_empty_or_whitespace = name.trim().is_empty();
        let is_too_long = name.graphemes(true).count() > consts::user_role::MAX_ROLE_NAME_LENGTH;

        if is_empty_or_whitespace || is_too_long || name.contains(' ') {
            Err(UserErrors::RoleNameParsingError.into())
        } else {
            Ok(Self(name.to_lowercase()))
        }
    }

    pub fn get_role_name(self) -> String {
        self.0
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct RecoveryCodes(pub Vec<Secret<String>>);

impl RecoveryCodes {
    pub fn generate_new() -> Self {
        let mut rand = rand::thread_rng();
        let recovery_codes = (0..consts::user::RECOVERY_CODES_COUNT)
            .map(|_| {
                let code_part_1 =
                    Alphanumeric.sample_string(&mut rand, consts::user::RECOVERY_CODE_LENGTH / 2);
                let code_part_2 =
                    Alphanumeric.sample_string(&mut rand, consts::user::RECOVERY_CODE_LENGTH / 2);

                Secret::new(format!("{code_part_1}-{code_part_2}"))
            })
            .collect::<Vec<_>>();

        Self(recovery_codes)
    }

    pub fn get_hashed(&self) -> UserResult<Vec<Secret<String>>> {
        self.0
            .iter()
            .cloned()
            .map(password::generate_password_hash)
            .collect::<Result<Vec<_>, _>>()
    }

    pub fn into_inner(self) -> Vec<Secret<String>> {
        self.0
    }
}

// This is for easier construction
#[derive(Clone)]
pub struct NoLevel;

#[derive(Clone)]
pub struct TenantLevel {
    pub tenant_id: id_type::TenantId,
}

#[derive(Clone)]
pub struct OrganizationLevel {
    pub tenant_id: id_type::TenantId,
    pub org_id: id_type::OrganizationId,
}

#[derive(Clone)]
pub struct MerchantLevel {
    pub tenant_id: id_type::TenantId,
    pub org_id: id_type::OrganizationId,
    pub merchant_id: id_type::MerchantId,
}

#[derive(Clone)]
pub struct ProfileLevel {
    pub tenant_id: id_type::TenantId,
    pub org_id: id_type::OrganizationId,
    pub merchant_id: id_type::MerchantId,
    pub profile_id: id_type::ProfileId,
}

#[derive(Clone)]
pub struct NewUserRole<E: Clone> {
    pub user_id: String,
    pub role_id: String,
    pub status: UserStatus,
    pub created_by: String,
    pub last_modified_by: String,
    pub created_at: PrimitiveDateTime,
    pub last_modified: PrimitiveDateTime,
    pub entity: E,
}

impl NewUserRole<NoLevel> {
    pub fn add_entity<T>(self, entity: T) -> NewUserRole<T>
    where
        T: Clone,
    {
        NewUserRole {
            entity,
            user_id: self.user_id,
            role_id: self.role_id,
            status: self.status,
            created_by: self.created_by,
            last_modified_by: self.last_modified_by,
            created_at: self.created_at,
            last_modified: self.last_modified,
        }
    }
}

pub struct EntityInfo {
    tenant_id: id_type::TenantId,
    org_id: Option<id_type::OrganizationId>,
    merchant_id: Option<id_type::MerchantId>,
    profile_id: Option<id_type::ProfileId>,
    entity_id: String,
    entity_type: EntityType,
}

impl From<TenantLevel> for EntityInfo {
    fn from(value: TenantLevel) -> Self {
        Self {
            entity_id: value.tenant_id.get_string_repr().to_owned(),
            entity_type: EntityType::Tenant,
            tenant_id: value.tenant_id,
            org_id: None,
            merchant_id: None,
            profile_id: None,
        }
    }
}

impl From<OrganizationLevel> for EntityInfo {
    fn from(value: OrganizationLevel) -> Self {
        Self {
            entity_id: value.org_id.get_string_repr().to_owned(),
            entity_type: EntityType::Organization,
            tenant_id: value.tenant_id,
            org_id: Some(value.org_id),
            merchant_id: None,
            profile_id: None,
        }
    }
}

impl From<MerchantLevel> for EntityInfo {
    fn from(value: MerchantLevel) -> Self {
        Self {
            entity_id: value.merchant_id.get_string_repr().to_owned(),
            entity_type: EntityType::Merchant,
            tenant_id: value.tenant_id,
            org_id: Some(value.org_id),
            merchant_id: Some(value.merchant_id),
            profile_id: None,
        }
    }
}

impl From<ProfileLevel> for EntityInfo {
    fn from(value: ProfileLevel) -> Self {
        Self {
            entity_id: value.profile_id.get_string_repr().to_owned(),
            entity_type: EntityType::Profile,
            tenant_id: value.tenant_id,
            org_id: Some(value.org_id),
            merchant_id: Some(value.merchant_id),
            profile_id: Some(value.profile_id),
        }
    }
}

impl<E> NewUserRole<E>
where
    E: Clone + Into<EntityInfo>,
{
    fn convert_to_new_v2_role(self, entity: EntityInfo) -> UserRoleNew {
        UserRoleNew {
            user_id: self.user_id,
            role_id: self.role_id,
            status: self.status,
            created_by: self.created_by,
            last_modified_by: self.last_modified_by,
            created_at: self.created_at,
            last_modified: self.last_modified,
            org_id: entity.org_id,
            merchant_id: entity.merchant_id,
            profile_id: entity.profile_id,
            entity_id: Some(entity.entity_id),
            entity_type: Some(entity.entity_type),
            version: UserRoleVersion::V2,
            tenant_id: entity.tenant_id,
        }
    }

    pub async fn insert_in_v2(self, state: &SessionState) -> UserResult<UserRole> {
        let entity = self.entity.clone();

        let new_v2_role = self.convert_to_new_v2_role(entity.into());

        state
            .global_store
            .insert_user_role(new_v2_role)
            .await
            .change_context(UserErrors::InternalServerError)
    }
}


impl AttemptAmountDetails {
    pub fn get_net_amount(&self) -> MinorUnit {
        self.net_amount
    }

    pub fn get_amount_to_capture(&self) -> Option<MinorUnit> {
        self.amount_to_capture
    }

    pub fn get_surcharge_amount(&self) -> Option<MinorUnit> {
        self.surcharge_amount
    }

    pub fn get_tax_on_surcharge(&self) -> Option<MinorUnit> {
        self.tax_on_surcharge
    }

    pub fn get_amount_capturable(&self) -> MinorUnit {
        self.amount_capturable
    }

    pub fn get_shipping_cost(&self) -> Option<MinorUnit> {
        self.shipping_cost
    }

    pub fn get_order_tax_amount(&self) -> Option<MinorUnit> {
        self.order_tax_amount
    }

    pub fn set_amount_to_capture(&mut self, amount_to_capture: MinorUnit) {
        self.amount_to_capture = Some(amount_to_capture);
    }

    /// Validate the amount to capture that is sent in the request
    pub fn validate_amount_to_capture(
        &self,
        request_amount_to_capture: MinorUnit,
    ) -> Result<(), ValidationError> {
        common_utils::fp_utils::when(request_amount_to_capture > self.get_net_amount(), || {
            Err(ValidationError::IncorrectValueProvided {
                field_name: "amount_to_capture",
            })
        })
    }
}


impl GlobalRefundId {
    /// Get string representation of the id
    pub fn get_string_repr(&self) -> &str {
        self.0.get_string_repr()
    }

    /// Generate a new GlobalRefundId from a cell id
    pub fn generate(cell_id: &crate::id_type::CellId) -> Self {
        let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Refund);
        Self(global_id)
    }
}


impl<K, V> DenseMap<K, V>
where
    K: EntityId,
{
    pub fn push(&mut self, elem: V) -> K {
        let curr_len = self.data.len();
        self.data.push(elem);
        K::with_id(curr_len)
    }

    #[inline]
    pub fn get(&self, idx: K) -> Option<&V> {
        self.data.get(idx.get_id())
    }

    #[inline]
    pub fn get_mut(&mut self, idx: K) -> Option<&mut V> {
        self.data.get_mut(idx.get_id())
    }

    #[inline]
    pub fn contains_key(&self, key: K) -> bool {
        key.get_id() < self.data.len()
    }

    #[inline]
    pub fn keys(&self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn into_keys(self) -> Keys<K> {
        Keys::new(0..self.data.len())
    }

    #[inline]
    pub fn values(&self) -> slice::Iter<'_, V> {
        self.data.iter()
    }

    #[inline]
    pub fn values_mut(&mut self) -> slice::IterMut<'_, V> {
        self.data.iter_mut()
    }

    #[inline]
    pub fn into_values(self) -> vec::IntoIter<V> {
        self.data.into_iter()
    }

    #[inline]
    pub fn iter(&self) -> Iter<'_, K, V> {
        Iter::new(self.data.iter())
    }

    #[inline]
    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
        IterMut::new(self.data.iter_mut())
    }
}


impl ConnectorValidation for Jpmorgan {
    fn validate_psync_reference_id(
        &self,
        data: &PaymentsSyncData,
        _is_three_ds: bool,
        _status: enums::AttemptStatus,
        _connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        if data.encoded_data.is_some()
            || data
                .connector_transaction_id
                .get_connector_transaction_id()
                .is_ok()
        {
            return Ok(());
        }
        Err(errors::ConnectorError::MissingConnectorTransactionID.into())
    }
}

impl ConnectorValidation for for Jpmorgan


File: crates/analytics/src/refunds/core.rs
 Public functions: 2

#![allow(dead_code)]
use std::collections::{HashMap, HashSet};

use api_models::analytics::{
    refunds::{
        RefundDimensions, RefundDistributions, RefundMetrics, RefundMetricsBucketIdentifier,
        RefundMetricsBucketResponse,
    },
    GetRefundFilterRequest, GetRefundMetricRequest, RefundFilterValue, RefundFiltersResponse,
    RefundsAnalyticsMetadata, RefundsMetricsResponse,
};
use bigdecimal::ToPrimitive;
use common_enums::Currency;
use common_utils::errors::CustomResult;
use currency_conversion::{conversion::convert, types::ExchangeRates};
use error_stack::ResultExt;
use router_env::{
    logger,
    tracing::{self, Instrument},
};

use super::{
    distribution::RefundDistributionRow,
    filters::{get_refund_filter_for_dimension, RefundFilterRow},
    metrics::RefundMetricRow,
    RefundMetricsAccumulator,
};
use crate::{
    enums::AuthInfo,
    errors::{AnalyticsError, AnalyticsResult},
    metrics,
    refunds::{accumulator::RefundDistributionAccumulator, RefundMetricAccumulator},
    AnalyticsProvider,
};

#[derive(Debug)]
pub enum TaskType {
    MetricTask(
        RefundMetrics,
        CustomResult<HashSet<(RefundMetricsBucketIdentifier, RefundMetricRow)>, AnalyticsError>,
    ),
    DistributionTask(
        RefundDistributions,
        CustomResult<Vec<(RefundMetricsBucketIdentifier, RefundDistributionRow)>, AnalyticsError>,
    ),
}

pub async fn get_metrics(
    pool: &AnalyticsProvider,
    ex_rates: &Option<ExchangeRates>,
    auth: &AuthInfo,
    req: GetRefundMetricRequest,
) -> AnalyticsResult<RefundsMetricsResponse<RefundMetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<RefundMetricsBucketIdentifier, RefundMetricsAccumulator> =
        HashMap::new();
    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let req = req.clone();
        let pool = pool.clone();
        let task_span = tracing::debug_span!(
            "analytics_refund_query",
            refund_metric = metric_type.as_ref()
        );
        // Currently JoinSet works with only static lifetime references even if the task pool does not outlive the given reference
        // We can optimize away this clone once that is fixed
        let auth_scoped = auth.to_owned();
        set.spawn(
            async move {
                let data = pool
                    .get_refund_metrics(
                        &metric_type,
                        &req.group_by_names.clone(),
                        &auth_scoped,
                        &req.filters,
                        req.time_series.map(|t| t.granularity),
                        &req.time_range,
                    )
                    .await
                    .change_context(AnalyticsError::UnknownError);
                TaskType::MetricTask(metric_type, data)
            }
            .instrument(task_span),
        );
    }

    if let Some(distribution) = req.clone().distribution {
        let req = req.clone();
        let pool = pool.clone();
        let task_span = tracing::debug_span!(
            "analytics_refunds_distribution_query",
            refund_distribution = distribution.distribution_for.as_ref()
        );

        let auth_scoped = auth.to_owned();
        set.spawn(
            async move {
                let data = pool
                    .get_refund_distribution(
                        &distribution,
                        &req.group_by_names.clone(),
                        &auth_scoped,
                        &req.filters,
                        &req.time_series.map(|t| t.granularity),
                        &req.time_range,
                    )
                    .await
                    .change_context(AnalyticsError::UnknownError);
                TaskType::DistributionTask(distribution.distribution_for, data)
            }
            .instrument(task_span),
        );
    }

    while let Some(task_type) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        match task_type {
            TaskType::MetricTask(metric, data) => {
                let data = data?;
                let attributes = router_env::metric_attributes!(
                    ("metric_type", metric.to_string()),
                    ("source", pool.to_string()),
                );

                let value = u64::try_from(data.len());
                if let Ok(val) = value {
                    metrics::BUCKETS_FETCHED.record(val, attributes);
                    logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val);
                }

                for (id, value) in data {
                    logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}");
                    let metrics_builder = metrics_accumulator.entry(id).or_default();
                    match metric {
                        RefundMetrics::RefundSuccessRate
                        | RefundMetrics::SessionizedRefundSuccessRate => metrics_builder
                            .refund_success_rate
                            .add_metrics_bucket(&value),
                        RefundMetrics::RefundCount | RefundMetrics::SessionizedRefundCount => {
                            metrics_builder.refund_count.add_metrics_bucket(&value)
                        }
                        RefundMetrics::RefundSuccessCount
                        | RefundMetrics::SessionizedRefundSuccessCount => {
                            metrics_builder.refund_success.add_metrics_bucket(&value)
                        }
                        RefundMetrics::RefundProcessedAmount
                        | RefundMetrics::SessionizedRefundProcessedAmount => {
                            metrics_builder.processed_amount.add_metrics_bucket(&value)
                        }
                        RefundMetrics::SessionizedRefundReason => {
                            metrics_builder.refund_reason.add_metrics_bucket(&value)
                        }
                        RefundMetrics::SessionizedRefundErrorMessage => metrics_builder
                            .refund_error_message
                            .add_metrics_bucket(&value),
                    }
                }

                logger::debug!(
                    "Analytics Accumulated Results: metric: {}, results: {:#?}",
                    metric,
                    metrics_accumulator
                );
            }
            TaskType::DistributionTask(distribution, data) => {
                let data = data?;
                let attributes = router_env::metric_attributes!(
                    ("distribution_type", distribution.to_string()),
                    ("source", pool.to_string()),
                );
                let value = u64::try_from(data.len());
                if let Ok(val) = value {
                    metrics::BUCKETS_FETCHED.record(val, attributes);
                    logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val);
                }

                for (id, value) in data {
                    logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for distribution {distribution}");

                    let metrics_builder = metrics_accumulator.entry(id).or_default();
                    match distribution {
                        RefundDistributions::SessionizedRefundReason => metrics_builder
                            .refund_reason_distribution
                            .add_distribution_bucket(&value),
                        RefundDistributions::SessionizedRefundErrorMessage => metrics_builder
                            .refund_error_message_distribution
                            .add_distribution_bucket(&value),
                    }
                }
                logger::debug!(
                    "Analytics Accumulated Results: distribution: {}, results: {:#?}",
                    distribution,
                    metrics_accumulator
                );
            }
        }
    }

    let mut success = 0;
    let mut total = 0;
    let mut total_refund_processed_amount = 0;
    let mut total_refund_processed_amount_in_usd = 0;
    let mut total_refund_processed_count = 0;
    let mut total_refund_reason_count = 0;
    let mut total_refund_error_message_count = 0;
    let query_data: Vec<RefundMetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| {
            let mut collected_values = val.collect();
            if let Some(success_count) = collected_values.successful_refunds {
                success += success_count;
            }
            if let Some(total_count) = collected_values.total_refunds {
                total += total_count;
            }
            if let Some(amount) = collected_values.refund_processed_amount {
                let amount_in_usd = if let Some(ex_rates) = ex_rates {
                    id.currency
                        .and_then(|currency| {
                            i64::try_from(amount)
                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
                                .ok()
                                .and_then(|amount_i64| {
                                    convert(ex_rates, currency, Currency::USD, amount_i64)
                                        .inspect_err(|e| {
                                            logger::error!("Currency conversion error: {:?}", e)
                                        })
                                        .ok()
                                })
                        })
                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
                        .unwrap_or_default()
                } else {
                    None
                };
                collected_values.refund_processed_amount_in_usd = amount_in_usd;
                total_refund_processed_amount += amount;
                total_refund_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
            }
            if let Some(count) = collected_values.refund_processed_count {
                total_refund_processed_count += count;
            }
            if let Some(total_count) = collected_values.refund_reason_count {
                total_refund_reason_count += total_count;
            }
            if let Some(total_count) = collected_values.refund_error_message_count {
                total_refund_error_message_count += total_count;
            }
            RefundMetricsBucketResponse {
                values: collected_values,
                dimensions: id,
            }
        })
        .collect();
    let total_refund_success_rate = match (success, total) {
        (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
        _ => None,
    };
    Ok(RefundsMetricsResponse {
        query_data,
        meta_data: [RefundsAnalyticsMetadata {
            total_refund_success_rate,
            total_refund_processed_amount: Some(total_refund_processed_amount),
            total_refund_processed_amount_in_usd: if ex_rates.is_some() {
                Some(total_refund_processed_amount_in_usd)
            } else {
                None
            },
            total_refund_processed_count: Some(total_refund_processed_count),
            total_refund_reason_count: Some(total_refund_reason_count),
            total_refund_error_message_count: Some(total_refund_error_message_count),
        }],
    })
}

pub async fn get_filters(
    pool: &AnalyticsProvider,
    req: GetRefundFilterRequest,
    auth: &AuthInfo,
) -> AnalyticsResult<RefundFiltersResponse> {
    let mut res = RefundFiltersResponse::default();
    for dim in req.group_by_names {
        let values = match pool {
                        AnalyticsProvider::Sqlx(pool) => {
                get_refund_filter_for_dimension(dim, auth, &req.time_range, pool)
                    .await
            }
                        AnalyticsProvider::Clickhouse(pool) => {
                get_refund_filter_for_dimension(dim, auth, &req.time_range, pool)
                    .await
            }
                    AnalyticsProvider::CombinedCkh(sqlx_pool, ckh_pool) => {
                let ckh_result = get_refund_filter_for_dimension(
                    dim,
                    auth,
                    &req.time_range,
                    ckh_pool,
                )
                .await;
                let sqlx_result = get_refund_filter_for_dimension(
                    dim,
                    auth,
                    &req.time_range,
                    sqlx_pool,
                )
                .await;
                match (&sqlx_result, &ckh_result) {
                    (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => {
                        router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres refunds analytics filters")
                    },
                    _ => {}
                };
                ckh_result
            }
                    AnalyticsProvider::CombinedSqlx(sqlx_pool, ckh_pool) => {
                let ckh_result = get_refund_filter_for_dimension(
                    dim,
                    auth,
                    &req.time_range,
                    ckh_pool,
                )
                .await;
                let sqlx_result = get_refund_filter_for_dimension(
                    dim,
                    auth,
                    &req.time_range,
                    sqlx_pool,
                )
                .await;
                match (&sqlx_result, &ckh_result) {
                    (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => {
                        router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres refunds analytics filters")
                    },
                    _ => {}
                };
                sqlx_result
            }
        }
        .change_context(AnalyticsError::UnknownError)?
        .into_iter()
        .filter_map(|fil: RefundFilterRow| match dim {
            RefundDimensions::Currency => fil.currency.map(|i| i.as_ref().to_string()),
            RefundDimensions::RefundStatus => fil.refund_status.map(|i| i.as_ref().to_string()),
            RefundDimensions::Connector => fil.connector,
            RefundDimensions::RefundType => fil.refund_type.map(|i| i.as_ref().to_string()),
            RefundDimensions::ProfileId => fil.profile_id,
            RefundDimensions::RefundReason => fil.refund_reason,
            RefundDimensions::RefundErrorMessage => fil.refund_error_message,
        })
        .collect::<Vec<String>>();
        res.query_data.push(RefundFilterValue {
            dimension: dim,
            values,
        })
    }
    Ok(res)
}


impl api::Payment for Worldpay {}


/// fn set_error
    pub fn set_error(&mut self, error: serde_json::Value) {
        self.error = Some(error.to_string());
    }


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


impl api::Refund for Stripe {}


/// Allow [error_stack::Report] to convert between error types
/// This serves as an alternative to [ErrorSwitch]
pub trait ErrorSwitchFrom<T> {
    /// Convert to an error type that the source can be escalated into
    /// This does not consume the source error since we need to keep it in context
    fn switch_from(error: &T) -> Self;
}


impl IntermediateString {
    /// Create a new Instance of IntermediateString using a string
    pub fn new(inner: String) -> Self {
        Self(inner)
    }

    /// Get the inner String
    pub fn into_inner(self) -> String {
        self.0
    }
}


pub struct RedirectUrls {
    pub success_url: Option<String>,
    pub cancel_url: Option<String>,
    pub error_url: Option<String>,
}


pub struct PaymentMethodTokenResult {
    pub payment_method_token_result: Result<Option<String>, ErrorResponse>,
    pub is_payment_method_tokenization_performed: bool,
    pub connector_response: Option<ConnectorResponseData>,
}


OpenAPI Block Path: paths."/organization"
{
  "post": {
    "tags": [
      "Organization"
    ],
    "summary": "Organization - Create",
    "description": "Create a new organization",
    "operationId": "Create an Organization",
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/OrganizationCreateRequest"
          },
          "examples": {
            "Create an organization with organization_name": {
              "value": {
                "organization_name": "organization_abc"
              }
            }
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Organization Created",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OrganizationResponse"
            }
          }
        }
      },
      "400": {
        "description": "Invalid data"
      }
    },
    "security": [
      {
        "admin_api_key": []
      }
    ]
  }
}


pub struct SilverflowCaptureRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub amount: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub close_charge: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reference: Option<String>,
}


pub fn combine(g1: &Self, g2: &Self) -> Result<Self, GraphError<V>> {
        let mut node_builder = builder::ConstraintGraphBuilder::new();
        let mut g1_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g2_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g1_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();
        let mut g2_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();

        let add_domain = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                          domain: DomainInfo|
         -> Result<DomainId, GraphError<V>> {
            node_builder.make_domain(
                domain.domain_identifier.into_inner(),
                &domain.domain_description,
            )
        };

        let add_node = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                        node: &Node<V>|
         -> Result<NodeId, GraphError<V>> {
            match &node.node_type {
                NodeType::Value(node_value) => {
                    Ok(node_builder.make_value_node(node_value.clone(), None, None::<()>))
                }

                NodeType::AllAggregator => {
                    Ok(node_builder.make_all_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::AnyAggregator => {
                    Ok(node_builder.make_any_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::InAggregator(expected) => Ok(node_builder.make_in_aggregator(
                    expected.iter().cloned().collect(),
                    None,
                    None::<()>,
                )?),
            }
        };

        for (_old_domain_id, domain) in g1.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g1_old2new_domain_id.push(new_domain_id);
        }

        for (_old_domain_id, domain) in g2.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g2_old2new_domain_id.push(new_domain_id);
        }

        for (_old_node_id, node) in g1.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g1_old2new_id.push(new_node_id);
        }

        for (_old_node_id, node) in g2.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g2_old2new_id.push(new_node_id);
        }

        for edge in g1.edges.values() {
            let new_pred_id = g1_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g1_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g1.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        for edge in g2.edges.values() {
            let new_pred_id = g2_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g2_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g2.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        Ok(node_builder.build())
    }


pub struct NomupayInnerError {
    pub error_code: String,
    pub error_description: Option<String>,
    pub validation_errors: Option<Vec<ValidationError>>,
}


pub struct AuthenticationRequest {
    #[serde(rename = "threeDSRequestorChallengeInd")]
    pub three_ds_requestor_challenge_ind: Option<ThreedsRequestorChallengeInd>,
}


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

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

impl SupportedPaymentMethodsExt for for SupportedPaymentMethods


pub struct AdditionalAmount {
    additional_amount: StringMajorUnit,
    currency: String,
}


impl PaymentIntent {
    /// Extract customer_id from payment intent feature metadata
    pub fn extract_connector_customer_id_from_payment_intent(
        &self,
    ) -> Result<String, common_utils::errors::ValidationError> {
        self.feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.payment_revenue_recovery_metadata.as_ref())
            .map(|recovery| {
                recovery
                    .billing_connector_payment_details
                    .connector_customer_id
                    .clone()
            })
            .ok_or(
                common_utils::errors::ValidationError::MissingRequiredField {
                    field_name: "connector_customer_id".to_string(),
                },
            )
    }

    fn get_payment_method_sub_type(&self) -> Option<common_enums::PaymentMethodType> {
        self.feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.get_payment_method_sub_type())
    }

    fn get_payment_method_type(&self) -> Option<common_enums::PaymentMethod> {
        self.feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.get_payment_method_type())
    }

    pub fn get_connector_customer_id_from_feature_metadata(&self) -> Option<String> {
        self.feature_metadata
            .as_ref()
            .and_then(|metadata| metadata.payment_revenue_recovery_metadata.as_ref())
            .map(|recovery_metadata| {
                recovery_metadata
                    .billing_connector_payment_details
                    .connector_customer_id
                    .clone()
            })
    }

    pub fn get_billing_merchant_connector_account_id(
        &self,
    ) -> Option<id_type::MerchantConnectorAccountId> {
        self.feature_metadata.as_ref().and_then(|feature_metadata| {
            feature_metadata.get_billing_merchant_connector_account_id()
        })
    }

    fn get_request_incremental_authorization_value(
        request: &api_models::payments::PaymentsCreateIntentRequest,
    ) -> CustomResult<
        common_enums::RequestIncrementalAuthorization,
        errors::api_error_response::ApiErrorResponse,
    > {
        request.request_incremental_authorization
            .map(|request_incremental_authorization| {
                if request_incremental_authorization == common_enums::RequestIncrementalAuthorization::True {
                    if request.capture_method == Some(common_enums::CaptureMethod::Automatic) {
                        Err(errors::api_error_response::ApiErrorResponse::InvalidRequestData { message: "incremental authorization is not supported when capture_method is automatic".to_owned() })?
                    }
                    Ok(common_enums::RequestIncrementalAuthorization::True)
                } else {
                    Ok(common_enums::RequestIncrementalAuthorization::False)
                }
            })
            .unwrap_or(Ok(common_enums::RequestIncrementalAuthorization::default()))
    }

    pub async fn create_domain_model_from_request(
        payment_id: &id_type::GlobalPaymentId,
        merchant_context: &merchant_context::MerchantContext,
        profile: &business_profile::Profile,
        request: api_models::payments::PaymentsCreateIntentRequest,
        decrypted_payment_intent: DecryptedPaymentIntent,
    ) -> CustomResult<Self, errors::api_error_response::ApiErrorResponse> {
        let connector_metadata = request
            .get_connector_metadata_as_value()
            .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError)
            .attach_printable("Error getting connector metadata as value")?;
        let request_incremental_authorization =
            Self::get_request_incremental_authorization_value(&request)?;
        let allowed_payment_method_types = request.allowed_payment_method_types;

        let session_expiry =
            common_utils::date_time::now().saturating_add(time::Duration::seconds(
                request.session_expiry.map(i64::from).unwrap_or(
                    profile
                        .session_expiry
                        .unwrap_or(common_utils::consts::DEFAULT_SESSION_EXPIRY),
                ),
            ));
        let order_details = request.order_details.map(|order_details| {
            order_details
                .into_iter()
                .map(|order_detail| Secret::new(OrderDetailsWithAmount::convert_from(order_detail)))
                .collect()
        });

        Ok(Self {
            id: payment_id.clone(),
            merchant_id: merchant_context.get_merchant_account().get_id().clone(),
            // Intent status would be RequiresPaymentMethod because we are creating a new payment intent
            status: common_enums::IntentStatus::RequiresPaymentMethod,
            amount_details: AmountDetails::from(request.amount_details),
            amount_captured: None,
            customer_id: request.customer_id,
            description: request.description,
            return_url: request.return_url,
            metadata: request.metadata,
            statement_descriptor: request.statement_descriptor,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            last_synced: None,
            setup_future_usage: request.setup_future_usage.unwrap_or_default(),
            active_attempt_id: None,
            order_details,
            allowed_payment_method_types,
            connector_metadata,
            feature_metadata: request.feature_metadata.map(FeatureMetadata::convert_from),
            // Attempt count is 0 in create intent as no attempt is made yet
            attempt_count: 0,
            profile_id: profile.get_id().clone(),
            payment_link_id: None,
            frm_merchant_decision: None,
            updated_by: merchant_context
                .get_merchant_account()
                .storage_scheme
                .to_string(),
            request_incremental_authorization,
            // Authorization count is 0 in create intent as no authorization is made yet
            authorization_count: Some(0),
            session_expiry,
            request_external_three_ds_authentication: request
                .request_external_three_ds_authentication
                .unwrap_or_default(),
            split_txns_enabled: profile.split_txns_enabled,
            frm_metadata: request.frm_metadata,
            customer_details: None,
            merchant_reference_id: request.merchant_reference_id,
            billing_address: decrypted_payment_intent
                .billing_address
                .as_ref()
                .map(|data| {
                    data.clone()
                        .deserialize_inner_value(|value| value.parse_value("Address"))
                })
                .transpose()
                .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to decode billing address")?,
            shipping_address: decrypted_payment_intent
                .shipping_address
                .as_ref()
                .map(|data| {
                    data.clone()
                        .deserialize_inner_value(|value| value.parse_value("Address"))
                })
                .transpose()
                .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to decode shipping address")?,
            capture_method: request.capture_method.unwrap_or_default(),
            authentication_type: request.authentication_type,
            prerouting_algorithm: None,
            organization_id: merchant_context
                .get_merchant_account()
                .organization_id
                .clone(),
            enable_payment_link: request.payment_link_enabled.unwrap_or_default(),
            apply_mit_exemption: request.apply_mit_exemption.unwrap_or_default(),
            customer_present: request.customer_present.unwrap_or_default(),
            payment_link_config: request
                .payment_link_config
                .map(ApiModelToDieselModelConvertor::convert_from),
            routing_algorithm_id: request.routing_algorithm_id,
            split_payments: None,
            force_3ds_challenge: None,
            force_3ds_challenge_trigger: None,
            processor_merchant_id: merchant_context.get_merchant_account().get_id().clone(),
            created_by: None,
            is_iframe_redirection_enabled: None,
            is_payment_id_from_merchant: None,
        })
    }

    pub fn get_revenue_recovery_metadata(
        &self,
    ) -> Option<diesel_models::types::PaymentRevenueRecoveryMetadata> {
        self.feature_metadata
            .as_ref()
            .and_then(|feature_metadata| feature_metadata.payment_revenue_recovery_metadata.clone())
    }

    pub fn get_feature_metadata(&self) -> Option<FeatureMetadata> {
        self.feature_metadata.clone()
    }

    pub fn create_revenue_recovery_attempt_data(
        &self,
        revenue_recovery_metadata: api_models::payments::PaymentRevenueRecoveryMetadata,
        billing_connector_account: &merchant_connector_account::MerchantConnectorAccount,
        card_info: api_models::payments::AdditionalCardInfo,
        payment_processor_token: &str,
    ) -> CustomResult<
        revenue_recovery::RevenueRecoveryAttemptData,
        errors::api_error_response::ApiErrorResponse,
    > {
        let merchant_reference_id = self.merchant_reference_id.clone().ok_or_else(|| {
            error_stack::report!(
                errors::api_error_response::ApiErrorResponse::GenericNotFoundError {
                    message: "mandate reference id not found".to_string()
                }
            )
        })?;

        let connector_account_reference_id = billing_connector_account
            .get_account_reference_id_using_payment_merchant_connector_account_id(
                revenue_recovery_metadata.active_attempt_payment_connector_id,
            )
            .ok_or_else(|| {
                error_stack::report!(
                    errors::api_error_response::ApiErrorResponse::GenericNotFoundError {
                        message: "connector account reference id not found".to_string()
                    }
                )
            })?;

        Ok(revenue_recovery::RevenueRecoveryAttemptData {
            amount: self.amount_details.order_amount,
            currency: self.amount_details.currency,
            merchant_reference_id,
            connector_transaction_id: None, // No connector id
            error_code: None,
            error_message: None,
            processor_payment_method_token: payment_processor_token.to_string(),
            connector_customer_id: revenue_recovery_metadata
                .billing_connector_payment_details
                .connector_customer_id,
            connector_account_reference_id,
            transaction_created_at: None, // would unwrap_or as now
            status: common_enums::AttemptStatus::Started,
            payment_method_type: self
                .get_payment_method_type()
                .unwrap_or(revenue_recovery_metadata.payment_method_type),
            payment_method_sub_type: self
                .get_payment_method_sub_type()
                .unwrap_or(revenue_recovery_metadata.payment_method_subtype),
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            retry_count: None,
            invoice_next_billing_time: None,
            invoice_billing_started_at_time: None,
            // No charge id is present here since it is an internal payment and we didn't call connector yet.
            charge_id: None,
            card_info: card_info.clone(),
        })
    }

    pub fn get_optional_customer_id(
        &self,
    ) -> CustomResult<Option<id_type::CustomerId>, common_utils::errors::ValidationError> {
        self.customer_id
            .as_ref()
            .map(|customer_id| id_type::CustomerId::try_from(customer_id.clone()))
            .transpose()
    }

    pub fn get_optional_connector_metadata(
        &self,
    ) -> CustomResult<
        Option<api_models::payments::ConnectorMetadata>,
        common_utils::errors::ParsingError,
    > {
        self.connector_metadata
            .clone()
            .map(|cm| {
                cm.parse_value::<api_models::payments::ConnectorMetadata>("ConnectorMetadata")
            })
            .transpose()
    }

    pub fn get_currency(&self) -> storage_enums::Currency {
        self.amount_details.currency
    }
}


- **postman:** Remove `routing algorithm` struct from `merchant account create` ([#2032](https://github.com/juspay/hyperswitch/pull/2032)) ([`3d4f750`](https://github.com/juspay/hyperswitch/commit/3d4f750089b97f0fde0e74b833bf386327fb4a52))

**Full Changelog:** [`v1.28.0...v1.28.1`](https://github.com/juspay/hyperswitch/compare/v1.28.0...v1.28.1)

- - -

## 1.28.0 (2023-08-28)

### Features

- **connector:** [CashToCode] perform currency based connector credentials mapping ([#2025](https://github.com/juspay/hyperswitch/pull/2025)) ([`7c0c3b6`](https://github.com/juspay/hyperswitch/commit/7c0c3b6b35f2654bbb64c9631c308925bbf5226d))

**Full Changelog:** [`v1.27.2...v1.28.0`](https://github.com/juspay/hyperswitch/compare/v1.27.2...v1.28.0)

- - -

## 1.27.2 (2023-08-27)

### Bug Fixes

- **request:** Add `idle_pool_connection_timeout` as a config ([#2016](https://github.com/juspay/hyperswitch/pull/2016)) ([`6247996`](https://github.com/juspay/hyperswitch/commit/6247996ddead66086551eef0de8f0b5d678eec27))

### Refactors

- **core:** Authenticate client secret with fulfilment time ([#2026](https://github.com/juspay/hyperswitch/pull/2026)) ([`1e44c8d`](https://github.com/juspay/hyperswitch/commit/1e44c8df1e57351bc5d704d7fc0bee66c5e84aec))

**Full Changelog:** [`v1.27.1...v1.27.2`](https://github.com/juspay/hyperswitch/compare/v1.27.1...v1.27.2)

- - -

## 1.27.1 (2023-08-25)

### Bug Fixes

- **locker:** Accept the incoming token as the basilisk token if it is a mandate payment ([#2013](https://github.com/juspay/hyperswitch/pull/2013)) ([`ac63794`](https://github.com/juspay/hyperswitch/commit/ac637941623ffe7e2b3d6445ea18b5aabbee513f))
- **payment:** Fix max limit on payment intents list ([#2014](https://github.com/juspay/hyperswitch/pull/2014)) ([`a888953`](https://github.com/juspay/hyperswitch/commit/a8889530043efb455b6a20ebffd2e972b5224b6f))

### Testing

- **connector:** Add support for adyen webhooks ([#1999](https://github.com/juspay/hyperswitch/pull/1999)) ([`fcaca76`](https://github.com/juspay/hyperswitch/commit/fcaca76c72bdea19125ae07d927bfd6119353c45))

**Full Changelog:** [`v1.27.0...v1.27.1`](https://github.com/juspay/hyperswitch/compare/v1.27.0...v1.27.1)

- - -

## 1.27.0 (2023-08-24)

### Features

- **api_client:** Add api client trait ([#1919](https://github.com/juspay/hyperswitch/pull/1919)) ([`97b2747`](https://github.com/juspay/hyperswitch/commit/97b2747458fbc9d823d56a6d69eaa5f914c64054))
- **connector:** [Braintree] Add Authorize, Capture, Void, PSync, Refund, Rsync for Braintree GraphQL API ([#1962](https://github.com/juspay/hyperswitch/pull/1962)) ([`820f615`](https://github.com/juspay/hyperswitch/commit/820f6153af10a288afb458089d4bbb2495cd5488))

### Bug Fixes

- **connector:**
  - [Paypal] fix amount to its currency base unit for Paypal Bank redirects ([#2002](https://github.com/juspay/hyperswitch/pull/2002)) ([`4accb41`](https://github.com/juspay/hyperswitch/commit/4accb41ef4ffaec8ac177b938c0f61b0737cc2c8))
  - [Trustpay] Add missing payment status codes in failure check ([#1997](https://github.com/juspay/hyperswitch/pull/1997)) ([`e889749`](https://github.com/juspay/hyperswitch/commit/e8897491b1395e9007d47108c42b789ded354592))
  - Fix payme error response deserialization error ([#1989](https://github.com/juspay/hyperswitch/pull/1989)) ([`16facdf`](https://github.com/juspay/hyperswitch/commit/16facdfa71049a968d448167f63963deb8b50cd0))
  - [Bluesnap] Update incoming Webhooks flow  ([#1982](https://github.com/juspay/hyperswitch/pull/1982)) ([`8c066d3`](https://github.com/juspay/hyperswitch/commit/8c066d3ea73481106982ced5f09058383bc97953))

### Testing

- Move Postman collections to directory structure ([#1995](https://github.com/juspay/hyperswitch/pull/1995)) ([`b7e4048`](https://github.com/juspay/hyperswitch/commit/b7e4048e56fc73bb741a5b25487cd3f56febf90e))

### Miscellaneous Tasks

- **creds:** Updated the API Keys to not use wrong creds ([#2001](https://github.com/juspay/hyperswitch/pull/2001)) ([`ad991c0`](https://github.com/juspay/hyperswitch/commit/ad991c04ecedd85ca4c432126487042c2fd03a67))

**Full Changelog:** [`v1.26.0...v1.27.0`](https://github.com/juspay/hyperswitch/compare/v1.26.0...v1.27.0)

- - -

## 1.26.0 (2023-08-23)

### Features

- **business_profile:** Add profile id in affected tables and modify api contract ([#1971](https://github.com/juspay/hyperswitch/pull/1971)) ([`fe8d4c2`](https://github.com/juspay/hyperswitch/commit/fe8d4c2eeca21e0d79c7a056505790c8cadaef9d))
- **connector:** Fail payment authorize when capture_method is manual_method ([#1893](https://github.com/juspay/hyperswitch/pull/1893)) ([`bca9d50`](https://github.com/juspay/hyperswitch/commit/bca9d5013b902d813a41f04286ea6cb645e1f199))
- **core:** Add psync for multiple partial captures ([#1934](https://github.com/juspay/hyperswitch/pull/1934)) ([`5657ad6`](https://github.com/juspay/hyperswitch/commit/5657ad6933bb407d2ae32f2e068e56c9b9698ed3))
- **pm_list:** Add  card pm required field info for connectors ([#1918](https://github.com/juspay/hyperswitch/pull/1918)) ([`52e0176`](https://github.com/juspay/hyperswitch/commit/52e01769d405308b0b882647e2e824f38aeef3dc))
- **router:**
  - Add relevant metrics and logs for manual retries flow ([#1985](https://github.com/juspay/hyperswitch/pull/1985)) ([`1b346fc`](https://github.com/juspay/hyperswitch/commit/1b346fcf5649a24becff2751aa6f93d7a863ee61))
  - Add fields in payments list response ([#1987](https://github.com/juspay/hyperswitch/pull/1987)) ([`abc736b`](https://github.com/juspay/hyperswitch/commit/abc736bbc13288d9b35c74ed12ec7da443643ee0))
  - Add `attempt_count` in list payments response ([#1990](https://github.com/juspay/hyperswitch/pull/1990)) ([`f0cc0fb`](https://github.com/juspay/hyperswitch/commit/f0cc0fba1684f200ce6dbf3e4bc951de23a60f94))

### Bug Fixes

- **test_utils:** Remove `cmd` alias for `std::process::Command` ([#1981](https://github.com/juspay/hyperswitch/pull/1981)) ([`c161530`](https://github.com/juspay/hyperswitch/commit/c161530a6c8a5486e1cd3fe16f8f01e0ca580108))
- **webhooks:**
  - Send stripe compatible webhooks for stripe compatible merchants ([#1986](https://github.com/juspay/hyperswitch/pull/1986)) ([`36631ad`](https://github.com/juspay/hyperswitch/commit/36631ad97be509d397b91babd4cd1a492703bb5c))
  - Handling errors inside source verification ([#1994](https://github.com/juspay/hyperswitch/pull/1994)) ([`f690c5f`](https://github.com/juspay/hyperswitch/commit/f690c5f3ead64c353ac1d36401e009582c1f0ecf))

### Performance

- **db:** Add index for attempt_id merchant_id ([#1993](https://github.com/juspay/hyperswitch/pull/1993)) ([`57d22b9`](https://github.com/juspay/hyperswitch/commit/57d22b966b911ee8948440072bf9ce23dbd21dd3))

### Refactors

- **core:** Made authenticate_client_secret function public ([#1992](https://github.com/juspay/hyperswitch/pull/1992)) ([`6986772`](https://github.com/juspay/hyperswitch/commit/698677263be56c4ad16cbf90f5607623a18e3d8b))

**Full Changelog:** [`v1.25.1...v1.26.0`](https://github.com/juspay/hyperswitch/compare/v1.25.1...v1.26.0)

- - -

## 1.25.1 (2023-08-22)

### Bug Fixes

- Storage of generic payment methods in permanent locker ([#1799](https://github.com/juspay/hyperswitch/pull/1799)) ([`19ee324`](https://github.com/juspay/hyperswitch/commit/19ee324d373262aea873bb7a120f0e80b918fd84))

**Full Changelog:** [`v1.25.0...v1.25.1`](https://github.com/juspay/hyperswitch/compare/v1.25.0...v1.25.1)

- - -

## 1.25.0 (2023-08-22)

### Features

- **storage_impl:** Split payment intent interface implementation ([#1946](https://github.com/juspay/hyperswitch/pull/1946)) ([`88d65a6`](https://github.com/juspay/hyperswitch/commit/88d65a62fc81f217ade71b2d4903d3bbe85e5c94))

### Bug Fixes

- **core:** Update Webhooks Event Mapping and Forced Psync preconditions ([#1970](https://github.com/juspay/hyperswitch/pull/1970)) ([`8cf1f75`](https://github.com/juspay/hyperswitch/commit/8cf1f75fb1705aa020db5f966e15c3d9a80dd908))

**Full Changelog:** [`v1.24.0...v1.25.0`](https://github.com/juspay/hyperswitch/compare/v1.24.0...v1.25.0)

- - -

## 1.24.0 (2023-08-21)

### Features

- **router:** Add total count for refunds list ([#1935](https://github.com/juspay/hyperswitch/pull/1935)) ([`84967d3`](https://github.com/juspay/hyperswitch/commit/84967d396e628d4cc256ff86d82145c478a91422))

### Bug Fixes

- **typo:** Add typo `daa` to allow list ([#1968](https://github.com/juspay/hyperswitch/pull/1968)) ([`875dbce`](https://github.com/juspay/hyperswitch/commit/875dbce927d86384dd41c2e900ae8074f9540b75))

**Full Changelog:** [`v1.23.0...v1.24.0`](https://github.com/juspay/hyperswitch/compare/v1.23.0...v1.24.0)

- - -

## 1.23.0 (2023-08-18)

### Features

- **business_profile:** Add business profile table and CRUD endpoints ([#1928](https://github.com/juspay/hyperswitch/pull/1928)) ([`53956d6`](https://github.com/juspay/hyperswitch/commit/53956d6f8379f90e4070b49bd2322950aa11a7f2))

### Bug Fixes

- **connector:** [CashToCode] Transform minor units to major units ([#1964](https://github.com/juspay/hyperswitch/pull/1964)) ([`ff2efe8`](https://github.com/juspay/hyperswitch/commit/ff2efe88357a253a22bb8467136717b7809218b6))
- **payment_methods:** Return parent_payment_method_token for other payment methods (BankTransfer, Wallet, BankRedirect)  ([#1951](https://github.com/juspay/hyperswitch/pull/1951)) ([`156430a`](https://github.com/juspay/hyperswitch/commit/156430a5703f40b6bb899caf9904323e39003986))

### Refactors

- **compatibility:** Changed MCA decode 500 error to 422 ([#1958](https://github.com/juspay/hyperswitch/pull/1958)) ([`0d85c1f`](https://github.com/juspay/hyperswitch/commit/0d85c1f8bb3e7d0e1d359d737a1e8a2f0d7885d2))

**Full Changelog:** [`v1.22.0...v1.23.0`](https://github.com/juspay/hyperswitch/compare/v1.22.0...v1.23.0)

- - -

## 1.22.0 (2023-08-18)

### Features

- **router:** Send 2xx payments response for all the connector http responses (2xx, 4xx etc.) ([#1924](https://github.com/juspay/hyperswitch/pull/1924)) ([`0ab6827`](https://github.com/juspay/hyperswitch/commit/0ab6827f6cf54b0a124856487f5359b91048736c))

### Bug Fixes

- **connector:** [Payme] Fix for partial capture validation ([#1939](https://github.com/juspay/hyperswitch/pull/1939)) ([`3d62cb0`](https://github.com/juspay/hyperswitch/commit/3d62cb07dd94d827b18e664a3454352f300575fe))

**Full Changelog:** [`v1.21.2...v1.22.0`](https://github.com/juspay/hyperswitch/compare/v1.21.2...v1.22.0)

- - -

## 1.21.2 (2023-08-17)

### Bug Fixes

- **connector:** [Braintree] fix status mapping for braintree ([#1941](https://github.com/juspay/hyperswitch/pull/1941)) ([`d30fefb`](https://github.com/juspay/hyperswitch/commit/d30fefb2c08d4a086f4d8c0519196d83fa228d45))
- **frm:** Added fraud_check_last_step field in fraud_check table to support 3DS transaction in frm ([#1944](https://github.com/juspay/hyperswitch/pull/1944)) ([`9a39345`](https://github.com/juspay/hyperswitch/commit/9a393455dd6643caf61747633698191ba8c59d49))

### Refactors

- **connector:** Remove payment experience from Not Supported Payment Methods error ([#1937](https://github.com/juspay/hyperswitch/pull/1937)) ([`c5cf029`](https://github.com/juspay/hyperswitch/commit/c5cf029d1f20dc27f6b246094d61a381669feb68))

**Full Changelog:** [`v1.21.1...v1.21.2`](https://github.com/juspay/hyperswitch/compare/v1.21.1...v1.21.2)

- - -

## 1.21.1 (2023-08-15)

### Bug Fixes

- **connector:** [Braintree] add merchant_account_id field in authorize request ([#1916](https://github.com/juspay/hyperswitch/pull/1916)) ([`68df9d6`](https://github.com/juspay/hyperswitch/commit/68df9d617c825e9a4fec88695c3c22588cf3673b))

### Refactors

- **storage_impl:** Integrate the composite store from external crate ([#1921](https://github.com/juspay/hyperswitch/pull/1921)) ([`9f199d9`](https://github.com/juspay/hyperswitch/commit/9f199d9ab8fb7360bda2661a7014aea8906b74f9))

### Documentation

- Documentation changes for clarity ([#1875](https://github.com/juspay/hyperswitch/pull/1875)) ([`b1e4e38`](https://github.com/juspay/hyperswitch/commit/b1e4e3883d4d039c3ed06272d984526da0e657af))

**Full Changelog:** [`v1.21.0...v1.21.1`](https://github.com/juspay/hyperswitch/compare/v1.21.0...v1.21.1)

- - -

## 1.21.0 (2023-08-14)

### Features

- **generics:** Add metrics for database calls ([#1901](https://github.com/juspay/hyperswitch/pull/1901)) ([`bb6ec49`](https://github.com/juspay/hyperswitch/commit/bb6ec49a66bc9380ff0f5eca44cad381b7dc4368))

### Bug Fixes

- **frm:** Add new column frm_config instead of alterning the existing… ([#1925](https://github.com/juspay/hyperswitch/pull/1925)) ([`8d916fe`](https://github.com/juspay/hyperswitch/commit/8d916feb3fe9fd5dd843cb6a4dbc29f5807aa205))
- Add diesel migration to update local db ([#1812](https://github.com/juspay/hyperswitch/pull/1812)) ([`97a495c`](https://github.com/juspay/hyperswitch/commit/97a495cfa700835fd2dbf4f4be1b404a1e4a264a))

### Refactors

- **storage:** Add redis structs to storage impls ([#1910](https://github.com/juspay/hyperswitch/pull/1910)) ([`3e26966`](https://github.com/juspay/hyperswitch/commit/3e269663c36c8a9f11108d01f96bd612f318cc15))

**Full Changelog:** [`v1.20.0...v1.21.0`](https://github.com/juspay/hyperswitch/compare/v1.20.0...v1.21.0)

- - -

## 1.20.0 (2023-08-11)

### Features

- **connector:** [PayMe] Implement preprocessing flow for cards ([#1904](https://github.com/juspay/hyperswitch/pull/1904)) ([`38b9c07`](https://github.com/juspay/hyperswitch/commit/38b9c077b7cd9563aaf3f39876670df7484f519d))
- **router:** Add webhook source verification support for multiple mca of the same connector ([#1897](https://github.com/juspay/hyperswitch/pull/1897)) ([`3554fec`](https://github.com/juspay/hyperswitch/commit/3554fec1c1ab6084480600c73fbefe39085723e0))

### Bug Fixes

- **connector:**
  - [STAX] Add currency filter for payments through Stax ([#1911](https://github.com/juspay/hyperswitch/pull/1911)) ([`5bc7592`](https://github.com/juspay/hyperswitch/commit/5bc7592af3c8587a402809c050e58b257b7af8bf))
  - [Paypal] send valid error_reason in all the error responses ([#1914](https://github.com/juspay/hyperswitch/pull/1914)) ([`3df9441`](https://github.com/juspay/hyperswitch/commit/3df944196f710587eee32be871eaef1d764b694a))
- **payment_methods:** Delete token when a payment reaches terminal state ([#1818](https://github.com/juspay/hyperswitch/pull/1818)) ([`07020d0`](https://github.com/juspay/hyperswitch/commit/07020d01b5d08d9ba5a146d62fbb8c23c6a6d3c2))

### Refactors

- **storage:** Add a separate crate to represent store implementations ([#1853](https://github.com/juspay/hyperswitch/pull/1853)) ([`32b731d`](https://github.com/juspay/hyperswitch/commit/32b731d9591ff4921b7d80556c7ebe050b53121f))

### Miscellaneous Tasks

- **webhooks:** Ignore payment not found in webhooks ([#1886](https://github.com/juspay/hyperswitch/pull/1886)) ([`29f068b`](https://github.com/juspay/hyperswitch/commit/29f068b20581fca280be9a1a98524368d635191f))

**Full Changelog:** [`v1.19.0...v1.20.0`](https://github.com/juspay/hyperswitch/compare/v1.19.0...v1.20.0)

- - -

## 1.19.0 (2023-08-10)

### Features

- **connector:** [Adyen] implement Japanese convenience stores ([#1819](https://github.com/juspay/hyperswitch/pull/1819)) ([`a6fdf6d`](https://github.com/juspay/hyperswitch/commit/a6fdf6dc34901a9985062fd5532d967910bcf3c0))
- **docs:** Add multiple examples support and webhook schema ([#1864](https://github.com/juspay/hyperswitch/pull/1864)) ([`f8ef52c`](https://github.com/juspay/hyperswitch/commit/f8ef52c645d353aac438d6af5b00d9097332fdcb))

### Bug Fixes

- **connector:**
  - [ACI] Response Handling in case of `ErrorResponse` ([#1870](https://github.com/juspay/hyperswitch/pull/1870)) ([`14f599d`](https://github.com/juspay/hyperswitch/commit/14f599d1be272afcfd16dfac58c47dbbb649423d))
  - [Adyen] Response Handling in case of RefusalResponse ([#1877](https://github.com/juspay/hyperswitch/pull/1877)) ([`c35a571`](https://github.com/juspay/hyperswitch/commit/c35a5719eb08ff76a10d554a0e61d0af81ff26e6))
- **router:** Handle JSON connector response parse error ([#1892](https://github.com/juspay/hyperswitch/pull/1892)) ([`393c2ab`](https://github.com/juspay/hyperswitch/commit/393c2ab94cf1052f6f8fa0b40c09e36555ffecd7))

### Refactors

- **connector:** Update the `connector_template`  ([#1895](https://github.com/juspay/hyperswitch/pull/1895)) ([`5fe96d4`](https://github.com/juspay/hyperswitch/commit/5fe96d4d9683d8eae25f214f3823d3765dce326a))
- Remove unnecessary debug logs from payment method list api ([#1884](https://github.com/juspay/hyperswitch/pull/1884)) ([`ba82f17`](https://github.com/juspay/hyperswitch/commit/ba82f173dbccfc2312677ec96fdd85813a417dc6))

### Documentation

- Add architecture and monitoring diagram of hyperswitch ([#1825](https://github.com/juspay/hyperswitch/pull/1825)) ([`125ef2b`](https://github.com/juspay/hyperswitch/commit/125ef2b4f82c922209bcfe161ce4790fe2ee3a86))

### Miscellaneous Tasks

- **configs:** Add `payout_connector_list` config to toml ([#1909](https://github.com/juspay/hyperswitch/pull/1909)) ([`c1e5626`](https://github.com/juspay/hyperswitch/commit/c1e56266df6aabd1c498d6a7ebec324b0df23c12))
- Add connector functionality validation based on connector_type ([#1849](https://github.com/juspay/hyperswitch/pull/1849)) ([`33c6d71`](https://github.com/juspay/hyperswitch/commit/33c6d71a8a71619f811accbc21f3c22c3c279c47))
- Remove spaces at beginning of commit messages when generating changelogs ([#1906](https://github.com/juspay/hyperswitch/pull/1906)) ([`7d13226`](https://github.com/juspay/hyperswitch/commit/7d13226740dbc4c1b6ec19631bb93ba89281d303))

**Full Changelog:** [`v1.18.0...v1.19.0`](https://github.com/juspay/hyperswitch/compare/v1.18.0...v1.19.0)

- - -

## 1.18.0 (2023-08-09)

### Features

- **connector:**
  - [Adyen] Add support for card redirection (KNET, BENEFIT) ([#1816](https://github.com/juspay/hyperswitch/pull/1816)) ([`62461f1`](https://github.com/juspay/hyperswitch/commit/62461f1b3849bfde3d0c0608b9efd96334e30f97))


pub struct GooglePayAssuranceDetails {
    ///indicates that Cardholder possession validation has been performed
    pub card_holder_authenticated: bool,
    /// indicates that identification and verifications (ID&V) was performed
    pub account_verified: bool,
}


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


impl api::PaymentAuthorize for Stripebilling {}

impl api::PaymentAuthorize for for Stripebilling


File: crates/router/src/utils/storage_partitioning.rs

pub use storage_impl::redis::kv_store::{KvStorePartition, PartitionKey};


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

        Self {
            payment_methods_enabled: payment_methods_enabled_flattened_with_connector,
        }
    }
}


pub struct RequestBuilder {
    pub url: String,
    pub headers: Headers,
    pub method: Method,
    pub certificate: Option<Secret<String>>,
    pub certificate_key: Option<Secret<String>>,
    pub body: Option<RequestContent>,
    pub ca_certificate: Option<Secret<String>>,
}


impl api::RefundSync for Amazonpay {}


pub struct ExchangeTokenResponse {
    pub access_token: String,
}


pub struct CardDetails {
    #[serde(rename = "card.number")]
    pub card_number: cards::CardNumber,
    #[serde(rename = "card.holder")]
    pub card_holder: Secret<String>,
    #[serde(rename = "card.expiryMonth")]
    pub card_expiry_month: Secret<String>,
    #[serde(rename = "card.expiryYear")]
    pub card_expiry_year: Secret<String>,
    #[serde(rename = "card.cvv")]
    pub card_cvv: Secret<String>,
}


impl MerchantConnectorAccount {
    pub async fn update(
        self,
        conn: &PgPooledConn,
        merchant_connector_account: MerchantConnectorAccountUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.id.to_owned(),
            merchant_connector_account,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn delete_by_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(conn, dsl::id.eq(id.to_owned()))
            .await
    }

    pub async fn find_by_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::id.eq(id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        get_disabled: bool,
    ) -> StorageResult<Vec<Self>> {
        if get_disabled {
            generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()),
                None,
                None,
                Some(dsl::created_at.asc()),
            )
            .await
        } else {
            generics::generic_filter::<
                <Self as HasTable>::Table,
                _,
                <<Self as HasTable>::Table as Table>::PrimaryKey,
                _,
            >(
                conn,
                dsl::merchant_id
                    .eq(merchant_id.to_owned())
                    .and(dsl::disabled.eq(false)),
                None,
                None,
                None,
            )
            .await
        }
    }

    pub async fn list_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::profile_id.eq(profile_id.to_owned()),
            None,
            None,
            Some(dsl::created_at.asc()),
        )
        .await
    }

    pub async fn list_enabled_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        connector_type: common_enums::ConnectorType,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::profile_id
                .eq(profile_id.to_owned())
                .and(dsl::disabled.eq(false))
                .and(dsl::connector_type.eq(connector_type)),
            None,
            None,
            Some(dsl::created_at.asc()),
        )
        .await
    }
}


impl ConnectorSpecifications for Worldline {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&WORLDLINE_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*WORLDLINE_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&WORLDLINE_SUPPORTED_WEBHOOK_FLOWS)
    }
}

impl ConnectorSpecifications for for Worldline


pub struct TokenizePaymentMethodRequest {
    /// Payment method's ID
    #[serde(skip_deserializing)]
    pub payment_method_id: String,

    /// The CVC number for the card
    #[schema(value_type = Option<String>,  example = "242")]
    pub card_cvc: Option<masking::Secret<String>>,
}


pub fn generate_secret_key(&self) -> String {
        format!("cs_{}", self.secret.peek())
    }


impl api::RefundSync for Placetopay {}


impl AuthInfo for AuthenticationDataWithMultipleProfiles {
    fn get_merchant_id(&self) -> Option<&id_type::MerchantId> {
        Some(self.merchant_account.get_id())
    }
}

impl AuthInfo for for AuthenticationDataWithMultipleProfiles


impl SubmitEvidence for Adyen {}


impl RoutingDecisionData {
    pub fn apply_routing_decision<F, D>(&self, payment_data: &mut D)
    where
        F: Send + Clone,
        D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    {
        match self {
            Self::DebitRouting(data) => data.apply_debit_routing_decision(payment_data),
        }
    }

    pub fn get_debit_routing_decision_data(
        network: common_enums::enums::CardNetwork,
        debit_routing_result: Option<open_router::DebitRoutingOutput>,
    ) -> Self {
        Self::DebitRouting(DebitRoutingDecisionData {
            card_network: network,
            debit_routing_result,
        })
    }
}


pub struct RefundMetricsBucketIdentifier {
    pub currency: Option<Currency>,
    pub refund_status: Option<String>,
    pub connector: Option<String>,
    pub refund_type: Option<String>,
    pub profile_id: Option<String>,
    pub refund_reason: Option<String>,
    pub refund_error_message: Option<String>,
    #[serde(rename = "time_range")]
    pub time_bucket: TimeRange,
    #[serde(rename = "time_bucket")]
    #[serde(with = "common_utils::custom_serde::iso8601custom")]
    pub start_time: time::PrimitiveDateTime,
}