// File: crates/router/src/core/payments/operations/payment_status.rs

use std::marker::PhantomData;

use api_models::enums::FrmSuggestion;
use async_trait::async_trait;
use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, logger, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        payments::{
            helpers, operations, types as payment_types, CustomerDetails, PaymentAddress,
            PaymentData,
        },
    },
    events::audit_events::{AuditEvent, AuditEventType},
    routes::{app::ReqState, SessionState},
    services,
    types::{
        api, domain,
        storage::{self, enums},
    },
    utils::OptionExt,
};

#[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(operations = "all", flow = "sync")]
pub struct PaymentStatus;

type PaymentStatusOperation<'b, F, R> = BoxedOperation<'b, F, R, PaymentData<F>>;

impl<F: Send + Clone + Sync> Operation<F, api::PaymentsRequest> for PaymentStatus {
    type Data = PaymentData<F>;
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, api::PaymentsRequest, PaymentData<F>>> {
        Ok(self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> + Send + Sync)>
    {
        Ok(self)
    }
}
impl<F: Send + Clone + Sync> Operation<F, api::PaymentsRequest> for &PaymentStatus {
    type Data = PaymentData<F>;
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, api::PaymentsRequest, PaymentData<F>>> {
        Ok(*self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> + Send + Sync)>
    {
        Ok(*self)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, api::PaymentsRequest, PaymentData<F>> for PaymentStatus {
    #[instrument(skip_all)]
    async fn get_or_create_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentData<F>,
        request: Option<CustomerDetails>,
        key_store: &domain::MerchantKeyStore,
        storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<
        (
            PaymentStatusOperation<'a, F, api::PaymentsRequest>,
            Option<domain::Customer>,
        ),
        errors::StorageError,
    > {
        helpers::create_customer_if_not_exist(
            state,
            Box::new(self),
            payment_data,
            request,
            &key_store.merchant_id,
            key_store,
            storage_scheme,
        )
        .await
    }

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

    #[instrument(skip_all)]
    async fn add_task_to_process_tracker<'a>(
        &'a self,
        state: &'a SessionState,
        payment_attempt: &storage::PaymentAttempt,
        requeue: bool,
        schedule_time: Option<time::PrimitiveDateTime>,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        helpers::add_domain_task_to_pt(self, state, payment_attempt, requeue, schedule_time).await
    }

    async fn get_connector<'a>(
        &'a self,
        _platform: &domain::Platform,
        state: &SessionState,
        request: &api::PaymentsRequest,
        _payment_intent: &storage::PaymentIntent,
    ) -> CustomResult<api::ConnectorChoice, errors::ApiErrorResponse> {
        helpers::get_connector_default(state, request.routing.clone()).await
    }

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

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for PaymentStatus {
    async fn update_trackers<'b>(
        &'b self,
        _state: &'b SessionState,
        req_state: ReqState,
        payment_data: PaymentData<F>,
        _customer: Option<domain::Customer>,
        _storage_scheme: enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        _key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(
        PaymentStatusOperation<'b, F, api::PaymentsRequest>,
        PaymentData<F>,
    )>
    where
        F: 'b + Send,
    {
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentStatus))
            .with(payment_data.to_event())
            .emit();

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

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRetrieveRequest>
    for PaymentStatus
{
    async fn update_trackers<'b>(
        &'b self,
        _state: &'b SessionState,
        req_state: ReqState,
        payment_data: PaymentData<F>,
        _customer: Option<domain::Customer>,
        _storage_scheme: enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        _key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(
        PaymentStatusOperation<'b, F, api::PaymentsRetrieveRequest>,
        PaymentData<F>,
    )>
    where
        F: 'b + Send,
    {
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentStatus))
            .with(payment_data.to_event())
            .emit();

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

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentData<F>, api::PaymentsRetrieveRequest>
    for PaymentStatus
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        request: &api::PaymentsRetrieveRequest,
        platform: &domain::Platform,
        _auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<
        operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData<F>>,
    > {
        Box::pin(get_tracker_for_sync(
            payment_id,
            &platform.clone(),
            state,
            request,
            self,
            platform.get_processor().get_account().storage_scheme,
        ))
        .await
    }
}

#[cfg(feature = "v2")]
async fn get_tracker_for_sync<
    'a,
    F: Send + Clone,
    Op: Operation<F, api::PaymentsRetrieveRequest, Data = PaymentData<F>> + 'a + Send + Sync,
>(
    _payment_id: &api::PaymentIdType,
    _platform: &domain::Platform,
    _state: &SessionState,
    _request: &api::PaymentsRetrieveRequest,
    _operation: Op,
    _storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData<F>>>
{
    todo!()
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
async fn get_tracker_for_sync<
    'a,
    F: Send + Clone,
    Op: Operation<F, api::PaymentsRetrieveRequest, Data = PaymentData<F>> + 'a + Send + Sync,
>(
    payment_id: &api::PaymentIdType,
    platform: &domain::Platform,
    state: &SessionState,
    request: &api::PaymentsRetrieveRequest,
    operation: Op,
    storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData<F>>>
{
    let (payment_intent, mut payment_attempt, currency, amount);

    (payment_intent, payment_attempt) = get_payment_intent_payment_attempt(
        state,
        payment_id,
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_key_store(),
        storage_scheme,
    )
    .await?;

    helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?;

    let payment_id = payment_attempt.payment_id.clone();

    currency = payment_attempt.currency.get_required_value("currency")?;
    amount = payment_attempt.get_total_amount().into();

    let shipping_address = helpers::get_address_by_id(
        state,
        payment_intent.shipping_address_id.clone(),
        platform.get_processor().get_key_store(),
        &payment_intent.payment_id.clone(),
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_account().storage_scheme,
    )
    .await?;
    let billing_address = helpers::get_address_by_id(
        state,
        payment_intent.billing_address_id.clone(),
        platform.get_processor().get_key_store(),
        &payment_intent.payment_id.clone(),
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_account().storage_scheme,
    )
    .await?;

    let payment_method_billing = helpers::get_address_by_id(
        state,
        payment_attempt.payment_method_billing_address_id.clone(),
        platform.get_processor().get_key_store(),
        &payment_intent.payment_id.clone(),
        platform.get_processor().get_account().get_id(),
        platform.get_processor().get_account().storage_scheme,
    )
    .await?;

    payment_attempt.encoded_data.clone_from(&request.param);
    let db = &*state.store;
    let attempts = match request.expand_attempts {
        Some(true) => {
            Some(db
                .find_attempts_by_merchant_id_payment_id(platform.get_processor().get_account().get_id(), &payment_id, storage_scheme)
                .await
                .change_context(errors::ApiErrorResponse::PaymentNotFound)
                .attach_printable_lazy(|| {
                    format!("Error while retrieving attempt list for, merchant_id: {:?}, payment_id: {payment_id:?}",platform.get_processor().get_account().get_id())
                })?)
        },
        _ => None,
    };

    let multiple_capture_data = if payment_attempt.multiple_capture_count > Some(0) {
        let captures = db
            .find_all_captures_by_merchant_id_payment_id_authorized_attempt_id(
                &payment_attempt.merchant_id,
                &payment_attempt.payment_id,
                &payment_attempt.attempt_id,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::PaymentNotFound)
                .attach_printable_lazy(|| {
                    format!("Error while retrieving capture list for, merchant_id: {:?}, payment_id: {payment_id:?}", platform.get_processor().get_account().get_id())
                })?;
        Some(payment_types::MultipleCaptureData::new_for_sync(
            captures,
            request.expand_captures,
        )?)
    } else {
        None
    };

    let refunds = db
        .find_refund_by_payment_id_merchant_id(
            &payment_id,
            platform.get_processor().get_account().get_id(),
            storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)
        .attach_printable_lazy(|| {
            format!(
                "Failed while getting refund list for, payment_id: {:?}, merchant_id: {:?}",
                &payment_id,
                platform.get_processor().get_account().get_id()
            )
        })?;

    let authorizations = db
        .find_all_authorizations_by_merchant_id_payment_id(
            platform.get_processor().get_account().get_id(),
            &payment_id,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)
        .attach_printable_lazy(|| {
            format!(
                "Failed while getting authorizations list for, payment_id: {:?}, merchant_id: {:?}",
                &payment_id,
                platform.get_processor().get_account().get_id()
            )
        })?;

    let disputes = db
        .find_disputes_by_merchant_id_payment_id(platform.get_processor().get_account().get_id(), &payment_id)
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)
        .attach_printable_lazy(|| {
            format!("Error while retrieving dispute list for, merchant_id: {:?}, payment_id: {payment_id:?}", platform.get_processor().get_account().get_id())
        })?;

    let frm_response = if cfg!(feature = "frm") {
        db.find_fraud_check_by_payment_id(payment_id.to_owned(), platform.get_processor().get_account().get_id().clone())
            .await
            .change_context(errors::ApiErrorResponse::PaymentNotFound)
            .attach_printable_lazy(|| {
                format!("Error while retrieving frm_response, merchant_id: {:?}, payment_id: {payment_id:?}", platform.get_processor().get_account().get_id())
            })
            .ok()
    } else {
        None
    };

    let contains_encoded_data = payment_attempt.encoded_data.is_some();

    let creds_identifier = request
        .merchant_connector_details
        .as_ref()
        .map(|mcd| mcd.creds_identifier.to_owned());
    request
        .merchant_connector_details
        .to_owned()
        .async_map(|mcd| async {
            helpers::insert_merchant_connector_creds_to_config(
                db,
                platform.get_processor().get_account().get_id(),
                mcd,
            )
            .await
        })
        .await
        .transpose()?;

    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("'profile_id' not set in payment intent")?;

    let business_profile = db
        .find_business_profile_by_profile_id(platform.get_processor().get_key_store(), profile_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let payment_method_info =
        if let Some(ref payment_method_id) = payment_attempt.payment_method_id.clone() {
            match db
                .find_payment_method(
                    platform.get_processor().get_key_store(),
                    payment_method_id,
                    storage_scheme,
                )
                .await
            {
                Ok(payment_method) => Some(payment_method),
                Err(error) => {
                    if error.current_context().is_db_not_found() {
                        logger::info!("Payment Method not found in db {:?}", error);
                        None
                    } else {
                        Err(error)
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Error retrieving payment method from db")?
                    }
                }
            }
        } else {
            None
        };

    let merchant_id = payment_intent.merchant_id.clone();

    let authentication_store = if let Some(ref authentication_id) =
        payment_attempt.authentication_id
    {
        let authentication = db
            .find_authentication_by_merchant_id_authentication_id(&merchant_id, authentication_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
            .attach_printable_lazy(|| {
                format!(
                    "Error while fetching authentication record with authentication_id {}",
                    authentication_id.get_string_repr()
                )
            })?;

        Some(
            hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore {
                authentication,
                cavv: None, // marking this as None since we don't need authentication value in payment status flow
            },
        )
    } else {
        None
    };

    let payment_link_data = payment_intent
        .payment_link_id
        .as_ref()
        .async_map(|id| crate::core::payments::get_payment_link_response_from_id(state, id))
        .await
        .transpose()?;

    let payment_data = PaymentData {
        flow: PhantomData,
        payment_intent,
        currency,
        amount,
        email: None,
        mandate_id: payment_attempt
            .mandate_id
            .clone()
            .map(|id| api_models::payments::MandateIds {
                mandate_id: Some(id),
                mandate_reference_id: None,
            }),
        mandate_connector: None,
        setup_mandate: None,
        customer_acceptance: None,
        token: None,
        address: PaymentAddress::new(
            shipping_address.as_ref().map(From::from),
            billing_address.as_ref().map(From::from),
            payment_method_billing.as_ref().map(From::from),
            business_profile.use_billing_as_payment_method_billing,
        ),
        token_data: None,
        confirm: Some(request.force_sync),
        payment_method_data: None,
        payment_method_token: None,
        payment_method_info,
        force_sync: Some(
            request.force_sync
                && (helpers::check_force_psync_precondition(payment_attempt.status)
                    || contains_encoded_data),
        ),
        all_keys_required: request.all_keys_required,
        payment_attempt,
        refunds,
        disputes,
        attempts,
        sessions_token: vec![],
        card_cvc: None,
        creds_identifier,
        pm_token: None,
        connector_customer_id: None,
        recurring_mandate_payment_data: None,
        ephemeral_key: None,
        multiple_capture_data,
        redirect_response: None,
        payment_link_data,
        surcharge_details: None,
        frm_message: frm_response,
        incremental_authorization_details: None,
        authorizations,
        authentication: authentication_store,
        recurring_details: None,
        poll_config: None,
        tax_data: None,
        session_id: None,
        service_details: None,
        card_testing_guard_data: None,
        vault_operation: None,
        threeds_method_comp_ind: None,
        whole_connector_response: None,
        is_manual_retry_enabled: business_profile.is_manual_retry_enabled,
        is_l2_l3_enabled: business_profile.is_l2_l3_enabled,
        external_authentication_data: None,
    };

    let get_trackers_response = operations::GetTrackerResponse {
        operation: Box::new(operation),
        customer_details: None,
        payment_data,
        business_profile,
        mandate_type: None,
    };

    Ok(get_trackers_response)
}

impl<F: Send + Clone + Sync> ValidateRequest<F, api::PaymentsRetrieveRequest, PaymentData<F>>
    for PaymentStatus
{
    fn validate_request<'b>(
        &'b self,
        request: &api::PaymentsRetrieveRequest,
        platform: &domain::Platform,
    ) -> RouterResult<(
        PaymentStatusOperation<'b, F, api::PaymentsRetrieveRequest>,
        operations::ValidateResult,
    )> {
        let request_merchant_id = request.merchant_id.as_ref();
        helpers::validate_merchant_id(
            platform.get_processor().get_account().get_id(),
            request_merchant_id,
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "merchant_id".to_string(),
            expected_format: "merchant_id from merchant account".to_string(),
        })?;

        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: platform.get_processor().get_account().get_id().to_owned(),
                payment_id: request.resource_id.clone(),
                storage_scheme: platform.get_processor().get_account().storage_scheme,
                requeue: false,
            },
        ))
    }
}

pub async fn get_payment_intent_payment_attempt(
    state: &SessionState,
    payment_id: &api::PaymentIdType,
    merchant_id: &common_utils::id_type::MerchantId,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<(storage::PaymentIntent, storage::PaymentAttempt)> {
    let db = &*state.store;
    let get_pi_pa = || async {
        let (pi, pa);
        match payment_id {
            api_models::payments::PaymentIdType::PaymentIntentId(ref id) => {
                pi = db
                    .find_payment_intent_by_payment_id_merchant_id(
                        id,
                        merchant_id,
                        key_store,
                        storage_scheme,
                    )
                    .await?;
                pa = db
                    .find_payment_attempt_by_payment_id_merchant_id_attempt_id(
                        &pi.payment_id,
                        merchant_id,
                        pi.active_attempt.get_id().as_str(),
                        storage_scheme,
                    )
                    .await?;
            }
            api_models::payments::PaymentIdType::ConnectorTransactionId(ref id) => {
                pa = db
                    .find_payment_attempt_by_merchant_id_connector_txn_id(
                        merchant_id,
                        id,
                        storage_scheme,
                    )
                    .await?;
                pi = db
                    .find_payment_intent_by_payment_id_merchant_id(
                        &pa.payment_id,
                        merchant_id,
                        key_store,
                        storage_scheme,
                    )
                    .await?;
            }
            api_models::payments::PaymentIdType::PaymentAttemptId(ref id) => {
                pa = db
                    .find_payment_attempt_by_attempt_id_merchant_id(id, merchant_id, storage_scheme)
                    .await?;
                pi = db
                    .find_payment_intent_by_payment_id_merchant_id(
                        &pa.payment_id,
                        merchant_id,
                        key_store,
                        storage_scheme,
                    )
                    .await?;
            }
            api_models::payments::PaymentIdType::PreprocessingId(ref id) => {
                pa = db
                    .find_payment_attempt_by_preprocessing_id_merchant_id(
                        id,
                        merchant_id,
                        storage_scheme,
                    )
                    .await?;

                pi = db
                    .find_payment_intent_by_payment_id_merchant_id(
                        &pa.payment_id,
                        merchant_id,
                        key_store,
                        storage_scheme,
                    )
                    .await?;
            }
        }
        error_stack::Result::<_, errors::StorageError>::Ok((pi, pa))
    };

    get_pi_pa()
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)

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


// File: crates/router/src/core/payments/operations/tax_calculation.rs

use std::marker::PhantomData;

use api_models::enums::FrmSuggestion;
use async_trait::async_trait;
use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use masking::PeekInterface;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payment_methods::cards::create_encrypted_data,
        payments::{self, helpers, operations, PaymentData, PaymentMethodChecker},
        utils as core_utils,
    },
    db::errors::ConnectorErrorExt,
    routes::{app::ReqState, SessionState},
    services,
    types::{
        self,
        api::{self, ConnectorCallType, PaymentIdTypeExt},
        domain,
        storage::{self, enums as storage_enums},
    },
    utils::OptionExt,
};

