File: crates/hyperswitch_domain_models/src/router_request_types/revenue_recovery.rs
 Public structs: 3

use common_enums::enums;

use crate::connector_endpoints;

#[derive(Debug, Clone)]
pub struct BillingConnectorPaymentsSyncRequest {
    /// unique id for making billing connector psync call
    pub billing_connector_psync_id: String,
    /// connector params of the connector
    pub connector_params: connector_endpoints::ConnectorParams,
}

#[derive(Debug, Clone)]
pub struct RevenueRecoveryRecordBackRequest {
    pub merchant_reference_id: common_utils::id_type::PaymentReferenceId,
    pub amount: common_utils::types::MinorUnit,
    pub currency: enums::Currency,
    pub payment_method_type: Option<common_enums::PaymentMethodType>,
    pub attempt_status: common_enums::AttemptStatus,
    pub connector_transaction_id: Option<common_utils::types::ConnectorTransactionId>,
    pub connector_params: connector_endpoints::ConnectorParams,
}

#[derive(Debug, Clone)]
pub struct BillingConnectorInvoiceSyncRequest {
    /// Invoice id
    pub billing_connector_invoice_id: String,
    /// connector params of the connector
    pub connector_params: connector_endpoints::ConnectorParams,
}


File: crates/hyperswitch_connectors/src/connectors/payone.rs
 Public functions: 2
 Public structs: 1

pub mod transformers;

use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId};
use base64::Engine;
#[cfg(feature = "payouts")]
use common_utils::request::RequestContent;
use common_utils::{consts::BASE64_ENGINE, errors::CustomResult, ext_traits::BytesExt};
#[cfg(feature = "payouts")]
use common_utils::{
    request::{Method, Request, RequestBuilder},
    types::{AmountConvertor, MinorUnit, MinorUnitForConnector},
};
use error_stack::{report, ResultExt};
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_data::RouterData,
    router_flow_types::PoFulfill,
    types::{PayoutsData, PayoutsResponseData, PayoutsRouterData},
};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse},
    router_flow_types::{
        AccessTokenAuth, Authorize, Capture, Execute, PSync, PaymentMethodToken, RSync, Session,
        SetupMandate, Void,
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods,
    },
};
use hyperswitch_interfaces::{
    api::{
        ConnectorAccessToken, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration,
        ConnectorSpecifications, ConnectorValidation, CurrencyUnit, MandateSetup, Payment,
        PaymentAuthorize, PaymentCapture, PaymentSession, PaymentSync, PaymentToken, PaymentVoid,
        Refund, RefundExecute, RefundSync,
    },
    configs::Connectors,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors::ConnectorError,
    events::connector_api_logs::ConnectorEvent,
    types::Response,
    webhooks::{IncomingWebhook, IncomingWebhookRequestDetails},
};
#[cfg(feature = "payouts")]
use hyperswitch_interfaces::{
    api::{PayoutFulfill, Payouts},
    types::PayoutFulfillType,
};
use masking::{ExposeInterface, Mask, Maskable, PeekInterface};
use ring::hmac;
#[cfg(feature = "payouts")]
use router_env::{instrument, tracing};

use self::transformers as payone;
#[cfg(feature = "payouts")]
use crate::constants::headers::DATE;
#[cfg(feature = "payouts")]
use crate::get_formatted_date_time;
use crate::{
    constants::headers::AUTHORIZATION,
    utils::{
        get_error_code_error_message_based_on_priority, ConnectorErrorType,
        ConnectorErrorTypeMapping,
    },
};
#[cfg(feature = "payouts")]
use crate::{types::ResponseRouterData, utils::convert_amount};
#[derive(Clone)]
pub struct Payone {
    #[cfg(feature = "payouts")]
    amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
}

impl Payone {
    pub fn new() -> &'static Self {
        &Self {
            #[cfg(feature = "payouts")]
            amount_converter: &MinorUnitForConnector,
        }
    }
    pub fn generate_signature(
        &self,
        auth: payone::PayoneAuthType,
        http_method: String,
        canonicalized_path: String,
        content_type: String,
        date_header: String,
    ) -> CustomResult<String, ConnectorError> {
        let payone::PayoneAuthType {
            api_key,
            api_secret,
            ..
        } = auth;
        let string_to_hash: String = format!(
            "{}\n{}\n{}\n{}\n",
            http_method,
            content_type.trim(),
            date_header.trim(),
            canonicalized_path.trim()
        );
        let key = hmac::Key::new(hmac::HMAC_SHA256, api_secret.expose().as_bytes());
        let hash_hmac = BASE64_ENGINE.encode(hmac::sign(&key, string_to_hash.as_bytes()));
        let signature_header = format!("GCS v1HMAC:{}:{}", api_key.peek(), hash_hmac);

        Ok(signature_header)
    }
}

impl Payment for Payone {}
impl PaymentSession for Payone {}
impl ConnectorAccessToken for Payone {}
impl MandateSetup for Payone {}
impl PaymentAuthorize for Payone {}
impl PaymentSync for Payone {}
impl PaymentCapture for Payone {}
impl PaymentVoid for Payone {}
impl Refund for Payone {}
impl RefundExecute for Payone {}
impl RefundSync for Payone {}
impl PaymentToken for Payone {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Payone
{
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Payone
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    #[cfg(feature = "payouts")]
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        let auth = payone::PayoneAuthType::try_from(&req.connector_auth_type)?;
        let http_method = self.get_http_method().to_string();
        let content_type = Self::get_content_type(self);
        let base_url = self.base_url(connectors);
        let url = Self::get_url(self, req, connectors)?;
        let date_header = get_formatted_date_time!(
            "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
        )?;
        let path: String = url.replace(base_url, "/");
        let authorization_header: String = self.generate_signature(
            auth,
            http_method,
            path,
            content_type.to_string(),
            date_header.clone(),
        )?;
        let headers = vec![
            (DATE.to_string(), date_header.to_string().into()),
            (
                AUTHORIZATION.to_string(),
                authorization_header.to_string().into(),
            ),
        ];

        Ok(headers)
    }
}

