            payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype.into()),
            connector: pa.as_ref().and_then(|p| p.connector.clone()),
            merchant_connector_id: pa.as_ref().and_then(|p| p.merchant_connector_id.clone()),
            customer: None,
            merchant_reference_id: pi.merchant_reference_id,
            connector_payment_id: pa.as_ref().and_then(|p| p.connector_payment_id.clone()),
            connector_response_reference_id: pa
                .as_ref()
                .and_then(|p| p.connector_response_reference_id.clone()),
            metadata: pi.metadata,
            description: pi.description.map(|val| val.get_string_repr().to_string()),
            authentication_type: pi.authentication_type,
            capture_method: Some(pi.capture_method),
            setup_future_usage: Some(pi.setup_future_usage),
            attempt_count: pi.attempt_count,
            error: pa
                .as_ref()
                .and_then(|p| p.error.as_ref())
                .map(api_models::payments::ErrorDetails::foreign_from),
            cancellation_reason: pa.as_ref().and_then(|p| p.cancellation_reason.clone()),
            order_details: None,
            return_url: pi.return_url,
            statement_descriptor: pi.statement_descriptor,
            allowed_payment_method_types: pi.allowed_payment_method_types,
            authorization_count: pi.authorization_count,
            modified_at: pa.as_ref().map(|p| p.modified_at),
        }
    }
}

#[cfg(feature = "v1")]
impl ForeignFrom<ephemeral_key::EphemeralKey> for api::ephemeral_key::EphemeralKeyCreateResponse {
    fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self {
        Self {
            customer_id: from.customer_id,
            created_at: from.created_at,
            expires: from.expires,
            secret: from.secret,
        }
    }
}

#[cfg(feature = "v1")]
pub fn bank_transfer_next_steps_check(
    payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::BankTransferNextStepsData>> {
    let bank_transfer_next_step = if let Some(diesel_models::enums::PaymentMethod::BankTransfer) =
        payment_attempt.payment_method
    {
        if payment_attempt.payment_method_type != Some(diesel_models::enums::PaymentMethodType::Pix)
        {
            let bank_transfer_next_steps: Option<api_models::payments::BankTransferNextStepsData> =
                payment_attempt
                    .connector_metadata
                    .map(|metadata| {
                        metadata
                            .parse_value("NextStepsRequirements")
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable(
                                "Failed to parse the Value to NextRequirements struct",
                            )
                    })
                    .transpose()?;
            bank_transfer_next_steps
        } else {
            None
        }
    } else {
        None
    };
    Ok(bank_transfer_next_step)
}

#[cfg(feature = "v1")]
pub fn voucher_next_steps_check(
    payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::VoucherNextStepData>> {
    let voucher_next_step = if let Some(diesel_models::enums::PaymentMethod::Voucher) =
        payment_attempt.payment_method
    {
        let voucher_next_steps: Option<api_models::payments::VoucherNextStepData> = payment_attempt
            .connector_metadata
            .map(|metadata| {
                metadata
                    .parse_value("NextStepsRequirements")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to parse the Value to NextRequirements struct")
            })
            .transpose()?;
        voucher_next_steps
    } else {
        None
    };
    Ok(voucher_next_step)
}

#[cfg(feature = "v1")]
pub fn mobile_payment_next_steps_check(
    payment_attempt: &storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::MobilePaymentNextStepData>> {
    let mobile_payment_next_step = if let Some(diesel_models::enums::PaymentMethod::MobilePayment) =
        payment_attempt.payment_method
    {
        let mobile_paymebnt_next_steps: Option<api_models::payments::MobilePaymentNextStepData> =
            payment_attempt
                .connector_metadata
                .clone()
                .map(|metadata| {
                    metadata
                        .parse_value("MobilePaymentNextStepData")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed to parse the Value to NextRequirements struct")
                })
                .transpose()?;
        mobile_paymebnt_next_steps
    } else {
        None
    };
    Ok(mobile_payment_next_step)
}

impl ForeignFrom<api_models::payments::QrCodeInformation> for api_models::payments::NextActionData {
    fn foreign_from(qr_info: api_models::payments::QrCodeInformation) -> Self {
        match qr_info {
            api_models::payments::QrCodeInformation::QrCodeUrl {
                image_data_url,
                qr_code_url,
                display_to_timestamp,
            } => Self::QrCodeInformation {
                image_data_url: Some(image_data_url),
                qr_code_url: Some(qr_code_url),
                display_to_timestamp,
                border_color: None,
                display_text: None,
            },
            api_models::payments::QrCodeInformation::QrDataUrl {
                image_data_url,
                display_to_timestamp,
            } => Self::QrCodeInformation {
                image_data_url: Some(image_data_url),
                display_to_timestamp,
                qr_code_url: None,
                border_color: None,
                display_text: None,
            },
            api_models::payments::QrCodeInformation::QrCodeImageUrl {
                qr_code_url,
                display_to_timestamp,
            } => Self::QrCodeInformation {
                qr_code_url: Some(qr_code_url),
                image_data_url: None,
                display_to_timestamp,
                border_color: None,
                display_text: None,
            },
            api_models::payments::QrCodeInformation::QrColorDataUrl {
                color_image_data_url,
                display_to_timestamp,
                border_color,
                display_text,
            } => Self::QrCodeInformation {
                qr_code_url: None,
                image_data_url: Some(color_image_data_url),
                display_to_timestamp,
                border_color,
                display_text,
            },
        }
    }
}

#[derive(Clone)]
pub struct PaymentAdditionalData<'a, F>
where
    F: Clone,
{
    router_base_url: String,
    connector_name: String,
    payment_data: PaymentData<F>,
    state: &'a SessionState,
    customer_data: &'a Option<domain::Customer>,
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthorizeData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data.clone();
        let router_base_url = &additional_data.router_base_url;
        let connector_name = &additional_data.connector_name;
        let attempt = &payment_data.payment_attempt;
        let browser_info: Option<types::BrowserInformation> = attempt
            .browser_info
            .clone()
            .map(types::BrowserInformation::from);

        let complete_authorize_url = Some(helpers::create_complete_authorize_url(
            router_base_url,
            attempt,
            connector_name,
            payment_data.creds_identifier.as_deref(),
        ));

        let merchant_connector_account_id_or_connector_name = payment_data
            .payment_attempt
            .merchant_connector_id
            .as_ref()
            .map(|mca_id| mca_id.get_string_repr())
            .unwrap_or(connector_name);

        let webhook_url = Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account_id_or_connector_name,
        ));
        let router_return_url = Some(helpers::create_redirect_url(
            router_base_url,
            attempt,
            connector_name,
            payment_data.creds_identifier.as_deref(),
        ));

        let payment_method_data = payment_data.payment_method_data.or_else(|| {
            if payment_data.mandate_id.is_some() {
                Some(domain::PaymentMethodData::MandatePayment)
            } else {
                None
            }
        });

        let amount = payment_data
            .payment_attempt
            .get_total_amount()
            .get_amount_as_i64();

        let customer_name = additional_data
            .customer_data
            .as_ref()
            .and_then(|customer_data| {
                customer_data
                    .name
                    .as_ref()
                    .map(|customer| customer.clone().into_inner())
            });

        let customer_id = additional_data
            .customer_data
            .as_ref()
            .and_then(|data| data.get_id().clone().try_into().ok());

        let merchant_order_reference_id = payment_data
            .payment_intent
            .merchant_reference_id
            .map(|s| s.get_string_repr().to_string());

        let shipping_cost = payment_data.payment_intent.amount_details.shipping_cost;

        Ok(Self {
            payment_method_data: payment_method_data
                .unwrap_or(domain::PaymentMethodData::Card(domain::Card::default())),
            amount,
            order_tax_amount: None, // V2 doesn't currently support order tax amount
            email: None,            // V2 doesn't store email directly in payment_intent
            customer_name,
            currency: payment_data.currency,
            confirm: true,
            statement_descriptor_suffix: None,
            statement_descriptor: None,
            capture_method: Some(payment_data.payment_intent.capture_method),
            router_return_url,
            webhook_url,
            complete_authorize_url,
            setup_future_usage: Some(payment_data.payment_intent.setup_future_usage),
            mandate_id: payment_data.mandate_id.clone(),
            off_session: get_off_session(payment_data.mandate_id.as_ref(), None),
            customer_acceptance: None,
            setup_mandate_details: None,
            browser_info,
            order_details: None,
            order_category: None,
            session_token: None,
            enrolled_for_3ds: false,
            related_transaction_id: None,
            payment_experience: None,
            payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype),
            surcharge_details: None,
            customer_id,
            request_incremental_authorization: false,
            metadata: payment_data
                .payment_intent
                .metadata
                .clone()
                .map(|m| m.expose()),
            authentication_data: None,
            request_extended_authorization: None,
            split_payments: None,
            minor_amount: payment_data.payment_attempt.get_total_amount(),
            merchant_order_reference_id,
            integrity_object: None,
            shipping_cost,
            additional_payment_method_data: None,
            merchant_account_id: None,
            merchant_config_currency: None,
            connector_testing_data: None,
            order_id: None,
            locale: None,
            payment_channel: None,
            enable_partial_authorization: None,
            enable_overcapture: None,
            is_stored_credential: None,
        })
    }
}