#[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(operations = "all", flow = "sdk_session_update")]
pub struct PaymentSessionUpdate;

type PaymentSessionUpdateOperation<'b, F> =
    BoxedOperation<'b, F, api::PaymentsDynamicTaxCalculationRequest, PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync>
    GetTracker<F, PaymentData<F>, api::PaymentsDynamicTaxCalculationRequest>
    for PaymentSessionUpdate
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        request: &api::PaymentsDynamicTaxCalculationRequest,
        platform: &domain::Platform,
        _auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<
        operations::GetTrackerResponse<
            'a,
            F,
            api::PaymentsDynamicTaxCalculationRequest,
            PaymentData<F>,
        >,
    > {
        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

        let db = &*state.store;
        let merchant_id = platform.get_processor().get_account().get_id();
        let storage_scheme = platform.get_processor().get_account().storage_scheme;

        let payment_intent = db
            .find_payment_intent_by_payment_id_merchant_id(
                &payment_id,
                merchant_id,
                platform.get_processor().get_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

        helpers::validate_payment_status_against_not_allowed_statuses(
            payment_intent.status,
            &[
                storage_enums::IntentStatus::Failed,
                storage_enums::IntentStatus::Succeeded,
            ],
            "create a session update for",
        )?;

        helpers::authenticate_client_secret(Some(request.client_secret.peek()), &payment_intent)?;

        let mut payment_attempt = db
            .find_payment_attempt_by_payment_id_merchant_id_attempt_id(
                &payment_intent.payment_id,
                merchant_id,
                payment_intent.active_attempt.get_id().as_str(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        let currency = payment_intent.currency.get_required_value("currency")?;

        let amount = payment_attempt.get_total_amount().into();

        payment_attempt.payment_method_type = Some(request.payment_method_type);

        let shipping_address = helpers::get_address_by_id(
            state,
            payment_intent.shipping_address_id.clone(),
            platform.get_processor().get_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            platform.get_processor().get_account().storage_scheme,
        )
        .await?;

        let profile_id = payment_intent
            .profile_id
            .as_ref()
            .get_required_value("profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("'profile_id' not set in payment intent")?;

        let business_profile = db
            .find_business_profile_by_profile_id(
                platform.get_processor().get_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;

        let tax_data = payments::TaxData {
            shipping_details: request.shipping.clone().into(),
            payment_method_type: request.payment_method_type,
        };

        let payment_data = PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount,
            email: None,
            mandate_id: None,
            mandate_connector: None,
            customer_acceptance: None,
            token: None,
            token_data: None,
            setup_mandate: None,
            address: payments::PaymentAddress::new(
                shipping_address.as_ref().map(From::from),
                None,
                None,
                business_profile.use_billing_as_payment_method_billing,
            ),
            confirm: None,
            payment_method_data: None,
            payment_method_token: None,
            payment_method_info: None,
            force_sync: None,
            all_keys_required: None,
            refunds: vec![],
            disputes: vec![],
            attempts: None,
            sessions_token: vec![],
            card_cvc: None,
            creds_identifier: None,
            pm_token: None,
            connector_customer_id: None,
            recurring_mandate_payment_data: None,
            ephemeral_key: None,
            multiple_capture_data: None,
            redirect_response: None,
            surcharge_details: None,
            frm_message: None,
            payment_link_data: None,
            incremental_authorization_details: None,
            authorizations: vec![],
            authentication: None,
            recurring_details: None,
            poll_config: None,
            tax_data: Some(tax_data),
            session_id: request.session_id.clone(),
            service_details: None,
            card_testing_guard_data: None,
            vault_operation: None,
            threeds_method_comp_ind: None,
            whole_connector_response: None,
            is_manual_retry_enabled: None,
            is_l2_l3_enabled: false,
            external_authentication_data: None,
        };
        let get_trackers_response = operations::GetTrackerResponse {
            operation: Box::new(self),
            customer_details: None,
            payment_data,
            business_profile,
            mandate_type: None,
        };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, api::PaymentsDynamicTaxCalculationRequest, PaymentData<F>>
    for PaymentSessionUpdate
{
    #[instrument(skip_all)]
    async fn get_or_create_customer_details<'a>(
        &'a self,
        _state: &SessionState,
        _payment_data: &mut PaymentData<F>,
        _request: Option<payments::CustomerDetails>,
        _merchant_key_store: &domain::MerchantKeyStore,
        _storage_scheme: storage_enums::MerchantStorageScheme,
    ) -> errors::CustomResult<
        (
            PaymentSessionUpdateOperation<'a, F>,
            Option<domain::Customer>,
        ),
        errors::StorageError,
    > {
        Ok((Box::new(self), None))
    }

    async fn payments_dynamic_tax_calculation<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentData<F>,
        _connector_call_type: &ConnectorCallType,
        business_profile: &domain::Profile,
        platform: &domain::Platform,
    ) -> errors::CustomResult<(), errors::ApiErrorResponse> {
        let is_tax_connector_enabled = business_profile.get_is_tax_connector_enabled();
        let skip_external_tax_calculation = payment_data
            .payment_intent
            .skip_external_tax_calculation
            .unwrap_or(false);
        if is_tax_connector_enabled && !skip_external_tax_calculation {
            let db = state.store.as_ref();

            let merchant_connector_id = business_profile
                .tax_connector_id
                .as_ref()
                .get_required_value("business_profile.tax_connector_id")?;

            #[cfg(feature = "v1")]
            let mca = db
                .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                    &business_profile.merchant_id,
                    merchant_connector_id,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )?;

            #[cfg(feature = "v2")]
            let mca = db
                .find_merchant_connector_account_by_id(merchant_connector_id, key_store)
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )?;

            let connector_data =
                api::TaxCalculateConnectorData::get_connector_by_name(&mca.connector_name)?;

            let router_data = core_utils::construct_payments_dynamic_tax_calculation_router_data(
                state,
                platform,
                payment_data,
                &mca,
            )
            .await?;
            let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
                api::CalculateTax,
                types::PaymentsTaxCalculationData,
                types::TaxCalculationResponseData,
            > = connector_data.connector.get_connector_integration();

            let response = services::execute_connector_processing_step(
                state,
                connector_integration,
                &router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            )
            .await
            .to_payment_failed_response()
            .attach_printable("Tax connector Response Failed")?;

            let tax_response = response.response.map_err(|err| {
                errors::ApiErrorResponse::ExternalConnectorError {
                    code: err.code,
                    message: err.message,
                    connector: connector_data.connector_name.clone().to_string(),
                    status_code: err.status_code,
                    reason: err.reason,
                }
            })?;

            let payment_method_type = payment_data
                .tax_data
                .clone()
                .map(|tax_data| tax_data.payment_method_type)
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("missing tax_data.payment_method_type")?;

            payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails {
                payment_method_type: Some(diesel_models::PaymentMethodTypeTax {
                    order_tax_amount: tax_response.order_tax_amount,
                    pmt: payment_method_type,
                }),
                default: None,
            });
            Ok(())
        } else {
            Ok(())
        }
    }

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

    async fn get_connector<'a>(
        &'a self,
        _platform: &domain::Platform,
        state: &SessionState,
        _request: &api::PaymentsDynamicTaxCalculationRequest,
        _payment_intent: &storage::PaymentIntent,
    ) -> errors::CustomResult<api::ConnectorChoice, errors::ApiErrorResponse> {
        helpers::get_connector_default(state, None).await
    }

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

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsDynamicTaxCalculationRequest>
    for PaymentSessionUpdate
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        _req_state: ReqState,
        mut payment_data: PaymentData<F>,
        _customer: Option<domain::Customer>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(PaymentSessionUpdateOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        // For Google Pay and Apple Pay, we don’t need to call the connector again; we can directly confirm the payment after tax_calculation. So, we update the required fields in the database during the update_tracker call.
        if payment_data.should_update_in_update_tracker() {
            let shipping_address = payment_data
                .tax_data
                .clone()
                .map(|tax_data| tax_data.shipping_details);
            let key_manager_state = state.into();

            let shipping_details = shipping_address
                .clone()
                .async_map(|shipping_details| {
                    create_encrypted_data(&key_manager_state, key_store, shipping_details)
                })
                .await
                .transpose()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to encrypt shipping details")?;

            let shipping_address = helpers::create_or_update_address_for_payment_by_request(
                state,
                shipping_address.map(From::from).as_ref(),
                payment_data.payment_intent.shipping_address_id.as_deref(),
                &payment_data.payment_intent.merchant_id,
                payment_data.payment_intent.customer_id.as_ref(),
                key_store,
                &payment_data.payment_intent.payment_id,
                storage_scheme,
            )
            .await?;

            let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::SessionResponseUpdate {
            tax_details: payment_data.payment_intent.tax_details.clone().ok_or(errors::ApiErrorResponse::InternalServerError).attach_printable("payment_intent.tax_details not found")?,
            shipping_address_id: shipping_address.map(|address| address.address_id),
            updated_by: payment_data.payment_intent.updated_by.clone(),
            shipping_details,
        };

            let db = &*state.store;
            let payment_intent = payment_data.payment_intent.clone();

            let updated_payment_intent = db
                .update_payment_intent(
                    payment_intent,
                    payment_intent_update,
                    key_store,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

            payment_data.payment_intent = updated_payment_intent;
            Ok((Box::new(self), payment_data))
        } else {
            Ok((Box::new(self), payment_data))
        }
    }
}