impl ConnectorCommon for Payone {
    fn id(&self) -> &'static str {
        "payone"
    }

    fn get_currency_unit(&self) -> CurrencyUnit {
        CurrencyUnit::Minor
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.payone.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        let auth = payone::PayoneAuthType::try_from(auth_type)
            .change_context(ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            AUTHORIZATION.to_string(),
            auth.api_key.expose().into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        let response: payone::ErrorResponse = res
            .response
            .parse_struct("ErrorResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        let errors_list = response.errors.clone().unwrap_or_default();
        let option_error_code_message = get_error_code_error_message_based_on_priority(
            self.clone(),
            errors_list
                .into_iter()
                .map(|errors| errors.into())
                .collect(),
        );
        match response.errors {
            Some(errors) => Ok(ErrorResponse {
                status_code: res.status_code,
                code: option_error_code_message
                    .clone()
                    .map(|error_code_message| error_code_message.error_code)
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: option_error_code_message
                    .clone()
                    .map(|error_code_message| error_code_message.error_code)
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                reason: Some(
                    errors
                        .iter()
                        .map(|error| format!("{} : {}", error.code, error.message))
                        .collect::<Vec<String>>()
                        .join(", "),
                ),
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            None => Ok(ErrorResponse {
                status_code: res.status_code,
                code: NO_ERROR_CODE.to_string(),
                message: NO_ERROR_MESSAGE.to_string(),
                reason: None,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        }
    }
}
impl ConnectorValidation for Payone {}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Payone {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Payone {}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Payone {}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Payone {}
#[cfg(feature = "payouts")]
impl Payouts for Payone {}

#[cfg(feature = "payouts")]
impl PayoutFulfill for Payone {}

#[cfg(feature = "payouts")]
impl ConnectorIntegration<PoFulfill, PayoutsData, PayoutsResponseData> for Payone {
    fn get_url(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        _connectors: &Connectors,
    ) -> CustomResult<String, ConnectorError> {
        let auth = payone::PayoneAuthType::try_from(&req.connector_auth_type)
            .change_context(ConnectorError::FailedToObtainAuthType)?;
        Ok(format!(
            "{}v2/{}/payouts",
            self.base_url(_connectors),
            auth.merchant_account.peek()
        ))
    }

    fn get_headers(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.destination_currency,
        )?;
        let connector_router_data = payone::PayoneRouterData::from((amount, req));
        let connector_req = payone::PayonePayoutFulfillRequest::try_from(connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PayoutsRouterData<PoFulfill>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&PayoutFulfillType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(PayoutFulfillType::get_headers(self, req, connectors)?)
            .set_body(PayoutFulfillType::get_request_body(self, req, connectors)?)
            .build();
        Ok(Some(request))
    }

    #[instrument(skip_all)]
    fn handle_response(
        &self,
        data: &PayoutsRouterData<PoFulfill>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PayoutsRouterData<PoFulfill>, ConnectorError> {
        let response: payone::PayonePayoutFulfillResponse = res
            .response
            .parse_struct("PayonePayoutFulfillResponse")
            .change_context(ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl IncomingWebhook for Payone {
    fn get_webhook_object_reference_id(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<ObjectReferenceId, ConnectorError> {
        Err(report!(ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, ConnectorError> {
        Err(report!(ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, ConnectorError> {
        Err(report!(ConnectorError::WebhooksNotImplemented))
    }
}

impl ConnectorErrorTypeMapping for Payone {
    fn get_connector_error_type(
        &self,
        error_code: String,
        _error_message: String,
    ) -> ConnectorErrorType {
        match error_code.as_str() {
            "30001101" => ConnectorErrorType::BusinessError,
            "30001100" => ConnectorErrorType::BusinessError,
            "30001102" => ConnectorErrorType::BusinessError,
            "30001104" => ConnectorErrorType::BusinessError,
            "30001105" => ConnectorErrorType::BusinessError,
            "30001106" => ConnectorErrorType::TechnicalError,
            "30001120" => ConnectorErrorType::BusinessError,
            "30001130" => ConnectorErrorType::BusinessError,
            "30001140" => ConnectorErrorType::BusinessError,
            "30001141" => ConnectorErrorType::BusinessError,
            "30001142" => ConnectorErrorType::BusinessError,
            "30001143" => ConnectorErrorType::BusinessError,
            "30001158" => ConnectorErrorType::UserError,
            "30001180" => ConnectorErrorType::TechnicalError,
            "30031001" => ConnectorErrorType::UserError,
            "30041001" => ConnectorErrorType::UserError,
            "30051001" => ConnectorErrorType::BusinessError,
            "30141001" => ConnectorErrorType::UserError,
            "30431001" => ConnectorErrorType::UserError,
            "30511001" => ConnectorErrorType::UserError,
            "30581001" => ConnectorErrorType::UserError,
            "30591001" => ConnectorErrorType::BusinessError,
            "30621001" => ConnectorErrorType::BusinessError,
            "30921001" => ConnectorErrorType::TechnicalError,
            "40001134" => ConnectorErrorType::BusinessError,
            "40001135" => ConnectorErrorType::BusinessError,
            "50001081" => ConnectorErrorType::TechnicalError,
            "40001137" => ConnectorErrorType::TechnicalError,
            "40001138" => ConnectorErrorType::TechnicalError,
            "40001139" => ConnectorErrorType::UserError,
            "50001054" => ConnectorErrorType::TechnicalError,
            "50001087" => ConnectorErrorType::TechnicalError,
            _ => ConnectorErrorType::UnknownError,
        }
    }
}

static PAYONE_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Payone",
    description: "Payone payout connector for European market disbursements and automated fund distribution with comprehensive compliance support",
    connector_type: common_enums::HyperswitchConnectorCategory::PayoutProcessor,
    integration_status: common_enums::ConnectorIntegrationStatus::Sandbox,
};

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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

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


pub async fn get_forex_rates(
    state: &SessionState,
    data_expiration_delay: u32,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexError> {
    if let Some(local_rates) = retrieve_forex_from_local_cache().await {
        if local_rates.is_expired(data_expiration_delay) {
            // expired local data
            logger::debug!("forex_log: Forex stored in cache is expired");
            call_forex_api_and_save_data_to_cache_and_redis(state, Some(local_rates)).await
        } else {
            // Valid data present in local
            logger::debug!("forex_log: forex found in cache");
            Ok(local_rates)
        }
    } else {
        // No data in local
        call_api_if_redis_forex_data_expired(state, data_expiration_delay).await
    }
}


pub async fn save_migration_payment_method(
    req: pm_api::PaymentMethodCreate,
    migration_status: &mut migration::RecordMigrationStatusBuilder,
    controller: &dyn PaymentMethodsController,
) -> CustomResult<ApplicationResponse<pm_api::PaymentMethodResponse>, errors::ApiErrorResponse> {
    let connector_mandate_details = req
        .connector_mandate_details
        .clone()
        .map(serde_json::to_value)
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let network_transaction_id = req.network_transaction_id.clone();

    let res = controller.add_payment_method(&req).await?;

    migration_status.card_migrated(true);
    migration_status.network_transaction_id_migrated(
        network_transaction_id.and_then(|val| (!val.is_empty_after_trim()).then_some(true)),
    );

    migration_status.connector_mandate_details_migrated(
        connector_mandate_details
            .and_then(|val| if val == json!({}) { None } else { Some(true) })
            .or_else(|| {
                req.connector_mandate_details
                    .and_then(|val| (!val.0.is_empty()).then_some(false))
            }),
    );
    Ok(res)
}


File: crates/router/src/utils/user/two_factor_auth.rs
 Public functions: 16

use common_utils::pii;
use error_stack::ResultExt;
use masking::{ExposeInterface, PeekInterface};
use totp_rs::{Algorithm, TOTP};

use crate::{
    consts,
    core::errors::{UserErrors, UserResult},
    routes::SessionState,
};

pub fn generate_default_totp(
    email: pii::Email,
    secret: Option<masking::Secret<String>>,
    issuer: String,
) -> UserResult<TOTP> {
    let secret = secret
        .map(|sec| totp_rs::Secret::Encoded(sec.expose()))
        .unwrap_or_else(totp_rs::Secret::generate_secret)
        .to_bytes()
        .change_context(UserErrors::InternalServerError)?;

    TOTP::new(
        Algorithm::SHA1,
        consts::user::TOTP_DIGITS,
        consts::user::TOTP_TOLERANCE,
        consts::user::TOTP_VALIDITY_DURATION_IN_SECONDS,
        secret,
        Some(issuer),
        email.expose().expose(),
    )
    .change_context(UserErrors::InternalServerError)
}

pub async fn check_totp_in_redis(state: &SessionState, user_id: &str) -> UserResult<bool> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id);
    redis_conn
        .exists::<()>(&key.into())
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn check_recovery_code_in_redis(state: &SessionState, user_id: &str) -> UserResult<bool> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id);
    redis_conn
        .exists::<()>(&key.into())
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn insert_totp_in_redis(state: &SessionState, user_id: &str) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id);
    redis_conn
        .set_key_with_expiry(
            &key.as_str().into(),
            common_utils::date_time::now_unix_timestamp(),
            state.conf.user.two_factor_auth_expiry_in_secs,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn insert_totp_secret_in_redis(
    state: &SessionState,
    user_id: &str,
    secret: &masking::Secret<String>,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .set_key_with_expiry(
            &get_totp_secret_key(user_id).into(),
            secret.peek(),
            consts::user::REDIS_TOTP_SECRET_TTL_IN_SECS,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn get_totp_secret_from_redis(
    state: &SessionState,
    user_id: &str,
) -> UserResult<Option<masking::Secret<String>>> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<String>>(&get_totp_secret_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|secret| secret.map(Into::into))
}

pub async fn delete_totp_secret_from_redis(state: &SessionState, user_id: &str) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&get_totp_secret_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|_| ())
}

fn get_totp_secret_key(user_id: &str) -> String {
    format!("{}{}", consts::user::REDIS_TOTP_SECRET_PREFIX, user_id)
}

pub async fn insert_recovery_code_in_redis(state: &SessionState, user_id: &str) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id);
    redis_conn
        .set_key_with_expiry(
            &key.as_str().into(),
            common_utils::date_time::now_unix_timestamp(),
            state.conf.user.two_factor_auth_expiry_in_secs,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn delete_totp_from_redis(state: &SessionState, user_id: &str) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id);
    redis_conn
        .delete_key(&key.into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|_| ())
}

pub async fn delete_recovery_code_from_redis(
    state: &SessionState,
    user_id: &str,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id);
    redis_conn
        .delete_key(&key.into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|_| ())
}

fn get_totp_attempts_key(user_id: &str) -> String {
    format!("{}{}", consts::user::REDIS_TOTP_ATTEMPTS_PREFIX, user_id)
}
fn get_recovery_code_attempts_key(user_id: &str) -> String {
    format!(
        "{}{}",
        consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_PREFIX,
        user_id
    )
}

pub async fn insert_totp_attempts_in_redis(
    state: &SessionState,
    user_id: &str,
    user_totp_attempts: u8,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .set_key_with_expiry(
            &get_totp_attempts_key(user_id).into(),
            user_totp_attempts,
            consts::user::REDIS_TOTP_ATTEMPTS_TTL_IN_SECS,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}
pub async fn get_totp_attempts_from_redis(state: &SessionState, user_id: &str) -> UserResult<u8> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<u8>>(&get_totp_attempts_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|v| v.unwrap_or(0))
}

pub async fn insert_recovery_code_attempts_in_redis(
    state: &SessionState,
    user_id: &str,
    user_recovery_code_attempts: u8,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .set_key_with_expiry(
            &get_recovery_code_attempts_key(user_id).into(),
            user_recovery_code_attempts,
            consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_TTL_IN_SECS,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

pub async fn get_recovery_code_attempts_from_redis(
    state: &SessionState,
    user_id: &str,
) -> UserResult<u8> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<u8>>(&get_recovery_code_attempts_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|v| v.unwrap_or(0))
}

pub async fn delete_totp_attempts_from_redis(
    state: &SessionState,
    user_id: &str,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&get_totp_attempts_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|_| ())
}

pub async fn delete_recovery_code_attempts_from_redis(
    state: &SessionState,
    user_id: &str,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&get_recovery_code_attempts_key(user_id).into())
        .await
        .change_context(UserErrors::InternalServerError)
        .map(|_| ())
}


impl ReverseLookup {
    pub async fn find_by_lookup_id(lookup_id: &str, conn: &PgPooledConn) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::lookup_id.eq(lookup_id.to_owned()),
        )
        .await
    }
}


File: crates/storage_impl/src/mandate.rs

use diesel_models::Mandate;

use crate::redis::kv_store::KvStorePartition;

impl KvStorePartition for Mandate {}


pub trait IntoDirValue {
    fn into_dir_value(self) -> Result<dir::DirValue, KgraphError>;
}


pub struct WorldpayvantivPaymentMetadata {
    pub report_group: Option<String>,
}


File: crates/router/src/core/payments.rs
 Public functions: 93
 Public structs: 15

pub mod access_token;
pub mod conditional_configs;
pub mod customers;
pub mod flows;
pub mod helpers;
pub mod operations;

#[cfg(feature = "retry")]
pub mod retry;
pub mod routing;
#[cfg(feature = "v2")]
pub mod session_operation;
pub mod tokenization;
pub mod transformers;
pub mod types;
#[cfg(feature = "v2")]
pub mod vault_session;
#[cfg(feature = "olap")]
use std::collections::HashMap;
use std::{
    collections::HashSet, fmt::Debug, marker::PhantomData, ops::Deref, str::FromStr, time::Instant,
    vec::IntoIter,
};

#[cfg(feature = "v2")]
pub mod payment_methods;

use std::future;

#[cfg(feature = "olap")]
use api_models::admin::MerchantConnectorInfo;
use api_models::{
    self, enums,
    mandates::RecurringDetails,
    payments::{self as payments_api},
};
pub use common_enums::enums::{CallConnectorAction, GatewaySystem};
use common_types::payments as common_payments_types;
use common_utils::{
    ext_traits::{AsyncExt, StringExt},
    id_type, pii,
    types::{AmountConvertor, MinorUnit, Surcharge},
};
use diesel_models::{ephemeral_key, fraud_check::FraudCheck, refund as diesel_refund};
use error_stack::{report, ResultExt};
use events::EventInfo;
use futures::future::join_all;
use helpers::{decrypt_paze_token, ApplePayData};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::payments::{
    PaymentAttemptListData, PaymentCaptureData, PaymentConfirmData, PaymentIntentData,
    PaymentStatusData,
};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::router_response_types::RedirectForm;
pub use hyperswitch_domain_models::{
    mandates::MandateData,
    payment_address::PaymentAddress,
    payments::{self as domain_payments, HeaderPayload},
    router_data::{PaymentMethodToken, RouterData},
    router_request_types::CustomerDetails,
};
use hyperswitch_domain_models::{
    payments::{self, payment_intent::CustomerData, ClickToPayMetaData},
    router_data::AccessToken,
};
use masking::{ExposeInterface, PeekInterface, Secret};
#[cfg(feature = "v2")]
use operations::ValidateStatusForOperation;
use redis_interface::errors::RedisError;
use router_env::{instrument, tracing};
#[cfg(feature = "olap")]
use router_types::transformers::ForeignFrom;
use rustc_hash::FxHashMap;
use scheduler::utils as pt_utils;
#[cfg(feature = "v2")]
pub use session_operation::payments_session_core;
#[cfg(feature = "olap")]
use strum::IntoEnumIterator;
use time;

#[cfg(feature = "v1")]
pub use self::operations::{
    PaymentApprove, PaymentCancel, PaymentCancelPostCapture, PaymentCapture, PaymentConfirm,
    PaymentCreate, PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject,
    PaymentSession, PaymentSessionUpdate, PaymentStatus, PaymentUpdate, PaymentUpdateMetadata,
};
use self::{
    conditional_configs::perform_decision_management,
    flows::{ConstructFlowSpecificData, Feature},
    operations::{BoxedOperation, Operation, PaymentResponse},
    routing::{self as self_routing, SessionFlowRoutingInput},
};
#[cfg(feature = "v1")]
use super::unified_connector_service::update_gateway_system_in_feature_metadata;
use super::{
    errors::StorageErrorExt, payment_methods::surcharge_decision_configs, routing::TransactionData,
    unified_connector_service::should_call_unified_connector_service,
};
#[cfg(feature = "v1")]
use crate::core::debit_routing;
#[cfg(feature = "frm")]
use crate::core::fraud_check as frm_core;
#[cfg(feature = "v2")]
use crate::core::payment_methods::vault;
#[cfg(feature = "v1")]
use crate::core::routing::helpers as routing_helpers;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use crate::types::api::convert_connector_data_to_routable_connectors;
use crate::{
    configs::settings::{ApplePayPreDecryptFlow, PaymentFlow, PaymentMethodTypeTokenFilter},
    consts,
    core::{
        errors::{self, CustomResult, RouterResponse, RouterResult},
        payment_methods::{cards, network_tokenization},
        payouts,
        routing::{self as core_routing},
        unified_authentication_service::types::{ClickToPay, UnifiedAuthenticationService},
        utils::{self as core_utils},
    },
    db::StorageInterface,
    logger,
    routes::{app::ReqState, metrics, payment_methods::ParentPaymentMethodToken, SessionState},
    services::{self, api::Authenticate, ConnectorRedirectResponse},
    types::{
        self as router_types,
        api::{self, ConnectorCallType, ConnectorCommon},
        domain,
        storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt},
        transformers::ForeignTryInto,
    },
    utils::{
        self, add_apple_pay_flow_metrics, add_connector_http_status_code_metrics, Encode,
        OptionExt, ValueExt,
    },
    workflows::payment_sync,
};
#[cfg(feature = "v1")]
use crate::{
    core::authentication as authentication_core,
    types::{api::authentication, BrowserInformation},
};

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[instrument(skip_all, fields(payment_id, merchant_id))]
pub async fn payments_operation_core<F, Req, Op, FData, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile: &domain::Profile,
    operation: Op,
    req: Req,
    get_tracker_response: operations::GetTrackerResponse<D>,
    call_connector_action: CallConnectorAction,
    header_payload: HeaderPayload,
) -> RouterResult<(
    D,
    Req,
    Option<domain::Customer>,
    Option<u16>,
    Option<u128>,
    common_types::domain::ConnectorResponseData,
)>
where
    F: Send + Clone + Sync,
    Req: Send + Sync + Authenticate,
    Op: Operation<F, Req, Data = D> + Send + Sync,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,

    // To create connector flow specific interface data
    D: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
    RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,

    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,

    RouterData<F, FData, router_types::PaymentsResponseData>:
        hyperswitch_domain_models::router_data::TrackerPostUpdateObjects<F, FData, D>,

    // To perform router related operation for PaymentResponse
    PaymentResponse: Operation<F, FData, Data = D>,
    FData: Send + Sync + Clone,
{
    let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation);

    // Get the trackers related to track the state of the payment
    let operations::GetTrackerResponse { mut payment_data } = get_tracker_response;

    operation
        .to_domain()?
        .create_or_fetch_payment_method(state, &merchant_context, profile, &mut payment_data)
        .await?;

    let (_operation, customer) = operation
        .to_domain()?
        .get_customer_details(
            state,
            &mut payment_data,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)
        .attach_printable("Failed while fetching/creating customer")?;

    operation
        .to_domain()?
        .run_decision_manager(state, &mut payment_data, profile)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to run decision manager")?;

    let connector = operation
        .to_domain()?
        .perform_routing(&merchant_context, profile, state, &mut payment_data)
        .await?;

    let mut connector_http_status_code = None;
    let (payment_data, connector_response_data) = match connector {
        ConnectorCallType::PreDetermined(connector_data) => {
            let (mca_type_details, updated_customer, router_data) =
                call_connector_service_prerequisites(
                    state,
                    req_state.clone(),
                    &merchant_context,
                    connector_data.connector_data.clone(),
                    &operation,
                    &mut payment_data,
                    &customer,
                    call_connector_action.clone(),
                    None,
                    header_payload.clone(),
                    None,
                    profile,
                    false,
                    false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType
                    req.should_return_raw_response(),
                )
                .await?;

            let router_data = decide_unified_connector_service_call(
                state,
                req_state.clone(),
                &merchant_context,
                connector_data.connector_data.clone(),
                &operation,
                &mut payment_data,
                &customer,
                call_connector_action.clone(),
                None, // schedule_time is not used in PreDetermined ConnectorCallType
                header_payload.clone(),
                #[cfg(feature = "frm")]
                None,
                profile,
                false,
                false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType
                req.should_return_raw_response(),
                mca_type_details,
                router_data,
                updated_customer,
            )
            .await?;

            let connector_response_data = common_types::domain::ConnectorResponseData {
                raw_connector_response: router_data.raw_connector_response.clone(),
            };

            let payments_response_operation = Box::new(PaymentResponse);

            connector_http_status_code = router_data.connector_http_status_code;
            add_connector_http_status_code_metrics(connector_http_status_code);

            payments_response_operation
                .to_post_update_tracker()?
                .save_pm_and_mandate(
                    state,
                    &router_data,
                    &merchant_context,
                    &mut payment_data,
                    profile,
                )
                .await?;

            let payment_data = payments_response_operation
                .to_post_update_tracker()?
                .update_tracker(
                    state,
                    payment_data,
                    router_data,
                    merchant_context.get_merchant_key_store(),
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await?;

            (payment_data, connector_response_data)
        }
        ConnectorCallType::Retryable(connectors) => {
            let mut connectors = connectors.clone().into_iter();
            let connector_data = get_connector_data(&mut connectors)?;

            let (mca_type_details, updated_customer, router_data) =
                call_connector_service_prerequisites(
                    state,
                    req_state.clone(),
                    &merchant_context,
                    connector_data.connector_data.clone(),
                    &operation,
                    &mut payment_data,
                    &customer,
                    call_connector_action.clone(),
                    None,
                    header_payload.clone(),
                    None,
                    profile,
                    false,
                    false, //should_retry_with_pan is set to false in case of Retryable ConnectorCallType
                    req.should_return_raw_response(),
                )
                .await?;

            let router_data = decide_unified_connector_service_call(
                state,
                req_state.clone(),
                &merchant_context,
                connector_data.connector_data.clone(),
                &operation,
                &mut payment_data,
                &customer,
                call_connector_action.clone(),
                None, // schedule_time is not used in Retryable ConnectorCallType
                header_payload.clone(),
                #[cfg(feature = "frm")]
                None,
                profile,
                true,
                false, //should_retry_with_pan is set to false in case of PreDetermined ConnectorCallType
                req.should_return_raw_response(),
                mca_type_details,
                router_data,
                updated_customer,
            )
            .await?;

            let connector_response_data = common_types::domain::ConnectorResponseData {
                raw_connector_response: router_data.raw_connector_response.clone(),
            };

            let payments_response_operation = Box::new(PaymentResponse);

            connector_http_status_code = router_data.connector_http_status_code;
            add_connector_http_status_code_metrics(connector_http_status_code);

            payments_response_operation
                .to_post_update_tracker()?
                .save_pm_and_mandate(
                    state,
                    &router_data,
                    &merchant_context,
                    &mut payment_data,
                    profile,
                )
                .await?;

            let payment_data = payments_response_operation
                .to_post_update_tracker()?
                .update_tracker(
                    state,
                    payment_data,
                    router_data,
                    merchant_context.get_merchant_key_store(),
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await?;

            (payment_data, connector_response_data)
        }
        ConnectorCallType::SessionMultiple(vec) => todo!(),
        ConnectorCallType::Skip => (
            payment_data,
            common_types::domain::ConnectorResponseData {
                raw_connector_response: None,
            },
        ),
    };

    let payment_intent_status = payment_data.get_payment_intent().status;

    // Delete the tokens after payment
    payment_data
        .get_payment_attempt()
        .payment_token
        .as_ref()
        .zip(Some(payment_data.get_payment_attempt().payment_method_type))
        .map(ParentPaymentMethodToken::return_key_for_token)
        .async_map(|key_for_token| async move {
            let _ = vault::delete_payment_token(state, &key_for_token, payment_intent_status)
                .await
                .inspect_err(|err| logger::error!("Failed to delete payment_token: {:?}", err));
        })
        .await;

    Ok((
        payment_data,
        req,
        customer,
        connector_http_status_code,
        None,
        connector_response_data,
    ))
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[instrument(skip_all, fields(payment_id, merchant_id))]
pub async fn internal_payments_operation_core<F, Req, Op, FData, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile: &domain::Profile,
    operation: Op,
    req: Req,
    get_tracker_response: operations::GetTrackerResponse<D>,
    call_connector_action: CallConnectorAction,
    header_payload: HeaderPayload,
) -> RouterResult<(
    D,
    Req,
    Option<u16>,
    Option<u128>,
    common_types::domain::ConnectorResponseData,
)>
where
    F: Send + Clone + Sync,
    Req: Send + Sync + Authenticate,
    Op: Operation<F, Req, Data = D> + Send + Sync,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,

    // To create connector flow specific interface data
    D: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
    RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,

    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,

    RouterData<F, FData, router_types::PaymentsResponseData>:
        hyperswitch_domain_models::router_data::TrackerPostUpdateObjects<F, FData, D>,

    // To perform router related operation for PaymentResponse
    PaymentResponse: Operation<F, FData, Data = D>,
    FData: Send + Sync + Clone,
{
    let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation);

    // Get the trackers related to track the state of the payment
    let operations::GetTrackerResponse { mut payment_data } = get_tracker_response;

    let connector_data = operation
        .to_domain()?
        .get_connector_from_request(state, &req, &mut payment_data)
        .await?;

    let merchant_connector_account = payment_data
        .get_merchant_connector_details()
        .map(domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails)
        .ok_or_else(|| {
            error_stack::report!(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Merchant connector details not found in payment data")
        })?;

    operation
        .to_domain()?
        .populate_payment_data(
            state,
            &mut payment_data,
            &merchant_context,
            profile,
            &connector_data,
        )
        .await?;

    let router_data = connector_service_decider(
        state,
        req_state.clone(),
        &merchant_context,
        connector_data.clone(),
        &operation,
        &mut payment_data,
        call_connector_action.clone(),
        header_payload.clone(),
        profile,
        req.should_return_raw_response(),
        merchant_connector_account,
    )
    .await?;

    let connector_response_data = common_types::domain::ConnectorResponseData {
        raw_connector_response: router_data.raw_connector_response.clone(),
    };

    let payments_response_operation = Box::new(PaymentResponse);

    let connector_http_status_code = router_data.connector_http_status_code;
    add_connector_http_status_code_metrics(connector_http_status_code);

    let payment_data = payments_response_operation
        .to_post_update_tracker()?
        .update_tracker(
            state,
            payment_data,
            router_data,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

    Ok((
        payment_data,
        req,
        connector_http_status_code,
        None,
        connector_response_data,
    ))
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[instrument(skip_all, fields(payment_id, merchant_id))]
pub async fn payments_operation_core<F, Req, Op, FData, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: &domain::MerchantContext,
    profile_id_from_auth_layer: Option<id_type::ProfileId>,
    operation: Op,
    req: Req,
    call_connector_action: CallConnectorAction,
    auth_flow: services::AuthFlow,
    eligible_connectors: Option<Vec<common_enums::RoutableConnectors>>,
    header_payload: HeaderPayload,
) -> RouterResult<(D, Req, Option<domain::Customer>, Option<u16>, Option<u128>)>
where
    F: Send + Clone + Sync,
    Req: Authenticate + Clone,
    Op: Operation<F, Req, Data = D> + Send + Sync,
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,

    // To create connector flow specific interface data
    D: ConstructFlowSpecificData<F, FData, router_types::PaymentsResponseData>,
    RouterData<F, FData, router_types::PaymentsResponseData>: Feature<F, FData>,

    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, FData, router_types::PaymentsResponseData>,

    // To perform router related operation for PaymentResponse
    PaymentResponse: Operation<F, FData, Data = D>,
    FData: Send + Sync + Clone + router_types::Capturable,
{
    let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation);

    tracing::Span::current().record(
        "merchant_id",
        merchant_context
            .get_merchant_account()
            .get_id()
            .get_string_repr(),
    );
    let (operation, validate_result) = operation
        .to_validate_request()?
        .validate_request(&req, merchant_context)?;

    tracing::Span::current().record("payment_id", format!("{}", validate_result.payment_id));
    // get profile from headers
    let operations::GetTrackerResponse {
        operation,
        customer_details,
        mut payment_data,
        business_profile,
        mandate_type,
    } = operation
        .to_get_tracker()?
        .get_trackers(
            state,
            &validate_result.payment_id,
            &req,
            merchant_context,
            auth_flow,
            &header_payload,
        )
        .await?;

    operation
        .to_get_tracker()?
        .validate_request_with_state(state, &req, &mut payment_data, &business_profile)
        .await?;

    core_utils::validate_profile_id_from_auth_layer(
        profile_id_from_auth_layer,
        &payment_data.get_payment_intent().clone(),
    )?;

    let (operation, customer) = operation
        .to_domain()?
        // get_customer_details
        .get_or_create_customer_details(
            state,
            &mut payment_data,
            customer_details,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)
        .attach_printable("Failed while fetching/creating customer")?;

    let authentication_type =
        call_decision_manager(state, merchant_context, &business_profile, &payment_data).await?;

    payment_data.set_authentication_type_in_attempt(authentication_type);

    let connector = get_connector_choice(
        &operation,
        state,
        &req,
        merchant_context,
        &business_profile,
        &mut payment_data,
        eligible_connectors,
        mandate_type,
    )
    .await?;

    let payment_method_token = get_decrypted_wallet_payment_method_token(
        &operation,
        state,
        merchant_context,
        &mut payment_data,
        connector.as_ref(),
    )
    .await?;

    payment_method_token.map(|token| payment_data.set_payment_method_token(Some(token)));

    let (connector, debit_routing_output) = debit_routing::perform_debit_routing(
        &operation,
        state,
        &business_profile,
        &mut payment_data,
        connector,
    )
    .await;

    operation
        .to_domain()?
        .apply_three_ds_authentication_strategy(state, &mut payment_data, &business_profile)
        .await?;

    let should_add_task_to_process_tracker = should_add_task_to_process_tracker(&payment_data);

    let locale = header_payload.locale.clone();

    payment_data = tokenize_in_router_when_confirm_false_or_external_authentication(
        state,
        &operation,
        &mut payment_data,
        &validate_result,
        merchant_context.get_merchant_key_store(),
        &customer,
        &business_profile,
    )
    .await?;

    let mut connector_http_status_code = None;
    let mut external_latency = None;
    if let Some(connector_details) = connector {
        // Fetch and check FRM configs
        #[cfg(feature = "frm")]
        let mut frm_info = None;
        #[allow(unused_variables, unused_mut)]
        let mut should_continue_transaction: bool = true;
        #[cfg(feature = "frm")]
        let mut should_continue_capture: bool = true;
        #[cfg(feature = "frm")]
        let frm_configs = if state.conf.frm.enabled {
            Box::pin(frm_core::call_frm_before_connector_call(
                &operation,
                merchant_context,
                &mut payment_data,
                state,
                &mut frm_info,
                &customer,
                &mut should_continue_transaction,
                &mut should_continue_capture,
            ))
            .await?
        } else {
            None
        };
        #[cfg(feature = "frm")]
        logger::debug!(
            "frm_configs: {:?}\nshould_continue_transaction: {:?}\nshould_continue_capture: {:?}",
            frm_configs,
            should_continue_transaction,
            should_continue_capture,
        );

        let is_eligible_for_uas = helpers::is_merchant_eligible_authentication_service(
            merchant_context.get_merchant_account().get_id(),
            state,
        )
        .await?;

        if is_eligible_for_uas {
            operation
                .to_domain()?
                .call_unified_authentication_service_if_eligible(
                    state,
                    &mut payment_data,
                    &mut should_continue_transaction,
                    &connector_details,
                    &business_profile,
                    merchant_context.get_merchant_key_store(),
                    mandate_type,
                )
                .await?;
        } else {
            logger::info!(
                "skipping authentication service call since the merchant is not eligible."
            );

            operation
                .to_domain()?
                .call_external_three_ds_authentication_if_eligible(
                    state,
                    &mut payment_data,
                    &mut should_continue_transaction,
                    &connector_details,
                    &business_profile,
                    merchant_context.get_merchant_key_store(),
                    mandate_type,
                )
                .await?;
        };

        operation
            .to_domain()?
            .payments_dynamic_tax_calculation(
                state,
                &mut payment_data,
                &connector_details,
                &business_profile,
                merchant_context,
            )
            .await?;

        if should_continue_transaction {
            #[cfg(feature = "frm")]
            match (
                should_continue_capture,
                payment_data.get_payment_attempt().capture_method,
            ) {
                (
                    false,
                    Some(storage_enums::CaptureMethod::Automatic)
                    | Some(storage_enums::CaptureMethod::SequentialAutomatic),
                )
                | (false, Some(storage_enums::CaptureMethod::Scheduled)) => {
                    if let Some(info) = &mut frm_info {
                        if let Some(frm_data) = &mut info.frm_data {
                            frm_data.fraud_check.payment_capture_method =
                                payment_data.get_payment_attempt().capture_method;
                        }
                    }
                    payment_data
                        .set_capture_method_in_attempt(storage_enums::CaptureMethod::Manual);
                    logger::debug!("payment_id : {:?} capture method has been changed to manual, since it has configured Post FRM flow",payment_data.get_payment_attempt().payment_id);
                }
                _ => (),
            };

            payment_data = match connector_details {
                ConnectorCallType::PreDetermined(ref connector) => {
                    #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                    let routable_connectors = convert_connector_data_to_routable_connectors(
                        std::slice::from_ref(connector),
                    )
                    .map_err(|e| logger::error!(routable_connector_error=?e))
                    .unwrap_or_default();
                    let schedule_time = if should_add_task_to_process_tracker {
                        payment_sync::get_sync_process_schedule_time(
                            &*state.store,
                            connector.connector_data.connector.id(),
                            merchant_context.get_merchant_account().get_id(),
                            0,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed while getting process schedule time")?
                    } else {
                        None
                    };

                    let (merchant_connector_account, router_data, tokenization_action) =
                        call_connector_service_prerequisites(
                            state,
                            merchant_context,
                            connector.connector_data.clone(),
                            &operation,
                            &mut payment_data,
                            &customer,
                            &validate_result,
                            &business_profile,
                            false,
                            None,
                        )
                        .await?;

                    let (router_data, mca) = decide_unified_connector_service_call(
                        state,
                        req_state.clone(),
                        merchant_context,
                        connector.connector_data.clone(),
                        &operation,
                        &mut payment_data,
                        &customer,
                        call_connector_action.clone(),
                        &validate_result,
                        schedule_time,
                        header_payload.clone(),
                        #[cfg(feature = "frm")]
                        frm_info.as_ref().and_then(|fi| fi.suggested_action),
                        #[cfg(not(feature = "frm"))]
                        None,
                        &business_profile,
                        false,
                        <Req as Authenticate>::should_return_raw_response(&req),
                        merchant_connector_account,
                        router_data,
                        tokenization_action,
                    )
                    .await?;

                    let op_ref = &operation;
                    let should_trigger_post_processing_flows = is_operation_confirm(&operation);

                    let operation = Box::new(PaymentResponse);

                    connector_http_status_code = router_data.connector_http_status_code;
                    external_latency = router_data.external_latency;
                    //add connector http status code metrics
                    add_connector_http_status_code_metrics(connector_http_status_code);

                    operation
                        .to_post_update_tracker()?
                        .save_pm_and_mandate(
                            state,
                            &router_data,
                            merchant_context,
                            &mut payment_data,
                            &business_profile,
                        )
                        .await?;

                    let mut payment_data = operation
                        .to_post_update_tracker()?
                        .update_tracker(
                            state,
                            payment_data,
                            router_data,
                            merchant_context.get_merchant_key_store(),
                            merchant_context.get_merchant_account().storage_scheme,
                            &locale,
                            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                            routable_connectors,
                            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                            &business_profile,
                        )
                        .await?;

                    if should_trigger_post_processing_flows {
                        complete_postprocessing_steps_if_required(
                            state,
                            merchant_context,
                            &customer,
                            &mca,
                            &connector.connector_data,
                            &mut payment_data,
                            op_ref,
                            Some(header_payload.clone()),
                        )
                        .await?;
                    }

                    if is_eligible_for_uas {
                        complete_confirmation_for_click_to_pay_if_required(
                            state,
                            merchant_context,
                            &payment_data,
                        )
                        .await?;
                    }

                    payment_data
                }

                ConnectorCallType::Retryable(ref connectors) => {
                    #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                    let routable_connectors =
                        convert_connector_data_to_routable_connectors(connectors)
                            .map_err(|e| logger::error!(routable_connector_error=?e))
                            .unwrap_or_default();

                    let mut connectors = connectors.clone().into_iter();

                    let (connector_data, routing_decision) =
                        get_connector_data_with_routing_decision(
                            &mut connectors,
                            &business_profile,
                            debit_routing_output,
                        )?;

                    let schedule_time = if should_add_task_to_process_tracker {
                        payment_sync::get_sync_process_schedule_time(
                            &*state.store,
                            connector_data.connector.id(),
                            merchant_context.get_merchant_account().get_id(),
                            0,
                        )
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed while getting process schedule time")?
                    } else {
                        None
                    };

                    let (merchant_connector_account, router_data, tokenization_action) =
                        call_connector_service_prerequisites(
                            state,
                            merchant_context,
                            connector_data.clone(),
                            &operation,
                            &mut payment_data,
                            &customer,
                            &validate_result,
                            &business_profile,
                            false,
                            routing_decision,
                        )
                        .await?;

                    let (router_data, mca) = decide_unified_connector_service_call(
                        state,
                        req_state.clone(),
                        merchant_context,
                        connector_data.clone(),
                        &operation,
                        &mut payment_data,
                        &customer,
                        call_connector_action.clone(),
                        &validate_result,
                        schedule_time,
                        header_payload.clone(),
                        #[cfg(feature = "frm")]
                        frm_info.as_ref().and_then(|fi| fi.suggested_action),
                        #[cfg(not(feature = "frm"))]
                        None,
                        &business_profile,
                        false,
                        <Req as Authenticate>::should_return_raw_response(&req),
                        merchant_connector_account,
                        router_data,
                        tokenization_action,
                    )
                    .await?;

                    #[cfg(all(feature = "retry", feature = "v1"))]
                    let mut router_data = router_data;
                    #[cfg(all(feature = "retry", feature = "v1"))]
                    {
                        use crate::core::payments::retry::{self, GsmValidation};
                        let config_bool = retry::config_should_call_gsm(
                            &*state.store,
                            merchant_context.get_merchant_account().get_id(),
                            &business_profile,
                        )
                        .await;

                        if config_bool && router_data.should_call_gsm() {
                            router_data = retry::do_gsm_actions(
                                state,
                                req_state.clone(),
                                &mut payment_data,
                                connectors,
                                &connector_data,
                                router_data,
                                merchant_context,
                                &operation,
                                &customer,
                                &validate_result,
                                schedule_time,
                                #[cfg(feature = "frm")]
                                frm_info.as_ref().and_then(|fi| fi.suggested_action),
                                #[cfg(not(feature = "frm"))]
                                None,
                                &business_profile,
                            )
                            .await?;
                        };
                    }

                    let op_ref = &operation;
                    let should_trigger_post_processing_flows = is_operation_confirm(&operation);

                    let operation = Box::new(PaymentResponse);
                    connector_http_status_code = router_data.connector_http_status_code;
                    external_latency = router_data.external_latency;
                    //add connector http status code metrics
                    add_connector_http_status_code_metrics(connector_http_status_code);

                    operation
                        .to_post_update_tracker()?
                        .save_pm_and_mandate(
                            state,
                            &router_data,
                            merchant_context,
                            &mut payment_data,
                            &business_profile,
                        )
                        .await?;

                    let mut payment_data = operation
                        .to_post_update_tracker()?
                        .update_tracker(
                            state,
                            payment_data,
                            router_data,
                            merchant_context.get_merchant_key_store(),
                            merchant_context.get_merchant_account().storage_scheme,
                            &locale,
                            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                            routable_connectors,
                            #[cfg(all(feature = "dynamic_routing", feature = "v1"))]
                            &business_profile,
                        )
                        .await?;

                    if should_trigger_post_processing_flows {
                        complete_postprocessing_steps_if_required(
                            state,
                            merchant_context,
                            &customer,
                            &mca,
                            &connector_data,
                            &mut payment_data,
                            op_ref,
                            Some(header_payload.clone()),
                        )
                        .await?;
                    }

                    if is_eligible_for_uas {
                        complete_confirmation_for_click_to_pay_if_required(
                            state,
                            merchant_context,
                            &payment_data,
                        )
                        .await?;
                    }

                    payment_data
                }

                ConnectorCallType::SessionMultiple(connectors) => {
                    let session_surcharge_details =
                        call_surcharge_decision_management_for_session_flow(
                            state,
                            merchant_context,
                            &business_profile,
                            payment_data.get_payment_attempt(),
                            payment_data.get_payment_intent(),
                            payment_data.get_billing_address(),
                            &connectors,
                        )
                        .await?;
                    Box::pin(call_multiple_connectors_service(
                        state,
                        merchant_context,
                        connectors,
                        &operation,
                        payment_data,
                        &customer,
                        session_surcharge_details,
                        &business_profile,
                        header_payload.clone(),
                        <Req as Authenticate>::should_return_raw_response(&req),
                    ))
                    .await?
                }
            };

            #[cfg(feature = "frm")]
            if let Some(fraud_info) = &mut frm_info {
                #[cfg(feature = "v1")]
                Box::pin(frm_core::post_payment_frm_core(
                    state,
                    req_state,
                    merchant_context,
                    &mut payment_data,
                    fraud_info,
                    frm_configs
                        .clone()
                        .ok_or(errors::ApiErrorResponse::MissingRequiredField {
                            field_name: "frm_configs",
                        })
                        .attach_printable("Frm configs label not found")?,
                    &customer,
                    &mut should_continue_capture,
                ))
                .await?;
            }
        } else {
            (_, payment_data) = operation
                .to_update_tracker()?
                .update_trackers(
                    state,
                    req_state,
                    payment_data.clone(),
                    customer.clone(),
                    validate_result.storage_scheme,
                    None,
                    merchant_context.get_merchant_key_store(),
                    #[cfg(feature = "frm")]
                    frm_info.and_then(|info| info.suggested_action),
                    #[cfg(not(feature = "frm"))]
                    None,
                    header_payload.clone(),
                )
                .await?;
        }

        let payment_intent_status = payment_data.get_payment_intent().status;

        payment_data
            .get_payment_attempt()
            .payment_token
            .as_ref()
            .zip(payment_data.get_payment_attempt().payment_method)
            .map(ParentPaymentMethodToken::create_key_for_token)
            .async_map(|key_for_hyperswitch_token| async move {
                if key_for_hyperswitch_token
                    .should_delete_payment_method_token(payment_intent_status)
                {
                    let _ = key_for_hyperswitch_token.delete(state).await;
                }
            })
            .await;
    } else {
        (_, payment_data) = operation
            .to_update_tracker()?
            .update_trackers(
                state,
                req_state,
                payment_data.clone(),
                customer.clone(),
                validate_result.storage_scheme,
                None,
                merchant_context.get_merchant_key_store(),
                None,
                header_payload.clone(),
            )
            .await?;
    }

    let cloned_payment_data = payment_data.clone();
    let cloned_customer = customer.clone();

    #[cfg(feature = "v1")]
    operation
        .to_domain()?
        .store_extended_card_info_temporarily(
            state,
            payment_data.get_payment_intent().get_id(),
            &business_profile,
            payment_data.get_payment_method_data(),
        )
        .await?;

    utils::trigger_payments_webhook(
        merchant_context.clone(),
        business_profile,
        cloned_payment_data,
        cloned_customer,
        state,
        operation,
    )
    .await
    .map_err(|error| logger::warn!(payments_outgoing_webhook_error=?error))
    .ok();

    Ok((
        payment_data,
        req,
        customer,
        connector_http_status_code,
        external_latency,
    ))
}

#[cfg(feature = "v1")]
// This function is intended for use when the feature being implemented is not aligned with the
// core payment operations.
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
#[instrument(skip_all, fields(payment_id, merchant_id))]
pub async fn proxy_for_payments_operation_core<F, Req, Op, FData, D>(
    state: &SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile_id_from_auth_layer: Option<id_type::ProfileId>,
    operation: Op,
    req: Req,
    call_connector_action: CallConnectorAction,
    auth_flow: services::AuthFlow,
    header_payload: HeaderPayload,
    return_raw_connector_response: Option<bool>,


pub struct PaymentRequestBankRedirect {
    pub payment_method: TrustpayPaymentMethod,
    pub merchant_identification: MerchantIdentification,
    pub payment_information: BankPaymentInformation,
    pub callback_urls: CallbackURLs,
}


pub struct CardTypeIndicators {
    issuance_country_code: Option<Secret<String>>,
    is_durbin_regulated: Option<bool>,
    card_product_types: Secret<Vec<String>>,
}


pub async fn find_all_combined_kv_database<F, RFut, DFut, T>(
    redis_fut: RFut,
    database_call: F,
    limit: Option<i64>,
) -> error_stack::Result<Vec<T>, StorageError>
where
    T: UniqueConstraints,
    F: FnOnce() -> DFut,
    RFut:
        futures::Future<Output = error_stack::Result<Vec<T>, redis_interface::errors::RedisError>>,
    DFut: futures::Future<Output = error_stack::Result<Vec<T>, StorageError>>,
{
    let trunc = |v: &mut Vec<_>| {
        if let Some(l) = limit.and_then(|v| TryInto::try_into(v).ok()) {
            v.truncate(l);
        }
    };

    let limit_satisfies = |len: usize, limit: i64| {
        TryInto::try_into(limit)
            .ok()
            .map_or(true, |val: usize| len >= val)
    };

    let redis_output = redis_fut.await;
    match (redis_output, limit) {
        (Ok(mut kv_rows), Some(lim)) if limit_satisfies(kv_rows.len(), lim) => {
            trunc(&mut kv_rows);
            Ok(kv_rows)
        }
        (Ok(kv_rows), _) => database_call().await.map(|db_rows| {
            let mut res = union_vec(kv_rows, db_rows);
            trunc(&mut res);
            res
        }),
        (Err(redis_error), _) => match redis_error.current_context() {
            redis_interface::errors::RedisError::NotFound => {
                metrics::KV_MISS.add(1, &[]);
                database_call().await
            }
            // Keeping the key empty here since the error would never go here.
            _ => Err(redis_error.to_redis_failed_response("")),
        },
    }
}