fn get_off_session(
    mandate_id: Option<&MandateIds>,
    off_session_flag: Option<bool>,
) -> Option<bool> {
    match (mandate_id, off_session_flag) {
        (_, Some(false)) => Some(false),
        (Some(_), _) | (_, Some(true)) => Some(true),
        (None, None) => None,
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthorizeData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data.clone();
        let router_base_url = &additional_data.router_base_url;
        let connector_name = &additional_data.connector_name;
        let attempt = &payment_data.payment_attempt;
        let browser_info: Option<types::BrowserInformation> = attempt
            .browser_info
            .clone()
            .map(|b| b.parse_value("BrowserInformation"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "browser_info",
            })?;

        let connector_metadata = additional_data
            .payment_data
            .payment_intent
            .connector_metadata
            .clone()
            .map(|cm| {
                cm.parse_value::<api_models::payments::ConnectorMetadata>("ConnectorMetadata")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed parsing ConnectorMetadata")
            })
            .transpose()?;

        let order_category = connector_metadata.as_ref().and_then(|cm| {
            cm.noon
                .as_ref()
                .and_then(|noon| noon.order_category.clone())
        });

        let braintree_metadata = connector_metadata
            .as_ref()
            .and_then(|cm| cm.braintree.clone());

        let merchant_account_id = braintree_metadata
            .as_ref()
            .and_then(|braintree| braintree.merchant_account_id.clone());
        let merchant_config_currency =
            braintree_metadata.and_then(|braintree| braintree.merchant_config_currency);

        let order_details = additional_data
            .payment_data
            .payment_intent
            .order_details
            .map(|order_details| {
                order_details
                    .iter()
                    .map(|data| {
                        data.to_owned()
                            .parse_value("OrderDetailsWithAmount")
                            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                                field_name: "OrderDetailsWithAmount",
                            })
                            .attach_printable("Unable to parse OrderDetailsWithAmount")
                    })
                    .collect::<Result<Vec<_>, _>>()
            })
            .transpose()?;

        let complete_authorize_url = Some(helpers::create_complete_authorize_url(
            router_base_url,
            attempt,
            connector_name,
            payment_data.creds_identifier.as_deref(),
        ));

        let merchant_connector_account_id_or_connector_name = payment_data
            .payment_attempt
            .merchant_connector_id
            .as_ref()
            .map(|mca_id| mca_id.get_string_repr())
            .unwrap_or(connector_name);

        let webhook_url = Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account_id_or_connector_name,
        ));
        let router_return_url = Some(helpers::create_redirect_url(
            router_base_url,
            attempt,
            connector_name,
            payment_data.creds_identifier.as_deref(),
        ));

        let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
            payment_data.payment_attempt
                .payment_method_data
                .as_ref().map(|data| data.clone().parse_value("AdditionalPaymentData"))
                .transpose()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to parse AdditionalPaymentData from payment_data.payment_attempt.payment_method_data")?;

        let payment_method_data = payment_data.payment_method_data.or_else(|| {
            if payment_data.mandate_id.is_some() {
                Some(domain::PaymentMethodData::MandatePayment)
            } else {
                None
            }
        });

        let amount = payment_data.payment_attempt.get_total_amount();

        let customer_name = additional_data
            .customer_data
            .as_ref()
            .and_then(|customer_data| {
                customer_data
                    .name
                    .as_ref()
                    .map(|customer| customer.clone().into_inner())
            });

        let customer_id = additional_data
            .customer_data
            .as_ref()
            .map(|data| data.customer_id.clone());

        let split_payments = payment_data.payment_intent.split_payments.clone();

        let merchant_order_reference_id = payment_data
            .payment_intent
            .merchant_order_reference_id
            .clone();
        let shipping_cost = payment_data.payment_intent.shipping_cost;

        let connector = api_models::enums::Connector::from_str(connector_name)
            .change_context(errors::ConnectorError::InvalidConnectorName)
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "connector",
            })
            .attach_printable_lazy(|| {
                format!("unable to parse connector name {connector_name:?}")
            })?;

        let connector_testing_data = connector_metadata
            .and_then(|cm| match connector {
                api_models::enums::Connector::Adyen => cm
                    .adyen
                    .map(|adyen_cm| adyen_cm.testing)
                    .map(|testing_data| {
                        serde_json::to_value(testing_data)
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Failed to parse Adyen testing data")
                    }),
                _ => None,
            })
            .transpose()?
            .map(pii::SecretSerdeValue::new);
        let is_off_session = get_off_session(
            payment_data.mandate_id.as_ref(),
            payment_data.payment_intent.off_session,
        );

        Ok(Self {
            payment_method_data: (payment_method_data.get_required_value("payment_method_data")?),
            setup_future_usage: payment_data.payment_attempt.setup_future_usage_applied,
            mandate_id: payment_data.mandate_id.clone(),
            off_session: is_off_session,
            setup_mandate_details: payment_data.setup_mandate.clone(),
            confirm: payment_data.payment_attempt.confirm,
            statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix,
            statement_descriptor: payment_data.payment_intent.statement_descriptor_name,
            capture_method: payment_data.payment_attempt.capture_method,
            amount: amount.get_amount_as_i64(),
            order_tax_amount: payment_data
                .payment_attempt
                .net_amount
                .get_order_tax_amount(),
            minor_amount: amount,
            currency: payment_data.currency,
            browser_info,
            email: payment_data.email,
            customer_name,
            payment_experience: payment_data.payment_attempt.payment_experience,
            order_details,
            order_category,
            session_token: None,
            enrolled_for_3ds: true,
            related_transaction_id: None,
            payment_method_type: payment_data.payment_attempt.payment_method_type,
            router_return_url,
            webhook_url,
            complete_authorize_url,
            customer_id,
            surcharge_details: payment_data.surcharge_details,
            request_incremental_authorization: matches!(
                payment_data
                    .payment_intent
                    .request_incremental_authorization,
                Some(RequestIncrementalAuthorization::True)
            ),
            metadata: additional_data.payment_data.payment_intent.metadata,
            authentication_data: payment_data
                .authentication
                .as_ref()
                .map(AuthenticationData::foreign_try_from)
                .transpose()?,
            customer_acceptance: payment_data.customer_acceptance,
            request_extended_authorization: attempt.request_extended_authorization,
            split_payments,
            merchant_order_reference_id,
            integrity_object: None,
            additional_payment_method_data,
            shipping_cost,
            merchant_account_id,
            merchant_config_currency,
            connector_testing_data,
            order_id: None,
            locale: Some(additional_data.state.locale.clone()),
            payment_channel: payment_data.payment_intent.payment_channel,
            enable_partial_authorization: payment_data.payment_intent.enable_partial_authorization,
            enable_overcapture: payment_data.payment_intent.enable_overcapture,
            is_stored_credential: payment_data.payment_attempt.is_stored_credential,
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsSyncData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let capture_method = payment_data.get_capture_method();
        let amount = payment_data.payment_attempt.get_total_amount();

        let payment_method_type = payment_data
            .payment_attempt
            .get_payment_method_type()
            .to_owned();
        Ok(Self {
            amount,
            integrity_object: None,
            mandate_id: payment_data.mandate_id.clone(),
            connector_transaction_id: match payment_data.payment_attempt.get_connector_payment_id()
            {
                Some(connector_txn_id) => {
                    types::ResponseId::ConnectorTransactionId(connector_txn_id.to_owned())
                }
                None => types::ResponseId::NoResponseId,
            },
            encoded_data: payment_data.payment_attempt.encoded_data,
            capture_method,
            connector_meta: payment_data.payment_attempt.connector_metadata,
            sync_type: match payment_data.multiple_capture_data {
                Some(multiple_capture_data) => types::SyncRequestType::MultipleCaptureSync(
                    multiple_capture_data.get_pending_connector_capture_ids(),
                ),
                None => types::SyncRequestType::SinglePaymentSync,
            },
            payment_method_type,
            currency: payment_data.currency,
            split_payments: payment_data.payment_intent.split_payments,
            payment_experience: payment_data.payment_attempt.payment_experience,
            connector_reference_id: payment_data
                .payment_attempt
                .connector_response_reference_id
                .clone(),
            setup_future_usage: payment_data.payment_intent.setup_future_usage,
        })
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>>
    for types::PaymentsIncrementalAuthorizationData
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let payment_attempt = &payment_data.payment_attempt;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_attempt.merchant_connector_id.clone(),
        )?;
        let incremental_details = payment_data
            .incremental_authorization_details
            .as_ref()
            .ok_or(
                report!(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("missing incremental_authorization_details in payment_data"),
            )?;
        Ok(Self {
            total_amount: incremental_details.total_amount.get_amount_as_i64(),
            additional_amount: incremental_details.additional_amount.get_amount_as_i64(),
            reason: incremental_details.reason.clone(),
            currency: payment_data.currency,
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            connector_meta: payment_attempt.connector_metadata.clone(),
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>>
    for types::PaymentsIncrementalAuthorizationData
{
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let incremental_details = payment_data
            .incremental_authorization_details
            .as_ref()
            .ok_or(
                report!(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("missing incremental_authorization_details in payment_data"),
            )?;
        Ok(Self {
            total_amount: incremental_details.total_amount.get_amount_as_i64(),
            additional_amount: incremental_details.additional_amount.get_amount_as_i64(),
            reason: incremental_details.reason.clone(),
            currency: payment_data.currency,
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            connector_meta: payment_data
                .payment_attempt
                .connector_metadata
                .map(|secret| secret.expose()),
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        use masking::ExposeOptionInterface;

        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let amount_to_capture = payment_data
            .payment_attempt
            .amount_details
            .get_amount_to_capture()
            .unwrap_or(payment_data.payment_attempt.get_total_amount());

        let amount = payment_data.payment_attempt.get_total_amount();
        Ok(Self {
            capture_method: Some(payment_data.payment_intent.capture_method),
            amount_to_capture: amount_to_capture.get_amount_as_i64(), // This should be removed once we start moving to connector module
            minor_amount_to_capture: amount_to_capture,
            currency: payment_data.currency,
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module
            minor_payment_amount: amount,
            connector_meta: payment_data
                .payment_attempt
                .connector_metadata
                .expose_option(),
            // TODO: add multiple capture data
            multiple_capture_data: None,
            // TODO: why do we need browser info during capture?
            browser_info: None,
            metadata: payment_data.payment_intent.metadata.expose_option(),
            integrity_object: None,
            split_payments: None,
            webhook_url: None,
        })
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCaptureData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let amount_to_capture = payment_data
            .payment_attempt
            .amount_to_capture
            .unwrap_or(payment_data.payment_attempt.get_total_amount());
        let browser_info: Option<types::BrowserInformation> = payment_data
            .payment_attempt
            .browser_info
            .clone()
            .map(|b| b.parse_value("BrowserInformation"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "browser_info",
            })?;
        let amount = payment_data.payment_attempt.get_total_amount();

        let router_base_url = &additional_data.router_base_url;
        let attempt = &payment_data.payment_attempt;

        let merchant_connector_account_id = payment_data
            .payment_attempt
            .merchant_connector_id
            .as_ref()
            .map(|mca_id| mca_id.get_string_repr())
            .ok_or(errors::ApiErrorResponse::MerchantAccountNotFound)?;
        let webhook_url: Option<_> = Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account_id,
        ));
        Ok(Self {
            capture_method: payment_data.get_capture_method(),
            amount_to_capture: amount_to_capture.get_amount_as_i64(), // This should be removed once we start moving to connector module
            minor_amount_to_capture: amount_to_capture,
            currency: payment_data.currency,
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module
            minor_payment_amount: amount,
            connector_meta: payment_data.payment_attempt.connector_metadata,
            multiple_capture_data: match payment_data.multiple_capture_data {
                Some(multiple_capture_data) => Some(MultipleCaptureRequestData {
                    capture_sequence: multiple_capture_data.get_captures_count()?,
                    capture_reference: multiple_capture_data
                        .get_latest_capture()
                        .capture_id
                        .clone(),
                }),
                None => None,
            },
            browser_info,
            metadata: payment_data.payment_intent.metadata,
            integrity_object: None,
            split_payments: payment_data.payment_intent.split_payments,
            webhook_url,
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let browser_info: Option<types::BrowserInformation> = payment_data
            .payment_attempt
            .browser_info
            .clone()
            .map(types::BrowserInformation::from);

        let amount = payment_data.payment_attempt.amount_details.get_net_amount();

        let router_base_url = &additional_data.router_base_url;
        let attempt = &payment_data.payment_attempt;

        let merchant_connector_account_id = payment_data
            .payment_attempt
            .merchant_connector_id
            .as_ref()
            .map(|mca_id| mca_id.get_string_repr())
            .ok_or(errors::ApiErrorResponse::MerchantAccountNotFound)?;
        let webhook_url: Option<_> = Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account_id,
        ));
        let capture_method = payment_data.payment_intent.capture_method;
        Ok(Self {
            amount: Some(amount.get_amount_as_i64()), // This should be removed once we start moving to connector module
            minor_amount: Some(amount),
            currency: Some(payment_data.payment_intent.amount_details.currency),
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            cancellation_reason: payment_data.payment_attempt.cancellation_reason,
            connector_meta: payment_data
                .payment_attempt
                .connector_metadata
                .clone()
                .expose_option(),
            browser_info,
            metadata: payment_data.payment_intent.metadata.expose_option(),
            webhook_url,
            capture_method: Some(capture_method),
        })
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let browser_info: Option<types::BrowserInformation> = payment_data
            .payment_attempt
            .browser_info
            .clone()
            .map(|b| b.parse_value("BrowserInformation"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "browser_info",
            })?;
        let amount = payment_data.payment_attempt.get_total_amount();

        let router_base_url = &additional_data.router_base_url;
        let attempt = &payment_data.payment_attempt;

        let merchant_connector_account_id = payment_data
            .payment_attempt
            .merchant_connector_id
            .as_ref()
            .map(|mca_id| mca_id.get_string_repr())
            .ok_or(errors::ApiErrorResponse::MerchantAccountNotFound)?;
        let webhook_url: Option<_> = Some(helpers::create_webhook_url(
            router_base_url,
            &attempt.merchant_id,
            merchant_connector_account_id,
        ));
        let capture_method = payment_data.payment_attempt.capture_method;
        Ok(Self {
            amount: Some(amount.get_amount_as_i64()), // This should be removed once we start moving to connector module
            minor_amount: Some(amount),
            currency: Some(payment_data.currency),
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            cancellation_reason: payment_data.payment_attempt.cancellation_reason,
            connector_meta: payment_data.payment_attempt.connector_metadata,
            browser_info,
            metadata: payment_data.payment_intent.metadata,
            webhook_url,
            capture_method,
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelPostCaptureData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsCancelPostCaptureData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let connector = api::ConnectorData::get_connector_by_name(
            &additional_data.state.conf.connectors,
            &additional_data.connector_name,
            api::GetToken::Connector,
            payment_data.payment_attempt.merchant_connector_id.clone(),
        )?;
        let amount = payment_data.payment_attempt.get_total_amount();

        Ok(Self {
            minor_amount: Some(amount),
            currency: Some(payment_data.currency),
            connector_transaction_id: connector
                .connector
                .connector_transaction_id(&payment_data.payment_attempt)?
                .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?,
            cancellation_reason: payment_data.payment_attempt.cancellation_reason,
            connector_meta: payment_data.payment_attempt.connector_metadata,
        })
    }
}

impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsApproveData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let amount = payment_data.payment_attempt.get_total_amount();
        Ok(Self {
            amount: Some(amount.get_amount_as_i64()), //need to change after we move to connector module
            currency: Some(payment_data.currency),
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::SdkPaymentsSessionUpdateData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::SdkPaymentsSessionUpdateData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;
    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data;
        let order_tax_amount = payment_data
            .payment_intent
            .tax_details
            .clone()
            .and_then(|tax| tax.payment_method_type.map(|pmt| pmt.order_tax_amount))
            .ok_or(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "order_tax_amount",
            })?;
        let surcharge_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.get_total_surcharge_amount())
            .unwrap_or_default();
        let shipping_cost = payment_data
            .payment_intent
            .shipping_cost
            .unwrap_or_default();
        // net_amount here would include amount, order_tax_amount, surcharge_amount and shipping_cost
        let net_amount = payment_data.payment_intent.amount
            + order_tax_amount
            + shipping_cost
            + surcharge_amount;
        Ok(Self {
            amount: net_amount,
            order_tax_amount,
            currency: payment_data.currency,
            order_amount: payment_data.payment_intent.amount,
            session_id: payment_data.session_id,
            shipping_cost: payment_data.payment_intent.shipping_cost,
        })
    }
}

#[cfg(feature = "v2")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPostSessionTokensData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        todo!()
    }
}

#[cfg(feature = "v1")]
impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsPostSessionTokensData {
    type Error = error_stack::Report<errors::ApiErrorResponse>;

    fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result<Self, Self::Error> {
        let payment_data = additional_data.payment_data.clone();
        let surcharge_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.get_total_surcharge_amount())
            .unwrap_or_default();


// Struct: BreadpayAuthType
// File: crates/hyperswitch_connectors/src/connectors/breadpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct BreadpayAuthType


// Struct: RedirectRequest
// File: crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RedirectRequest


// Module Structure
// File: crates/router/src/services/authorization/roles.rs
// Module: router

// Public submodules:
pub mod predefined_roles;


// Implementation: impl api::Refund for for Volt
// File: crates/hyperswitch_connectors/src/connectors/volt.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Refund for for Volt


// Struct: PublishableKeyAndProfileIdAuth
// File: crates/router/src/services/authentication.rs
// Module: router
// Implementations: 0

pub struct PublishableKeyAndProfileIdAuth


// Struct: AirwallexAuthUpdateResponse
// File: crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AirwallexAuthUpdateResponse


// File: crates/router/src/core/payments/operations/payment_session_intent.rs
// Module: router
// Public structs: 1

use std::{collections::HashMap, marker::PhantomData};

use api_models::payments::PaymentsSessionRequest;
use async_trait::async_trait;
use common_utils::{errors::CustomResult, ext_traits::Encode};
use error_stack::ResultExt;
use hyperswitch_domain_models::customer;
use router_env::{instrument, logger, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payments::{self, helpers, operations, operations::ValidateStatusForOperation},
    },
    routes::{app::ReqState, SessionState},
    types::{api, domain, storage, storage::enums},
    utils::ext_traits::OptionExt,
};

#[derive(Debug, Clone, Copy)]
pub struct PaymentSessionIntent;

type PaymentSessionOperation<'b, F> =
    BoxedOperation<'b, F, PaymentsSessionRequest, payments::PaymentIntentData<F>>;

impl ValidateStatusForOperation for PaymentSessionIntent {
    /// Validate if the current operation can be performed on the current status of the payment intent
    fn validate_status_for_operation(
        &self,
        intent_status: common_enums::IntentStatus,
    ) -> Result<(), errors::ApiErrorResponse> {
        match intent_status {
            common_enums::IntentStatus::RequiresPaymentMethod => Ok(()),
            common_enums::IntentStatus::Cancelled
            | common_enums::IntentStatus::CancelledPostCapture
            | common_enums::IntentStatus::Processing
            | common_enums::IntentStatus::RequiresCustomerAction
            | common_enums::IntentStatus::RequiresMerchantAction
            | common_enums::IntentStatus::RequiresCapture
            | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture
            | common_enums::IntentStatus::PartiallyCaptured
            | common_enums::IntentStatus::RequiresConfirmation
            | common_enums::IntentStatus::PartiallyCapturedAndCapturable
            | common_enums::IntentStatus::Succeeded
            | common_enums::IntentStatus::Failed | common_enums::IntentStatus::Conflicted | common_enums::IntentStatus::Expired => {
                Err(errors::ApiErrorResponse::PreconditionFailed {
                    message: format!(
                        "You cannot create session token for this payment because it has status {intent_status}. Expected status is requires_payment_method.",
                    ),
                })
            }
        }
    }
}

impl<F: Send + Clone + Sync> Operation<F, PaymentsSessionRequest> for &PaymentSessionIntent {
    type Data = payments::PaymentIntentData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<&(dyn ValidateRequest<F, PaymentsSessionRequest, Self::Data> + Send + Sync)>
    {
        Ok(*self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsSessionRequest> + Send + Sync)> {
        Ok(*self)
    }
    fn to_domain(&self) -> RouterResult<&(dyn Domain<F, PaymentsSessionRequest, Self::Data>)> {
        Ok(*self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<
        &(dyn UpdateTracker<F, payments::PaymentIntentData<F>, PaymentsSessionRequest>
              + Send
              + Sync),
    > {
        Ok(*self)
    }
}

impl<F: Send + Clone + Sync> Operation<F, PaymentsSessionRequest> for PaymentSessionIntent {
    type Data = payments::PaymentIntentData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<&(dyn ValidateRequest<F, PaymentsSessionRequest, Self::Data> + Send + Sync)>
    {
        Ok(self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsSessionRequest> + Send + Sync)> {
        Ok(self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, PaymentsSessionRequest, Self::Data>> {
        Ok(self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<
        &(dyn UpdateTracker<F, payments::PaymentIntentData<F>, PaymentsSessionRequest>
              + Send
              + Sync),
    > {
        Ok(self)
    }
}

type PaymentsCreateIntentOperation<'b, F> =
    BoxedOperation<'b, F, PaymentsSessionRequest, payments::PaymentIntentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, payments::PaymentIntentData<F>, PaymentsSessionRequest>
    for PaymentSessionIntent
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &common_utils::id_type::GlobalPaymentId,
        _request: &PaymentsSessionRequest,
        merchant_context: &domain::MerchantContext,
        _profile: &domain::Profile,
        header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<operations::GetTrackerResponse<payments::PaymentIntentData<F>>> {
        let db = &*state.store;
        let key_manager_state = &state.into();
        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;

        let payment_intent = db
            .find_payment_intent_by_id(
                key_manager_state,
                payment_id,
                merchant_context.get_merchant_key_store(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        // TODO (#7195): Add platform merchant account validation once publishable key auth is solved

        self.validate_status_for_operation(payment_intent.status)?;

        let payment_data = payments::PaymentIntentData {
            flow: PhantomData,
            payment_intent,
            client_secret: None,
            sessions_token: vec![],
            vault_session_details: None,
            connector_customer_id: None,
        };

        let get_trackers_response = operations::GetTrackerResponse { payment_data };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, payments::PaymentIntentData<F>, PaymentsSessionRequest>
    for PaymentSessionIntent
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        _req_state: ReqState,
        mut payment_data: payments::PaymentIntentData<F>,
        customer: Option<domain::Customer>,
        storage_scheme: enums::MerchantStorageScheme,
        updated_customer: Option<customer::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<common_enums::FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(
        PaymentSessionOperation<'b, F>,
        payments::PaymentIntentData<F>,
    )>
    where
        F: 'b + Send,
    {
        let prerouting_algorithm = payment_data.payment_intent.prerouting_algorithm.clone();
        payment_data.payment_intent = match prerouting_algorithm {
            Some(prerouting_algorithm) => state
                .store
                .update_payment_intent(
                    &state.into(),
                    payment_data.payment_intent,
                    storage::PaymentIntentUpdate::SessionIntentUpdate {
                        prerouting_algorithm,
                        updated_by: storage_scheme.to_string(),
                    },
                    key_store,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?,
            None => payment_data.payment_intent,
        };

        Ok((Box::new(self), payment_data))
    }
}

impl<F: Send + Clone> ValidateRequest<F, PaymentsSessionRequest, payments::PaymentIntentData<F>>
    for PaymentSessionIntent
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        _request: &PaymentsSessionRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<operations::ValidateResult> {
        Ok(operations::ValidateResult {
            merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
            storage_scheme: merchant_context.get_merchant_account().storage_scheme,
            requeue: false,
        })
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, PaymentsSessionRequest, payments::PaymentIntentData<F>>
    for PaymentSessionIntent
{
    #[instrument(skip_all)]
    async fn get_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut payments::PaymentIntentData<F>,
        merchant_key_store: &domain::MerchantKeyStore,
        storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<
        (
            BoxedOperation<'a, F, PaymentsSessionRequest, payments::PaymentIntentData<F>>,
            Option<domain::Customer>,
        ),
        errors::StorageError,
    > {
        match payment_data.payment_intent.customer_id.clone() {
            Some(id) => {
                let customer = state
                    .store
                    .find_customer_by_global_id(
                        &state.into(),
                        &id,
                        merchant_key_store,
                        storage_scheme,
                    )
                    .await?;
                Ok((Box::new(self), Some(customer)))
            }
            None => Ok((Box::new(self), None)),
        }
    }

    #[instrument(skip_all)]
    async fn make_pm_data<'a>(
        &'a self,
        _state: &'a SessionState,
        _payment_data: &mut payments::PaymentIntentData<F>,
        _storage_scheme: enums::MerchantStorageScheme,
        _merchant_key_store: &domain::MerchantKeyStore,
        _customer: &Option<domain::Customer>,
        _business_profile: &domain::Profile,
        _should_retry_with_pan: bool,
    ) -> RouterResult<(
        PaymentsCreateIntentOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Ok((Box::new(self), None, None))
    }

    async fn perform_routing<'a>(
        &'a self,
        merchant_context: &domain::MerchantContext,
        business_profile: &domain::Profile,
        state: &SessionState,
        payment_data: &mut payments::PaymentIntentData<F>,
    ) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
        let db = &state.store;
        let all_connector_accounts = db
            .find_merchant_connector_account_by_merchant_id_and_disabled_list(
                &state.into(),
                merchant_context.get_merchant_account().get_id(),
                false,
                merchant_context.get_merchant_key_store(),
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Database error when querying for merchant connector accounts")?;
        let profile_id = business_profile.get_id();
        let filtered_connector_accounts = all_connector_accounts
            .filter_based_on_profile_and_connector_type(
                profile_id,
                common_enums::ConnectorType::PaymentProcessor,
            );
        let connector_and_supporting_payment_method_type = filtered_connector_accounts
            .get_connector_and_supporting_payment_method_type_for_session_call();

        let session_connector_data: api::SessionConnectorDatas =
            connector_and_supporting_payment_method_type
                .into_iter()
                .filter_map(
                    |(merchant_connector_account, payment_method_type, payment_method)| {
                        let connector_type = api::GetToken::from(payment_method_type);

                        match api::ConnectorData::get_connector_by_name(
                            &state.conf.connectors,
                            &merchant_connector_account.connector_name.to_string(),
                            connector_type,
                            Some(merchant_connector_account.get_id()),
                        ) {
                            Ok(connector_data) => Some(api::SessionConnectorData::new(
                                payment_method_type,
                                connector_data,
                                None,
                                payment_method,
                            )),
                            Err(err) => {
                                logger::error!(session_token_error=?err);
                                None
                            }
                        }
                    },
                )
                .collect();
        let session_token_routing_result = payments::perform_session_token_routing(
            state.clone(),
            business_profile,
            merchant_context.clone(),
            payment_data,
            session_connector_data,
        )
        .await?;

        let pre_routing = storage::PaymentRoutingInfo {
            algorithm: None,
            pre_routing_results: Some((|| {
                let mut pre_routing_results: HashMap<
                    common_enums::PaymentMethodType,
                    storage::PreRoutingConnectorChoice,
                > = HashMap::new();
                for (pm_type, routing_choice) in session_token_routing_result.routing_result {
                    let mut routable_choice_list = vec![];
                    for choice in routing_choice {
                        let routable_choice = api::routing::RoutableConnectorChoice {
                            choice_kind: api::routing::RoutableChoiceKind::FullStruct,
                            connector: choice
                                .connector
                                .connector_name
                                .to_string()
                                .parse::<common_enums::RoutableConnectors>()
                                .change_context(errors::ApiErrorResponse::InternalServerError)?,
                            merchant_connector_id: choice.connector.merchant_connector_id.clone(),
                        };
                        routable_choice_list.push(routable_choice);
                    }
                    pre_routing_results.insert(
                        pm_type,
                        storage::PreRoutingConnectorChoice::Multiple(routable_choice_list),
                    );
                }
                Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(pre_routing_results)
            })()?),
        };

        // Store the routing results in payment intent
        payment_data.payment_intent.prerouting_algorithm = Some(pre_routing);

        Ok(api::ConnectorCallType::SessionMultiple(
            session_token_routing_result.final_result,
        ))
    }

    #[instrument(skip_all)]
    async fn guard_payment_against_blocklist<'a>(
        &'a self,
        _state: &SessionState,
        _merchant_context: &domain::MerchantContext,
        _payment_data: &mut payments::PaymentIntentData<F>,
    ) -> CustomResult<bool, errors::ApiErrorResponse> {
        Ok(false)
    }
}

impl From<api_models::enums::PaymentMethodType> for api::GetToken {
    fn from(value: api_models::enums::PaymentMethodType) -> Self {
        match value {
            api_models::enums::PaymentMethodType::GooglePay => Self::GpayMetadata,
            api_models::enums::PaymentMethodType::ApplePay => Self::ApplePayMetadata,
            api_models::enums::PaymentMethodType::SamsungPay => Self::SamsungPayMetadata,
            api_models::enums::PaymentMethodType::Paypal => Self::PaypalSdkMetadata,
            api_models::enums::PaymentMethodType::Paze => Self::PazeMetadata,
            api_models::enums::PaymentMethodType::AmazonPay => Self::AmazonPayMetadata,
            _ => Self::Connector,
        }
    }
}


// Struct: MerchantConnectorDetails
// File: crates/api_models/src/admin.rs
// Module: api_models
// Implementations: 0

pub struct MerchantConnectorDetails


// Function: into_values
// File: crates/hyperswitch_constraint_graph/src/dense_map.rs
// Module: hyperswitch_constraint_graph

pub fn into_values(self) -> vec::IntoIter<V>


// Function: find_optional_by_merchant_id_payout_id
// File: crates/diesel_models/src/query/payouts.rs
// Module: diesel_models

pub fn find_optional_by_merchant_id_payout_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payout_id: &common_utils::id_type::PayoutId,
    ) -> StorageResult<Option<Self>>


// Function: is_backfill_required
// File: crates/router/src/utils/user/dashboard_metadata.rs
// Module: router

pub fn is_backfill_required(metadata_key: DBEnum) -> bool


// Function: inner
// File: crates/hyperswitch_domain_models/src/type_encryption.rs
// Module: hyperswitch_domain_models

pub fn inner(&self) -> &T


// Implementation: impl TokenizationInterface for for KafkaStore
// File: crates/router/src/db/kafka_store.rs
// Module: router
// Methods: 3 total (0 public)

impl TokenizationInterface for for KafkaStore


// Struct: CybersourceTokenInformation
// File: crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct CybersourceTokenInformation


// Implementation: impl MasterKeyInterface for for Store
// File: crates/router/src/db.rs
// Module: router
// Methods: 1 total (0 public)

impl MasterKeyInterface for for Store


// Struct: AuthorizedotnetWebhookObjectId
// File: crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AuthorizedotnetWebhookObjectId


// Implementation: impl common_utils::events::ApiEventMetric for for FeatureMatrixRequest
// File: crates/api_models/src/feature_matrix.rs
// Module: api_models
// Methods: 0 total (0 public)

impl common_utils::events::ApiEventMetric for for FeatureMatrixRequest


// File: crates/router/src/core/payouts/retry.rs
// Module: router
// Public functions: 8

use std::vec::IntoIter;

use common_enums::PayoutRetryType;
use error_stack::ResultExt;
use router_env::{
    logger,
    tracing::{self, instrument},
};

use super::{call_connector_payout, PayoutData};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payouts,
    },
    db::StorageInterface,
    routes::{self, app, metrics},
    types::{api, domain, storage},
    utils,
};

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn do_gsm_multiple_connector_actions(
    state: &app::SessionState,
    mut connectors_routing_data: IntoIter<api::ConnectorRoutingData>,
    original_connector_data: api::ConnectorData,
    payout_data: &mut PayoutData,
    merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
    let mut retries = None;

    metrics::AUTO_PAYOUT_RETRY_ELIGIBLE_REQUEST_COUNT.add(1, &[]);

    let mut connector = original_connector_data;

    loop {
        let gsm = get_gsm(state, &connector, payout_data).await?;

        match get_gsm_decision(gsm) {
            common_enums::GsmDecision::Retry => {
                retries = get_retries(
                    state,
                    retries,
                    merchant_context.get_merchant_account().get_id(),
                    PayoutRetryType::MultiConnector,
                )
                .await;

                if retries.is_none() || retries == Some(0) {
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    logger::info!("retries exhausted for auto_retry payout");
                    break;
                }

                if connectors_routing_data.len() == 0 {
                    logger::info!("connectors exhausted for auto_retry payout");
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    break;
                }

                connector = super::get_next_connector(&mut connectors_routing_data)?.connector_data;

                Box::pin(do_retry(
                    &state.clone(),
                    connector.to_owned(),
                    merchant_context,
                    payout_data,
                ))
                .await?;

                retries = retries.map(|i| i - 1);
            }
            common_enums::GsmDecision::DoDefault => break,
        }
    }
    Ok(())
}

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn do_gsm_single_connector_actions(
    state: &app::SessionState,
    original_connector_data: api::ConnectorData,
    payout_data: &mut PayoutData,
    merchant_context: &domain::MerchantContext,
) -> RouterResult<()> {
    let mut retries = None;

    metrics::AUTO_PAYOUT_RETRY_ELIGIBLE_REQUEST_COUNT.add(1, &[]);

    let mut previous_gsm = None; // to compare previous status

    loop {
        let gsm = get_gsm(state, &original_connector_data, payout_data).await?;

        // if the error config is same as previous, we break out of the loop
        if gsm == previous_gsm {
            break;
        }
        previous_gsm.clone_from(&gsm);

        match get_gsm_decision(gsm) {
            common_enums::GsmDecision::Retry => {
                retries = get_retries(
                    state,
                    retries,
                    merchant_context.get_merchant_account().get_id(),
                    PayoutRetryType::SingleConnector,
                )
                .await;

                if retries.is_none() || retries == Some(0) {
                    metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                    logger::info!("retries exhausted for auto_retry payment");
                    break;
                }

                Box::pin(do_retry(
                    &state.clone(),
                    original_connector_data.to_owned(),
                    merchant_context,
                    payout_data,
                ))
                .await?;

                retries = retries.map(|i| i - 1);
            }
            common_enums::GsmDecision::DoDefault => break,
        }
    }
    Ok(())
}

#[instrument(skip_all)]
pub async fn get_retries(
    state: &app::SessionState,
    retries: Option<i32>,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_type: PayoutRetryType,
) -> Option<i32> {
    match retries {
        Some(retries) => Some(retries),
        None => {
            let key = merchant_id.get_max_auto_single_connector_payout_retries_enabled(retry_type);
            let db = &*state.store;
            db.find_config_by_key(key.as_str())
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .and_then(|retries_config| {
                    retries_config
                        .config
                        .parse::<i32>()
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Retries config parsing failed")
                })
                .map_err(|err| {
                    logger::error!(retries_error=?err);
                    None::<i32>
                })
                .ok()
        }
    }
}

#[instrument(skip_all)]
pub async fn get_gsm(
    state: &app::SessionState,
    original_connector_data: &api::ConnectorData,
    payout_data: &PayoutData,
) -> RouterResult<Option<hyperswitch_domain_models::gsm::GatewayStatusMap>> {
    let error_code = payout_data.payout_attempt.error_code.to_owned();
    let error_message = payout_data.payout_attempt.error_message.to_owned();
    let connector_name = Some(original_connector_data.connector_name.to_string());

    Ok(payouts::helpers::get_gsm_record(
        state,
        error_code,
        error_message,
        connector_name,
        common_utils::consts::PAYOUT_FLOW_STR,
    )
    .await)
}

#[instrument(skip_all)]
pub fn get_gsm_decision(
    option_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
) -> common_enums::GsmDecision {
    let option_gsm_decision = option_gsm.map(|gsm| gsm.feature_data.get_decision());

    if option_gsm_decision.is_some() {
        metrics::AUTO_PAYOUT_RETRY_GSM_MATCH_COUNT.add(1, &[]);
    }
    option_gsm_decision.unwrap_or_default()
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn do_retry(
    state: &routes::SessionState,
    connector: api::ConnectorData,
    merchant_context: &domain::MerchantContext,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    metrics::AUTO_RETRY_PAYOUT_COUNT.add(1, &[]);

    modify_trackers(state, &connector, merchant_context, payout_data).await?;

    Box::pin(call_connector_payout(
        state,
        merchant_context,
        &connector,
        payout_data,
    ))
    .await
}

#[instrument(skip_all)]
pub async fn modify_trackers(
    state: &routes::SessionState,
    connector: &api::ConnectorData,
    merchant_context: &domain::MerchantContext,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    let new_attempt_count = payout_data.payouts.attempt_count + 1;

    let db = &*state.store;

    // update payout table's attempt count
    let payouts = payout_data.payouts.to_owned();
    let updated_payouts = storage::PayoutsUpdate::AttemptCountUpdate {
        attempt_count: new_attempt_count,
    };

    let payout_id = payouts.payout_id.clone();
    payout_data.payouts = db
        .update_payout(
            &payout_data.payouts,
            updated_payouts,
            &payout_data.payout_attempt,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error updating payouts")?;

    let payout_attempt_id = utils::get_payout_attempt_id(
        payout_id.get_string_repr(),
        payout_data.payouts.attempt_count,
    );

    let payout_attempt_req = storage::PayoutAttemptNew {
        payout_attempt_id: payout_attempt_id.to_string(),
        payout_id: payout_id.to_owned(),
        merchant_order_reference_id: payout_data
            .payout_attempt
            .merchant_order_reference_id
            .clone(),
        customer_id: payout_data.payout_attempt.customer_id.to_owned(),
        connector: Some(connector.connector_name.to_string()),
        merchant_id: payout_data.payout_attempt.merchant_id.to_owned(),
        address_id: payout_data.payout_attempt.address_id.to_owned(),
        business_country: payout_data.payout_attempt.business_country.to_owned(),
        business_label: payout_data.payout_attempt.business_label.to_owned(),
        payout_token: payout_data.payout_attempt.payout_token.to_owned(),
        profile_id: payout_data.payout_attempt.profile_id.to_owned(),
        connector_payout_id: None,
        status: common_enums::PayoutStatus::default(),
        is_eligible: None,
        error_message: None,
        error_code: None,
        created_at: common_utils::date_time::now(),
        last_modified_at: common_utils::date_time::now(),
        merchant_connector_id: None,
        routing_info: None,
        unified_code: None,
        unified_message: None,
        additional_payout_method_data: payout_data
            .payout_attempt
            .additional_payout_method_data
            .to_owned(),
    };
    payout_data.payout_attempt = db
        .insert_payout_attempt(
            payout_attempt_req,
            &payouts,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayout { payout_id })
        .attach_printable("Error inserting payouts in db")?;

    payout_data.merchant_connector_account = None;

    Ok(())
}

pub async fn config_should_call_gsm_payout(
    db: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_type: PayoutRetryType,
) -> bool {
    let key = merchant_id.get_should_call_gsm_payout_key(retry_type);
    let config = db
        .find_config_by_key_unwrap_or(key.as_str(), Some("false".to_string()))
        .await;
    match config {
        Ok(conf) => conf.config == "true",
        Err(error) => {
            logger::error!(?error);
            false
        }
    }
}

pub trait GsmValidation {
    // TODO : move this function to appropriate place later.
    fn should_call_gsm(&self) -> bool;
}

impl GsmValidation for PayoutData {
    #[inline(always)]
    fn should_call_gsm(&self) -> bool {
        match self.payout_attempt.status {
            common_enums::PayoutStatus::Success
            | common_enums::PayoutStatus::RequiresConfirmation
            | common_enums::PayoutStatus::Cancelled
            | common_enums::PayoutStatus::Pending
            | common_enums::PayoutStatus::Initiated
            | common_enums::PayoutStatus::Reversed
            | common_enums::PayoutStatus::Expired
            | common_enums::PayoutStatus::Ineligible
            | common_enums::PayoutStatus::RequiresCreation
            | common_enums::PayoutStatus::RequiresPayoutMethodData
            | common_enums::PayoutStatus::RequiresVendorAccountCreation
            | common_enums::PayoutStatus::RequiresFulfillment => false,
            common_enums::PayoutStatus::Failed => true,
        }
    }
}


// File: crates/external_services/src/email.rs
// Module: external_services
// Public functions: 3
// Public structs: 3

//! Interactions with the AWS SES SDK

use aws_sdk_sesv2::types::Body;
use common_utils::{errors::CustomResult, pii};
use serde::Deserialize;

/// Implementation of aws ses client
pub mod ses;

/// Implementation of SMTP server client
pub mod smtp;

/// Implementation of Email client when email support is disabled
pub mod no_email;

/// Custom Result type alias for Email operations.
pub type EmailResult<T> = CustomResult<T, EmailError>;

/// A trait that defines the methods that must be implemented to send email.
#[async_trait::async_trait]
pub trait EmailClient: Sync + Send + dyn_clone::DynClone {
    /// The rich text type of the email client
    type RichText;

    /// Sends an email to the specified recipient with the given subject and body.
    async fn send_email(
        &self,
        recipient: pii::Email,
        subject: String,
        body: Self::RichText,
        proxy_url: Option<&String>,
    ) -> EmailResult<()>;

    /// Convert Stringified HTML to client native rich text format
    /// This has to be done because not all clients may format html as the same
    fn convert_to_rich_text(
        &self,
        intermediate_string: IntermediateString,
    ) -> CustomResult<Self::RichText, EmailError>
    where
        Self::RichText: Send;
}

/// A super trait which is automatically implemented for all EmailClients
#[async_trait::async_trait]
pub trait EmailService: Sync + Send + dyn_clone::DynClone {
    /// Compose and send email using the email data
    async fn compose_and_send_email(
        &self,
        base_url: &str,
        email_data: Box<dyn EmailData + Send>,
        proxy_url: Option<&String>,
    ) -> EmailResult<()>;
}

#[async_trait::async_trait]
impl<T> EmailService for T
where
    T: EmailClient,
    <Self as EmailClient>::RichText: Send,
{
    async fn compose_and_send_email(
        &self,
        base_url: &str,
        email_data: Box<dyn EmailData + Send>,
        proxy_url: Option<&String>,
    ) -> EmailResult<()> {
        let email_data = email_data.get_email_data(base_url);
        let email_data = email_data.await?;

        let EmailContents {
            subject,
            body,
            recipient,
        } = email_data;

        let rich_text_string = self.convert_to_rich_text(body)?;

        self.send_email(recipient, subject, rich_text_string, proxy_url)
            .await
    }
}

/// This is a struct used to create Intermediate String for rich text ( html )
#[derive(Debug)]
pub struct IntermediateString(String);

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
    }
}

/// Temporary output for the email subject
#[derive(Debug)]
pub struct EmailContents {
    /// The subject of email
    pub subject: String,

    /// This will be the intermediate representation of the email body in a generic format.
    /// The email clients can convert this intermediate representation to their client specific rich text format
    pub body: IntermediateString,

    /// The email of the recipient to whom the email has to be sent
    pub recipient: pii::Email,
}

/// A trait which will contain the logic of generating the email subject and body
#[async_trait::async_trait]
pub trait EmailData {
    /// Get the email contents
    async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError>;
}

dyn_clone::clone_trait_object!(EmailClient<RichText = Body>);

/// List of available email clients to choose from
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(tag = "active_email_client")]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EmailClientConfigs {
    #[default]
    /// Default Email client to use when no client is specified
    NoEmailClient,
    /// AWS ses email client
    Ses {
        /// AWS SES client configuration
        aws_ses: ses::SESConfig,
    },
    /// Other Simple SMTP server
    Smtp {
        /// SMTP server configuration
        smtp: smtp::SmtpServerConfig,
    },
}

/// Struct that contains the settings required to construct an EmailClient.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct EmailSettings {
    /// The AWS region to send SES requests to.
    pub aws_region: String,

    /// Number of days for verification of the email
    pub allowed_unverified_days: i64,

    /// Sender email
    pub sender_email: String,

    #[serde(flatten)]
    /// The client specific configurations
    pub client_config: EmailClientConfigs,

    /// Recipient email for recon emails
    pub recon_recipient_email: pii::Email,

    /// Recipient email for recon emails
    pub prod_intent_recipient_email: pii::Email,
}

impl EmailSettings {
    /// Validation for the Email client specific configurations
    pub fn validate(&self) -> Result<(), &'static str> {
        match &self.client_config {
            EmailClientConfigs::Ses { ref aws_ses } => aws_ses.validate(),
            EmailClientConfigs::Smtp { ref smtp } => smtp.validate(),
            EmailClientConfigs::NoEmailClient => Ok(()),
        }
    }
}

/// Errors that could occur from EmailClient.
#[derive(Debug, thiserror::Error)]
pub enum EmailError {
    /// An error occurred when building email client.
    #[error("Error building email client")]
    ClientBuildingFailure,

    /// An error occurred when sending email
    #[error("Error sending email to recipient")]
    EmailSendingFailure,

    /// Failed to generate the email token
    #[error("Failed to generate email token")]
    TokenGenerationFailure,

    /// The expected feature is not implemented
    #[error("Feature not implemented")]
    NotImplemented,

    /// An error occurred when building email content.
    #[error("Error building email content")]
    ContentBuildFailure,
}


// Implementation: impl api::RefundSync for for Blackhawknetwork
// File: crates/hyperswitch_connectors/src/connectors/blackhawknetwork.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundSync for for Blackhawknetwork


// Implementation: impl api::PaymentSession for for Stripe
// File: crates/hyperswitch_connectors/src/connectors/stripe.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSession for for Stripe


// Struct: GooglePayTokenPaymentInformation
// File: crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct GooglePayTokenPaymentInformation


// Implementation: impl Paybox
// File: crates/hyperswitch_connectors/src/connectors/paybox.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl Paybox


// File: crates/external_services/src/http_client/request.rs
// Module: external_services

use std::str::FromStr;

use common_utils::request::Headers;
pub use common_utils::{errors::CustomResult, request::ContentType};
use error_stack::ResultExt;
use hyperswitch_interfaces::errors::HttpClientError;
pub use masking::{Mask, Maskable};
use router_env::{instrument, tracing};

#[allow(missing_docs)]
pub trait HeaderExt {
    fn construct_header_map(self) -> CustomResult<reqwest::header::HeaderMap, HttpClientError>;
}

impl HeaderExt for Headers {
    fn construct_header_map(self) -> CustomResult<reqwest::header::HeaderMap, HttpClientError> {
        use reqwest::header::{HeaderMap, HeaderName, HeaderValue};

        self.into_iter().try_fold(
            HeaderMap::new(),
            |mut header_map, (header_name, header_value)| {
                let header_name = HeaderName::from_str(&header_name)
                    .change_context(HttpClientError::HeaderMapConstructionFailed)?;
                let header_value = header_value.into_inner();
                let header_value = HeaderValue::from_str(&header_value)
                    .change_context(HttpClientError::HeaderMapConstructionFailed)?;
                header_map.append(header_name, header_value);
                Ok(header_map)
            },
        )
    }
}

#[allow(missing_docs)]
pub trait RequestBuilderExt {
    fn add_headers(self, headers: reqwest::header::HeaderMap) -> Self;
}

impl RequestBuilderExt for reqwest::RequestBuilder {
    #[instrument(skip_all)]
    fn add_headers(mut self, headers: reqwest::header::HeaderMap) -> Self {
        self = self.headers(headers);
        self
    }
}


// Implementation: impl api::ConnectorAccessToken for for Square
// File: crates/hyperswitch_connectors/src/connectors/square.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorAccessToken for for Square


// Struct: NovalnetCaptureResponse
// File: crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NovalnetCaptureResponse


// File: crates/router/src/services/authentication/blacklist.rs
// Module: router
// Public functions: 6

use std::sync::Arc;

#[cfg(feature = "olap")]
use common_utils::date_time;
use error_stack::ResultExt;
use redis_interface::RedisConnectionPool;

use super::AuthToken;
#[cfg(feature = "olap")]
use super::{SinglePurposeOrLoginToken, SinglePurposeToken};
#[cfg(feature = "email")]
use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
use crate::{
    consts::{JWT_TOKEN_TIME_IN_SECS, ROLE_BLACKLIST_PREFIX, USER_BLACKLIST_PREFIX},
    core::errors::{ApiErrorResponse, RouterResult},
    routes::app::SessionStateInfo,
};
#[cfg(feature = "olap")]
use crate::{
    core::errors::{UserErrors, UserResult},
    routes::SessionState,
    services::authorization as authz,
};

#[cfg(feature = "olap")]
pub async fn insert_user_in_blacklist(state: &SessionState, user_id: &str) -> UserResult<()> {
    let user_blacklist_key = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &user_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
pub async fn insert_role_in_blacklist(state: &SessionState, role_id: &str) -> UserResult<()> {
    let role_blacklist_key = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &role_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)?;
    invalidate_role_cache(state, role_id)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
async fn invalidate_role_cache(state: &SessionState, role_id: &str) -> RouterResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&authz::get_cache_key_from_role_id(role_id).as_str().into())
        .await
        .map(|_| ())
        .change_context(ApiErrorResponse::InternalServerError)
}

pub async fn check_user_in_blacklist<A: SessionStateInfo>(
    state: &A,
    user_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

pub async fn check_role_in_blacklist<A: SessionStateInfo>(
    state: &A,
    role_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

#[cfg(feature = "email")]
pub async fn insert_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let expiry =
        expiry_to_i64(EMAIL_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(&blacklist_key.as_str().into(), true, expiry)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "email")]
pub async fn check_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let key_exists = redis_conn
        .exists::<()>(&blacklist_key.as_str().into())
        .await
        .change_context(UserErrors::InternalServerError)?;

    if key_exists {
        return Err(UserErrors::LinkInvalid.into());
    }
    Ok(())
}

fn get_redis_connection_for_global_tenant<A: SessionStateInfo>(
    state: &A,
) -> RouterResult<Arc<RedisConnectionPool>> {
    state
        .global_store()
        .get_redis_conn()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")
}

fn expiry_to_i64(expiry: u64) -> RouterResult<i64> {
    i64::try_from(expiry).change_context(ApiErrorResponse::InternalServerError)
}

#[async_trait::async_trait]
pub trait BlackList {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync;
}

#[async_trait::async_trait]
impl BlackList for AuthToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        Ok(
            check_user_in_blacklist(state, &self.user_id, self.exp).await?
                || check_role_in_blacklist(state, &self.role_id, self.exp).await?,
        )
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeOrLoginToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}


// File: crates/router/src/types/storage/merchant_connector_account.rs
// Module: router

pub use diesel_models::merchant_connector_account::{
    MerchantConnectorAccount, MerchantConnectorAccountNew, MerchantConnectorAccountUpdateInternal,
};

pub use crate::types::domain::MerchantConnectorAccountUpdate;


// Struct: AuthToken
// File: crates/router/src/services/authentication.rs
// Module: router
// Implementations: 1

pub struct AuthToken


// Function: retrieve_surcharge_decision_config
// File: crates/router/src/core/surcharge_decision_config.rs
// Module: router

pub fn retrieve_surcharge_decision_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
) -> RouterResponse<SurchargeDecisionManagerResponse>


// Struct: PlacetopayCard
// File: crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PlacetopayCard


// Struct: PhonepeAuthType
// File: crates/hyperswitch_connectors/src/connectors/phonepe/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PhonepeAuthType


// File: crates/router/src/types/api/payouts.rs
// Module: router

pub use api_models::payouts::{
    AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PaymentMethodTypeInfo,
    PayoutActionRequest, PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse,
    PayoutEnabledPaymentMethodsInfo, PayoutLinkResponse, PayoutListConstraints,
    PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, PayoutMethodData,
    PayoutMethodDataResponse, PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest,
    PixBankTransfer, RequiredFieldsOverrideRequest, SepaBankTransfer, Wallet as WalletPayout,
};
pub use hyperswitch_domain_models::router_flow_types::payouts::{
    PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync,
};
pub use hyperswitch_interfaces::api::payouts::{
    PayoutCancel, PayoutCreate, PayoutEligibility, PayoutFulfill, PayoutQuote, PayoutRecipient,
    PayoutRecipientAccount, PayoutSync, Payouts,
};

pub use super::payouts_v2::{
    PayoutCancelV2, PayoutCreateV2, PayoutEligibilityV2, PayoutFulfillV2, PayoutQuoteV2,
    PayoutRecipientAccountV2, PayoutRecipientV2, PayoutSyncV2, PayoutsV2,
};


// Implementation: impl ConnectorCommon for for HyperswitchVault
// File: crates/hyperswitch_connectors/src/connectors/hyperswitch_vault.rs
// Module: hyperswitch_connectors
// Methods: 6 total (0 public)

impl ConnectorCommon for for HyperswitchVault


// Function: get_payment_method_type
// File: crates/common_types/src/payment_methods.rs
// Module: common_types

// Documentation: Get payment_method_subtype

pub fn get_payment_method_type(&self) -> Option<common_enums::PaymentMethodType>


// Implementation: impl api::PaymentAuthorize for for Gpayments
// File: crates/hyperswitch_connectors/src/connectors/gpayments.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentAuthorize for for Gpayments


// Struct: WellsfargoStandardErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct WellsfargoStandardErrorResponse


// Struct: DomainInfo
// File: crates/hyperswitch_constraint_graph/src/types.rs
// Module: hyperswitch_constraint_graph
// Implementations: 0

pub struct DomainInfo