impl<F: Send + Clone + Sync>
    ValidateRequest<F, api::PaymentsDynamicTaxCalculationRequest, PaymentData<F>>
    for PaymentSessionUpdate
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &api::PaymentsDynamicTaxCalculationRequest,
        platform: &'a domain::Platform,
    ) -> RouterResult<(
        PaymentSessionUpdateOperation<'b, F>,
        operations::ValidateResult,
    )> {
        //paymentid is already generated and should be sent in the request
        let given_payment_id = request.payment_id.clone();

        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: platform.get_processor().get_account().get_id().to_owned(),
                payment_id: api::PaymentIdType::PaymentIntentId(given_payment_id),
                storage_scheme: platform.get_processor().get_account().storage_scheme,
                requeue: false,
            },
        ))
    }
}


// File: crates/router/src/core/payments/operations/payment_get.rs

use api_models::{enums::FrmSuggestion, payments::PaymentsRetrieveRequest};
use async_trait::async_trait;
use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use hyperswitch_domain_models::payments::PaymentStatusData;
use router_env::{instrument, tracing};

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

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

impl ValidateStatusForOperation for PaymentGet {
    /// 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::RequiresCapture
            | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture
            | common_enums::IntentStatus::RequiresCustomerAction
            | common_enums::IntentStatus::RequiresMerchantAction
            | common_enums::IntentStatus::Processing
            | common_enums::IntentStatus::Succeeded
            | common_enums::IntentStatus::Failed
            | common_enums::IntentStatus::PartiallyCapturedAndCapturable
            | common_enums::IntentStatus::PartiallyCaptured
            | common_enums::IntentStatus::Cancelled
            | common_enums::IntentStatus::CancelledPostCapture
            | common_enums::IntentStatus::Conflicted
            | common_enums::IntentStatus::Expired => Ok(()),
            // These statuses are not valid for this operation
            common_enums::IntentStatus::RequiresConfirmation
            | common_enums::IntentStatus::RequiresPaymentMethod => {
                Err(errors::ApiErrorResponse::PaymentUnexpectedState {
                    current_flow: format!("{self:?}"),
                    field_name: "status".to_string(),
                    current_value: intent_status.to_string(),
                    states: [
                        common_enums::IntentStatus::RequiresCapture,
                        common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture,
                        common_enums::IntentStatus::RequiresCustomerAction,
                        common_enums::IntentStatus::RequiresMerchantAction,
                        common_enums::IntentStatus::Processing,
                        common_enums::IntentStatus::Succeeded,
                        common_enums::IntentStatus::Failed,
                        common_enums::IntentStatus::PartiallyCapturedAndCapturable,
                        common_enums::IntentStatus::PartiallyCaptured,
                        common_enums::IntentStatus::Cancelled,
                    ]
                    .map(|enum_value| enum_value.to_string())
                    .join(", "),
                })
            }
        }
    }
}

type BoxedConfirmOperation<'b, F> =
    super::BoxedOperation<'b, F, PaymentsRetrieveRequest, PaymentStatusData<F>>;

// TODO: change the macro to include changes for v2
// TODO: PaymentData in the macro should be an input
impl<F: Send + Clone + Sync> Operation<F, PaymentsRetrieveRequest> for &PaymentGet {
    type Data = PaymentStatusData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<&(dyn ValidateRequest<F, PaymentsRetrieveRequest, Self::Data> + Send + Sync)>
    {
        Ok(*self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsRetrieveRequest> + Send + Sync)> {
        Ok(*self)
    }
    fn to_domain(&self) -> RouterResult<&(dyn Domain<F, PaymentsRetrieveRequest, Self::Data>)> {
        Ok(*self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsRetrieveRequest> + Send + Sync)>
    {
        Ok(*self)
    }
}
#[automatically_derived]
impl<F: Send + Clone + Sync> Operation<F, PaymentsRetrieveRequest> for PaymentGet {
    type Data = PaymentStatusData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<&(dyn ValidateRequest<F, PaymentsRetrieveRequest, Self::Data> + Send + Sync)>
    {
        Ok(self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsRetrieveRequest> + Send + Sync)> {
        Ok(self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, PaymentsRetrieveRequest, Self::Data>> {
        Ok(self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsRetrieveRequest> + Send + Sync)>
    {
        Ok(self)
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, PaymentsRetrieveRequest, PaymentStatusData<F>>
    for PaymentGet
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        _request: &PaymentsRetrieveRequest,
        platform: &'a domain::Platform,
    ) -> RouterResult<operations::ValidateResult> {
        let validate_result = operations::ValidateResult {
            merchant_id: platform.get_processor().get_account().get_id().to_owned(),
            storage_scheme: platform.get_processor().get_account().storage_scheme,
            requeue: false,
        };

        Ok(validate_result)
    }
}

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentStatusData<F>, PaymentsRetrieveRequest>
    for PaymentGet
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &common_utils::id_type::GlobalPaymentId,
        request: &PaymentsRetrieveRequest,
        platform: &domain::Platform,
        _profile: &domain::Profile,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<operations::GetTrackerResponse<PaymentStatusData<F>>> {
        let db = &*state.store;

        let storage_scheme = platform.get_processor().get_account().storage_scheme;

        let payment_intent = db
            .find_payment_intent_by_id(
                payment_id,
                platform.get_processor().get_key_store(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        self.validate_status_for_operation(payment_intent.status)?;

        let active_attempt_id = payment_intent.active_attempt_id.as_ref().ok_or_else(|| {
            errors::ApiErrorResponse::MissingRequiredField {
                field_name: ("active_attempt_id"),
            }
        })?;

        let mut payment_attempt = db
            .find_payment_attempt_by_id(
                platform.get_processor().get_key_store(),
                active_attempt_id,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Could not find payment attempt given the attempt id")?;

        payment_attempt.encoded_data = request
            .param
            .as_ref()
            .map(|val| masking::Secret::new(val.clone()));

        let should_sync_with_connector =
            request.force_sync && payment_intent.status.should_force_sync_with_connector();

        // We need the address here to send it in the response
        let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new(
            payment_intent
                .shipping_address
                .clone()
                .map(|address| address.into_inner()),
            payment_intent
                .billing_address
                .clone()
                .map(|address| address.into_inner()),
            payment_attempt
                .payment_method_billing_address
                .clone()
                .map(|address| address.into_inner()),
            Some(true),
        );

        let attempts = match request.expand_attempts {
            true => payment_intent
                .active_attempt_id
                .as_ref()
                .async_map(|active_attempt| async {
                    db.find_payment_attempts_by_payment_intent_id(
                        payment_id,
                        platform.get_processor().get_key_store(),
                        storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Could not find payment attempts for the given the intent id")
                })
                .await
                .transpose()?,
            false => None,
        };

        let merchant_connector_details = request.merchant_connector_details.clone();

        let payment_data = PaymentStatusData {
            flow: std::marker::PhantomData,
            payment_intent,
            payment_attempt,
            payment_address,
            attempts,
            should_sync_with_connector,
            merchant_connector_details,
        };

        let get_trackers_response = operations::GetTrackerResponse { payment_data };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, PaymentsRetrieveRequest, PaymentStatusData<F>>
    for PaymentGet
{
    async fn get_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentStatusData<F>,
        merchant_key_store: &domain::MerchantKeyStore,
        storage_scheme: storage_enums::MerchantStorageScheme,
    ) -> CustomResult<(BoxedConfirmOperation<'a, 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(&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 PaymentStatusData<F>,
        _storage_scheme: storage_enums::MerchantStorageScheme,
        _key_store: &domain::MerchantKeyStore,
        _customer: &Option<domain::Customer>,
        _business_profile: &domain::Profile,
        _should_retry_with_pan: bool,
    ) -> RouterResult<(
        BoxedConfirmOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Ok((Box::new(self), None, None))
    }

    #[instrument(skip_all)]
    async fn perform_routing<'a>(
        &'a self,
        _platform: &domain::Platform,
        _business_profile: &domain::Profile,
        state: &SessionState,
        // TODO: do not take the whole payment data here
        payment_data: &mut PaymentStatusData<F>,
    ) -> CustomResult<ConnectorCallType, errors::ApiErrorResponse> {
        let payment_attempt = &payment_data.payment_attempt;

        if payment_data.should_sync_with_connector {
            let connector = payment_attempt
                .connector
                .as_ref()
                .get_required_value("connector")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Connector is none when constructing response")?;

            let merchant_connector_id = payment_attempt
                .merchant_connector_id
                .as_ref()
                .get_required_value("merchant_connector_id")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Merchant connector id is none when constructing response")?;

            let connector_data = api::ConnectorData::get_connector_by_name(
                &state.conf.connectors,
                connector,
                api::GetToken::Connector,
                Some(merchant_connector_id.to_owned()),
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Invalid connector name received")?;

            Ok(ConnectorCallType::PreDetermined(
                api::ConnectorRoutingData::from(connector_data),
            ))
        } else {
            Ok(ConnectorCallType::Skip)
        }
    }

    #[cfg(feature = "v2")]
    async fn get_connector_from_request<'a>(
        &'a self,
        state: &SessionState,
        request: &PaymentsRetrieveRequest,
        payment_data: &mut PaymentStatusData<F>,
    ) -> CustomResult<api::ConnectorData, errors::ApiErrorResponse> {
        use crate::core::payments::OperationSessionSetters;

        let connector_data = helpers::get_connector_data_from_request(
            state,
            request.merchant_connector_details.clone(),
        )
        .await?;

        payment_data
            .set_connector_in_payment_attempt(Some(connector_data.connector_name.to_string()));
        Ok(connector_data)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentStatusData<F>, PaymentsRetrieveRequest>
    for PaymentGet
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        _state: &'b SessionState,
        _req_state: ReqState,
        payment_data: PaymentStatusData<F>,
        _customer: Option<domain::Customer>,
        _storage_scheme: storage_enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        _key_store: &domain::MerchantKeyStore,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(BoxedConfirmOperation<'b, F>, PaymentStatusData<F>)>
    where
        F: 'b + Send,
    {
        Ok((Box::new(self), payment_data))
    }
}


// File: crates/router/src/core/payments/operations/payment_confirm_intent.rs

use api_models::{enums::FrmSuggestion, payments::PaymentsConfirmIntentRequest};
use async_trait::async_trait;
use common_utils::{ext_traits::Encode, fp_utils::when, id_type, types::keymanager::ToEncryptable};
use error_stack::ResultExt;
use hyperswitch_domain_models::payments::PaymentConfirmData;
use hyperswitch_interfaces::api::ConnectorSpecifications;
use masking::{ExposeOptionInterface, PeekInterface};
use router_env::{instrument, tracing};

use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        admin,
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        payment_methods,
        payments::{
            self, call_decision_manager, helpers,
            operations::{self, ValidateStatusForOperation},
            populate_surcharge_details, CustomerDetails, OperationSessionSetters, PaymentAddress,
            PaymentData,
        },
        utils as core_utils,
    },
    routes::{app::ReqState, SessionState},
    services::{self, connector_integration_interface::ConnectorEnum},
    types::{
        self,
        api::{self, ConnectorCallType, PaymentIdTypeExt},
        domain::{self, types as domain_types},
        storage::{self, enums as storage_enums},
    },
    utils::{self, OptionExt},
};

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

impl ValidateStatusForOperation for PaymentIntentConfirm {
    /// 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::Succeeded
            | common_enums::IntentStatus::Conflicted
            | common_enums::IntentStatus::Failed
            | 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::Expired => {
                Err(errors::ApiErrorResponse::PaymentUnexpectedState {
                    current_flow: format!("{self:?}"),
                    field_name: "status".to_string(),
                    current_value: intent_status.to_string(),
                    states: ["requires_payment_method".to_string()].join(", "),
                })
            }
        }
    }
}

type BoxedConfirmOperation<'b, F> =
    super::BoxedOperation<'b, F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>;

// TODO: change the macro to include changes for v2
// TODO: PaymentData in the macro should be an input
impl<F: Send + Clone + Sync> Operation<F, PaymentsConfirmIntentRequest> for &PaymentIntentConfirm {
    type Data = PaymentConfirmData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<
        &(dyn ValidateRequest<F, PaymentsConfirmIntentRequest, Self::Data> + Send + Sync),
    > {
        Ok(*self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(*self)
    }
    fn to_domain(
        &self,
    ) -> RouterResult<&(dyn Domain<F, PaymentsConfirmIntentRequest, Self::Data>)> {
        Ok(*self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(*self)
    }
}
#[automatically_derived]
impl<F: Send + Clone + Sync> Operation<F, PaymentsConfirmIntentRequest> for PaymentIntentConfirm {
    type Data = PaymentConfirmData<F>;
    fn to_validate_request(
        &self,
    ) -> RouterResult<
        &(dyn ValidateRequest<F, PaymentsConfirmIntentRequest, Self::Data> + Send + Sync),
    > {
        Ok(self)
    }
    fn to_get_tracker(
        &self,
    ) -> RouterResult<&(dyn GetTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, PaymentsConfirmIntentRequest, Self::Data>> {
        Ok(self)
    }
    fn to_update_tracker(
        &self,
    ) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, PaymentsConfirmIntentRequest> + Send + Sync)>
    {
        Ok(self)
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &PaymentsConfirmIntentRequest,
        platform: &'a domain::Platform,
    ) -> RouterResult<operations::ValidateResult> {
        let validate_result = operations::ValidateResult {
            merchant_id: platform.get_processor().get_account().get_id().to_owned(),
            storage_scheme: platform.get_processor().get_account().storage_scheme,
            requeue: false,
        };

        Ok(validate_result)
    }
}

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentConfirmData<F>, PaymentsConfirmIntentRequest>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &id_type::GlobalPaymentId,
        request: &PaymentsConfirmIntentRequest,
        platform: &domain::Platform,
        profile: &domain::Profile,
        header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<operations::GetTrackerResponse<PaymentConfirmData<F>>> {
        let db = &*state.store;
        let key_manager_state = &state.into();

        let storage_scheme = platform.get_processor().get_account().storage_scheme;

        let payment_intent = db
            .find_payment_intent_by_id(
                payment_id,
                platform.get_processor().get_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 cell_id = state.conf.cell_information.id.clone();

        let batch_encrypted_data = domain_types::crypto_operation(
            key_manager_state,
            common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt),
            domain_types::CryptoOperation::BatchEncrypt(
                hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable(
                    hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt {
                        payment_method_billing_address: request.payment_method_data.billing.as_ref().map(|address| address.clone().encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new),
                    },
                ),
            ),
            common_utils::types::keymanager::Identifier::Merchant(platform.get_processor().get_account().get_id().to_owned()),
            platform.get_processor().get_key_store().key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while encrypting payment intent details".to_string())?;

        let encrypted_data =
             hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed while encrypting payment intent details")?;

        let payment_attempt_domain_model =
            hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt::create_domain_model(
                &payment_intent,
                cell_id,
                storage_scheme,
                request,
                encrypted_data
            )
            .await?;

        let payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt =
            db.insert_payment_attempt(
                platform.get_processor().get_key_store(),
                payment_attempt_domain_model,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Could not insert payment attempt")?;

        let payment_method_data = request
            .payment_method_data
            .payment_method_data
            .clone()
            .map(hyperswitch_domain_models::payment_method_data::PaymentMethodData::from);

        if request.payment_token.is_some() {
            when(
                !matches!(
                    payment_method_data,
                    Some(domain::payment_method_data::PaymentMethodData::CardToken(_))
                ),
                || {
                    Err(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "payment_method_data",
                    })
                    .attach_printable(
                        "payment_method_data should be card_token when a token is passed",
                    )
                },
            )?;
        };

        let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new(
            payment_intent
                .shipping_address
                .clone()
                .map(|address| address.into_inner()),
            payment_intent
                .billing_address
                .clone()
                .map(|address| address.into_inner()),
            payment_attempt
                .payment_method_billing_address
                .clone()
                .map(|address| address.into_inner()),
            Some(true),
        );

        let merchant_connector_details = request.merchant_connector_details.clone();

        let payment_data = PaymentConfirmData {
            flow: std::marker::PhantomData,
            payment_intent,
            payment_attempt,
            payment_method_data,
            payment_address,
            mandate_data: None,
            payment_method: None,
            merchant_connector_details,
            external_vault_pmd: None,
            webhook_url: request
                .webhook_url
                .as_ref()
                .map(|url| url.get_string_repr().to_string()),
        };

        let get_trackers_response = operations::GetTrackerResponse { payment_data };

        Ok(get_trackers_response)
    }

    #[instrument(skip_all)]
    async fn get_trackers_for_split_payments<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &id_type::GlobalPaymentId,
        request: &PaymentsConfirmIntentRequest,
        platform: &domain::Platform,
        profile: &domain::Profile,
        header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
        split_amount_data: (
            api_models::payments::PaymentMethodData,
            common_utils::types::MinorUnit,
        ),
        attempts_group_id: &id_type::GlobalAttemptGroupId,
    ) -> RouterResult<operations::GetTrackerResponse<PaymentConfirmData<F>>> {
        let db = &*state.store;
        let key_manager_state = &state.into();

        let storage_scheme = platform.get_processor().get_account().storage_scheme;

        let payment_intent = db
            .find_payment_intent_by_id(
                payment_id,
                platform.get_processor().get_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 cell_id = state.conf.cell_information.id.clone();

        let batch_encrypted_data = domain_types::crypto_operation(
            key_manager_state,
            common_utils::type_name!(hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt),
            domain_types::CryptoOperation::BatchEncrypt(
                hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::to_encryptable(
                    hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt {
                        payment_method_billing_address: request.payment_method_data.billing.as_ref().map(|address| address.clone().encode_to_value()).transpose().change_context(errors::ApiErrorResponse::InternalServerError).attach_printable("Failed to encode payment_method_billing address")?.map(masking::Secret::new),
                    },
                ),
            ),
            common_utils::types::keymanager::Identifier::Merchant(platform.get_processor().get_account().get_id().to_owned()),
            platform.get_processor().get_key_store().key.peek(),
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while encrypting payment intent details".to_string())?;

        let encrypted_data =
             hyperswitch_domain_models::payments::payment_attempt::FromRequestEncryptablePaymentAttempt::from_encryptable(batch_encrypted_data)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed while encrypting payment intent details")?;

        let payment_attempt_domain_model =
            crate::core::split_payments::create_domain_model_for_split_payment(
                &payment_intent,
                cell_id,
                storage_scheme,
                request,
                encrypted_data,
                split_amount_data.1,
                attempts_group_id,
            )
            .await?;

        let payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt =
            db.insert_payment_attempt(
                platform.get_processor().get_key_store(),
                payment_attempt_domain_model,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Could not insert payment attempt")?;

        let payment_method_data =
            hyperswitch_domain_models::payment_method_data::PaymentMethodData::from(
                split_amount_data.0,
            );

        let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new(
            payment_intent
                .shipping_address
                .clone()
                .map(|address| address.into_inner()),
            payment_intent
                .billing_address
                .clone()
                .map(|address| address.into_inner()),
            payment_attempt
                .payment_method_billing_address
                .clone()
                .map(|address| address.into_inner()),
            Some(true),
        );

        let merchant_connector_details = request.merchant_connector_details.clone();

        let payment_data = PaymentConfirmData {
            flow: std::marker::PhantomData,
            payment_intent,
            payment_attempt,
            payment_method_data: Some(payment_method_data),
            payment_address,
            mandate_data: None,
            payment_method: None,
            merchant_connector_details,
            external_vault_pmd: None,
            webhook_url: request
                .webhook_url
                .as_ref()
                .map(|url| url.get_string_repr().to_string()),
        };

        let get_trackers_response = operations::GetTrackerResponse { payment_data };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, PaymentsConfirmIntentRequest, PaymentConfirmData<F>>
    for PaymentIntentConfirm
{
    async fn get_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        merchant_key_store: &domain::MerchantKeyStore,
        storage_scheme: storage_enums::MerchantStorageScheme,
    ) -> CustomResult<(BoxedConfirmOperation<'a, 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(&id, merchant_key_store, storage_scheme)
                    .await?;

                Ok((Box::new(self), Some(customer)))
            }
            None => Ok((Box::new(self), None)),
        }
    }

    async fn run_decision_manager<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        business_profile: &domain::Profile,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let authentication_type = payment_data.payment_intent.authentication_type;

        let authentication_type = match business_profile.three_ds_decision_manager_config.as_ref() {
            Some(three_ds_decision_manager_config) => call_decision_manager(
                state,
                three_ds_decision_manager_config.clone(),
                payment_data,
            )?,
            None => authentication_type,
        };

        if let Some(auth_type) = authentication_type {
            payment_data.payment_attempt.authentication_type = auth_type;
        }

        Ok(())
    }

    #[instrument(skip_all)]
    async fn make_pm_data<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        key_store: &domain::MerchantKeyStore,
        customer: &Option<domain::Customer>,
        business_profile: &domain::Profile,
        _should_retry_with_pan: bool,
    ) -> RouterResult<(
        BoxedConfirmOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Ok((Box::new(self), None, None))
    }

    #[cfg(feature = "v2")]
    async fn perform_routing<'a>(
        &'a self,
        platform: &domain::Platform,
        business_profile: &domain::Profile,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<ConnectorCallType, errors::ApiErrorResponse> {
        payments::connector_selection(state, platform, business_profile, payment_data, None).await
    }

    #[instrument(skip_all)]
    async fn populate_payment_data<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentConfirmData<F>,
        _platform: &domain::Platform,
        business_profile: &domain::Profile,
        connector_data: &api::ConnectorData,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let connector_request_reference_id = connector_data
            .connector
            .generate_connector_request_reference_id(
                &payment_data.payment_intent,
                &payment_data.payment_attempt,
            );
        payment_data.set_connector_request_reference_id(Some(connector_request_reference_id));
        Ok(())
    }

    #[cfg(feature = "v2")]
    async fn create_or_fetch_payment_method<'a>(
        &'a self,
        state: &SessionState,
        platform: &domain::Platform,
        business_profile: &domain::Profile,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let (payment_method, payment_method_data) = match (
            &payment_data.payment_attempt.payment_token,
            &payment_data.payment_method_data,
            &payment_data.payment_attempt.customer_acceptance,
        ) {
            (
                Some(payment_token),
                Some(domain::payment_method_data::PaymentMethodData::CardToken(card_token)),
                None,
            ) => {
                let (card_cvc, card_holder_name) = {
                    (
                        card_token
                            .card_cvc
                            .clone()
                            .ok_or(errors::ApiErrorResponse::InvalidDataValue {
                                field_name: "card_cvc",
                            })
                            .or(
                                payment_methods::vault::retrieve_and_delete_cvc_from_payment_token(
                                    state,
                                    payment_token,
                                    payment_data.payment_attempt.payment_method_type,
                                    platform.get_processor().get_key_store().key.get_inner(),
                                )
                                .await,
                            )
                            .attach_printable("card_cvc not provided")?,
                        card_token.card_holder_name.clone(),
                    )
                };

                let (payment_method, vault_data) =
                    payment_methods::vault::retrieve_payment_method_from_vault_using_payment_token(
                        state,
                        platform,
                        business_profile,
                        payment_token,
                        &payment_data.payment_attempt.payment_method_type,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to retrieve payment method from vault")?;

                match vault_data {
                    domain::vault::PaymentMethodVaultingData::Card(card_detail) => {
                        let pm_data_from_vault =
                            domain::payment_method_data::PaymentMethodData::Card(
                                domain::payment_method_data::Card::from((
                                    card_detail,
                                    card_cvc,
                                    card_holder_name,
                                )),
                            );

                        (Some(payment_method), Some(pm_data_from_vault))
                    }
                    _ => Err(errors::ApiErrorResponse::NotImplemented {
                        message: errors::NotImplementedMessage::Reason(
                            "Non-card Tokenization not implemented".to_string(),
                        ),
                    })?,
                }
            }

            (Some(_payment_token), _, _) => Err(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "payment_method_data",
            })
            .attach_printable("payment_method_data should be card_token when a token is passed")?,

            (None, Some(domain::PaymentMethodData::Card(card)), Some(_customer_acceptance)) => {
                let customer_id = match &payment_data.payment_intent.customer_id {
                    Some(customer_id) => customer_id.clone(),
                    None => {
                        return Err(errors::ApiErrorResponse::InvalidDataValue {
                            field_name: "customer_id",
                        })
                        .attach_printable("customer_id not provided");
                    }
                };

                let pm_create_data =
                    api::PaymentMethodCreateData::Card(api::CardDetail::from(card.clone()));

                let req = api::PaymentMethodCreate {
                    payment_method_type: payment_data.payment_attempt.payment_method_type,
                    payment_method_subtype: payment_data.payment_attempt.payment_method_subtype,
                    metadata: None,
                    customer_id,
                    payment_method_data: pm_create_data,
                    billing: None,
                    psp_tokenization: None,
                    network_tokenization: None,
                };

                let (_pm_response, payment_method) =
                    Box::pin(payment_methods::create_payment_method_core(
                        state,
                        &state.get_req_state(),
                        req,
                        platform,
                        business_profile,
                    ))
                    .await?;

                // Don't modify payment_method_data in this case, only the payment_method and payment_method_id
                (Some(payment_method), None)
            }
            _ => (None, None), // Pass payment_data unmodified for any other case
        };

        if let Some(pm_data) = payment_method_data {
            payment_data.update_payment_method_data(pm_data);
        }
        if let Some(pm) = payment_method {
            payment_data.update_payment_method_and_pm_id(pm.get_id().clone(), pm);
        }

        Ok(())
    }

    #[cfg(feature = "v2")]
    async fn get_connector_from_request<'a>(
        &'a self,
        state: &SessionState,
        request: &PaymentsConfirmIntentRequest,
        payment_data: &mut PaymentConfirmData<F>,
    ) -> CustomResult<api::ConnectorData, errors::ApiErrorResponse> {
        let connector_data = helpers::get_connector_data_from_request(
            state,
            request.merchant_connector_details.clone(),
        )
        .await?;
        payment_data
            .set_connector_in_payment_attempt(Some(connector_data.connector_name.to_string()));
        Ok(connector_data)
    }

    async fn get_connector_tokenization_action<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &PaymentConfirmData<F>,
    ) -> RouterResult<payments::TokenizationAction> {
        let connector = payment_data.payment_attempt.connector.to_owned();

        let is_connector_mandate_flow = payment_data
            .mandate_data
            .as_ref()
            .and_then(|mandate_details| mandate_details.mandate_reference_id.as_ref())
            .map(|mandate_reference| match mandate_reference {
                api_models::payments::MandateReferenceId::ConnectorMandateId(_) => true,
                api_models::payments::MandateReferenceId::NetworkMandateId(_)
                | api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_) => false,
            })
            .unwrap_or(false);

        let tokenization_action = match connector {
            Some(_) if is_connector_mandate_flow => {
                payments::TokenizationAction::SkipConnectorTokenization
            }
            Some(connector) => {
                let payment_method = payment_data
                    .payment_attempt
                    .get_payment_method()
                    .ok_or_else(|| errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Payment method not found")?;
                let payment_method_type: Option<common_enums::PaymentMethodType> =
                    payment_data.payment_attempt.get_payment_method_type();

                let mandate_flow_enabled = payment_data.payment_intent.setup_future_usage;

                let is_connector_tokenization_enabled =
                    payments::is_payment_method_tokenization_enabled_for_connector(
                        state,
                        &connector,
                        payment_method,
                        payment_method_type,
                        mandate_flow_enabled,
                    )?;

                if is_connector_tokenization_enabled {
                    payments::TokenizationAction::TokenizeInConnector
                } else {
                    payments::TokenizationAction::SkipConnectorTokenization
                }
            }
            None => payments::TokenizationAction::SkipConnectorTokenization,
        };

        Ok(tokenization_action)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentConfirmData<F>, PaymentsConfirmIntentRequest>
    for PaymentIntentConfirm
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        req_state: ReqState,
        mut payment_data: PaymentConfirmData<F>,
        customer: Option<domain::Customer>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        updated_customer: Option<storage::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        frm_suggestion: Option<FrmSuggestion>,
        header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(BoxedConfirmOperation<'b, F>, PaymentConfirmData<F>)>
    where
        F: 'b + Send,
    {
        let db = &*state.store;

        let intent_status = common_enums::IntentStatus::Processing;
        let attempt_status = common_enums::AttemptStatus::Pending;

        let connector = payment_data
            .payment_attempt
            .connector
            .clone()
            .get_required_value("connector")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Connector is none when constructing response")?;

        // If `merchant_connector_details` are present in the payment request, `merchant_connector_id` will not be populated.
        let merchant_connector_id = match &payment_data.merchant_connector_details {
            Some(_details) => None,
            None => Some(
                payment_data
                    .payment_attempt
                    .merchant_connector_id
                    .clone()
                    .get_required_value("merchant_connector_id")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Merchant connector id is none when constructing response")?,
            ),
        };

        let payment_intent_update =
            hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntent {
                status: intent_status,
                updated_by: storage_scheme.to_string(),
                active_attempt_id: Some(payment_data.payment_attempt.id.clone()),
            };

        let authentication_type = payment_data.payment_attempt.authentication_type;

        let connector_request_reference_id = payment_data
            .payment_attempt
            .connector_request_reference_id
            .clone();

        // Updates payment_attempt for cases where authorize flow is not performed.
        let connector_response_reference_id = payment_data
            .payment_attempt
            .connector_response_reference_id
            .clone();

        let payment_attempt_update = match &payment_data.payment_method {
            // In the case of a tokenized payment method, we update the payment attempt with the tokenized payment method details.
            Some(payment_method) => {
                hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentTokenized {
                    status: attempt_status,
                    updated_by: storage_scheme.to_string(),
                    connector,
                    merchant_connector_id: merchant_connector_id.ok_or_else( || {
                        error_stack::report!(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Merchant connector id is none when constructing response")
                    })?,
                    authentication_type,
                    connector_request_reference_id,
                    payment_method_id : payment_method.get_id().clone()
                }
            }
            None => {
                hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent {
                    status: attempt_status,
                    updated_by: storage_scheme.to_string(),
                    connector,
                    merchant_connector_id,
                    authentication_type,
                    connector_request_reference_id,
                    connector_response_reference_id,
                }
            }
        };

        let updated_payment_intent = db
            .update_payment_intent(
                payment_data.payment_intent.clone(),
                payment_intent_update,
                key_store,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to update payment intent")?;

        payment_data.payment_intent = updated_payment_intent;

        let updated_payment_attempt = db
            .update_payment_attempt(
                key_store,
                payment_data.payment_attempt.clone(),
                payment_attempt_update,
                storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to update payment attempt")?;

        payment_data.payment_attempt = updated_payment_attempt;

        if let Some((customer, updated_customer)) = customer.zip(updated_customer) {
            let customer_id = customer.get_id().clone();
            let customer_merchant_id = customer.merchant_id.clone();

            let _updated_customer = db
                .update_customer_by_global_id(
                    &customer_id,
                    customer,
                    updated_customer,
                    key_store,
                    storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to update customer during `update_trackers`")?;
        }

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