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

use std::marker::PhantomData;

use api_models::{
    enums::FrmSuggestion, mandates::RecurringDetails, payments::RequestSurchargeDetails,
};
use async_trait::async_trait;
use common_utils::{
    ext_traits::{AsyncExt, Encode, ValueExt},
    pii::Email,
    types::keymanager::KeyManagerState,
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::payments::payment_intent::{
    CustomerData, PaymentIntentUpdateFields,
};
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        mandate::helpers as m_helpers,
        payment_methods::cards::create_encrypted_data,
        payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData},
        utils as core_utils,
    },
    events::audit_events::{AuditEvent, AuditEventType},
    routes::{app::ReqState, SessionState},
    services,
    types::{
        self,
        api::{self, ConnectorCallType, PaymentIdTypeExt},
        domain,
        storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt},
        transformers::ForeignTryFrom,
    },
    utils::OptionExt,
};

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

type PaymentUpdateOperation<'a, F> = BoxedOperation<'a, F, api::PaymentsRequest, PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for PaymentUpdate {
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        request: &api::PaymentsRequest,
        merchant_context: &domain::MerchantContext,
        auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<operations::GetTrackerResponse<'a, F, api::PaymentsRequest, PaymentData<F>>>
    {
        let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency);

        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
        let merchant_id = merchant_context.get_merchant_account().get_id();
        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;

        let db = &*state.store;
        let key_manager_state = &state.into();
        helpers::allow_payment_update_enabled_for_client_auth(merchant_id, state, auth_flow)
            .await?;
        payment_intent = db
            .find_payment_intent_by_payment_id_merchant_id(
                key_manager_state,
                &payment_id,
                merchant_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

        if let Some(order_details) = &request.order_details {
            helpers::validate_order_details_amount(
                order_details.to_owned(),
                payment_intent.amount,
                false,
            )?;
        }

        payment_intent.setup_future_usage = request
            .setup_future_usage
            .or(payment_intent.setup_future_usage);

        helpers::validate_customer_access(&payment_intent, auth_flow, request)?;

        helpers::validate_card_data(
            request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.payment_method_data.clone()),
        )?;

        helpers::validate_payment_status_against_allowed_statuses(
            payment_intent.status,
            &[
                storage_enums::IntentStatus::RequiresPaymentMethod,
                storage_enums::IntentStatus::RequiresConfirmation,
            ],
            "update",
        )?;
        helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?;

        payment_intent.order_details = request
            .get_order_details_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to convert order details to value")?
            .or(payment_intent.order_details);

        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 customer_acceptance = request.customer_acceptance.clone();
        let recurring_details = request.recurring_details.clone();

        let mandate_type = m_helpers::get_mandate_type(
            request.mandate_data.clone(),
            request.off_session,
            payment_intent.setup_future_usage,
            request.customer_acceptance.clone(),
            request.payment_token.clone(),
            payment_attempt.payment_method.or(request.payment_method),
        )
        .change_context(errors::ApiErrorResponse::MandateValidationFailed {
            reason: "Expected one out of recurring_details and mandate_data but got both".into(),
        })?;

        let m_helpers::MandateGenericData {
            token,
            payment_method,
            payment_method_type,
            mandate_data,
            recurring_mandate_payment_data,
            mandate_connector,
            payment_method_info,
        } = Box::pin(helpers::get_token_pm_type_mandate_details(
            state,
            request,
            mandate_type.to_owned(),
            merchant_context,
            None,
            payment_intent.customer_id.as_ref(),
        ))
        .await?;
        helpers::validate_amount_to_capture_and_capture_method(Some(&payment_attempt), request)?;

        helpers::validate_request_amount_and_amount_to_capture(
            request.amount,
            request.amount_to_capture,
            request
                .surcharge_details
                .or(payment_attempt.get_surcharge_details()),
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "amount_to_capture".to_string(),
            expected_format: "amount_to_capture lesser than or equal to amount".to_string(),
        })?;

        currency = request
            .currency
            .or(payment_attempt.currency)
            .get_required_value("currency")?;

        payment_attempt.payment_method = payment_method.or(payment_attempt.payment_method);
        payment_attempt.payment_method_type =
            payment_method_type.or(payment_attempt.payment_method_type);
        let customer_details = helpers::get_customer_details_from_request(request);

        let amount = request
            .amount
            .unwrap_or_else(|| payment_attempt.net_amount.get_order_amount().into());

        if request.confirm.unwrap_or(false) {
            helpers::validate_customer_id_mandatory_cases(
                request.setup_future_usage.is_some(),
                payment_intent
                    .customer_id
                    .as_ref()
                    .or(customer_details.customer_id.as_ref()),
            )?;
        }

        let shipping_address = helpers::create_or_update_address_for_payment_by_request(
            state,
            request.shipping.as_ref(),
            payment_intent.shipping_address_id.as_deref(),
            merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;
        let billing_address = helpers::create_or_update_address_for_payment_by_request(
            state,
            request.billing.as_ref(),
            payment_intent.billing_address_id.as_deref(),
            merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        let payment_method_billing = helpers::create_or_update_address_for_payment_by_request(
            state,
            request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.billing.as_ref()),
            payment_attempt.payment_method_billing_address_id.as_deref(),
            merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        payment_intent.shipping_address_id = shipping_address.clone().map(|x| x.address_id);
        payment_intent.billing_address_id = billing_address.clone().map(|x| x.address_id);
        payment_attempt.payment_method_billing_address_id = payment_method_billing
            .as_ref()
            .map(|payment_method_billing| payment_method_billing.address_id.clone());

        payment_intent.allowed_payment_method_types = request
            .get_allowed_payment_method_types_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error converting allowed_payment_types to Value")?
            .or(payment_intent.allowed_payment_method_types);

        payment_intent.connector_metadata = request
            .get_connector_metadata_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error converting connector_metadata to Value")?
            .or(payment_intent.connector_metadata);

        payment_intent.feature_metadata = request
            .get_feature_metadata_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error converting feature_metadata to Value")?
            .or(payment_intent.feature_metadata);
        payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata);
        payment_intent.frm_metadata = request.frm_metadata.clone().or(payment_intent.frm_metadata);
        payment_intent.psd2_sca_exemption_type = request
            .psd2_sca_exemption_type
            .or(payment_intent.psd2_sca_exemption_type);
        Self::populate_payment_intent_with_request(&mut payment_intent, request);

        let token = token.or_else(|| payment_attempt.payment_token.clone());

        if request.confirm.unwrap_or(false) {
            helpers::validate_pm_or_token_given(
                &request.payment_method,
                &request
                    .payment_method_data
                    .as_ref()
                    .and_then(|pmd| pmd.payment_method_data.clone()),
                &request.payment_method_type,
                &mandate_type,
                &token,
                &request.ctp_service_details,
            )?;
        }

        let token_data = if let Some(token) = token.clone() {
            Some(helpers::retrieve_payment_token_data(state, token, payment_method).await?)
        } else {
            None
        };

        let mandate_id = request
            .mandate_id
            .as_ref()
            .or_else(|| {
            request.recurring_details
                .as_ref()
                .and_then(|recurring_details| match recurring_details {
                    RecurringDetails::MandateId(id) => Some(id),
                    _ => None,
                })
        })
            .async_and_then(|mandate_id| async {
                let mandate = db
                    .find_mandate_by_merchant_id_mandate_id(merchant_id, mandate_id, merchant_context.get_merchant_account().storage_scheme)
                    .await
                    .change_context(errors::ApiErrorResponse::MandateNotFound);
                Some(mandate.and_then(|mandate_obj| {
                    match (
                        mandate_obj.network_transaction_id,
                        mandate_obj.connector_mandate_ids,
                    ) {
                        (Some(network_tx_id), _) => Ok(api_models::payments::MandateIds {
                            mandate_id: Some(mandate_obj.mandate_id),
                            mandate_reference_id: Some(
                                api_models::payments::MandateReferenceId::NetworkMandateId(
                                    network_tx_id,
                                ),
                            ),
                        }),
                        (_, Some(connector_mandate_id)) => connector_mandate_id
                        .parse_value("ConnectorMandateId")
                        .change_context(errors::ApiErrorResponse::MandateNotFound)
                        .map(|connector_id: api_models::payments::ConnectorMandateReferenceId| {
                            api_models::payments::MandateIds {
                                mandate_id: Some(mandate_obj.mandate_id),
                                mandate_reference_id: Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
                                    api_models::payments::ConnectorMandateReferenceId::new(
                                        connector_id.get_connector_mandate_id(),        // connector_mandate_id
                                        connector_id.get_payment_method_id(),           // payment_method_id
                                        None,                                     // update_history
                                        connector_id.get_mandate_metadata(),            // mandate_metadata
                                        connector_id.get_connector_mandate_request_reference_id()  // connector_mandate_request_reference_id
                                    )
                                ))
                            }
                         }),
                        (_, _) => Ok(api_models::payments::MandateIds {
                            mandate_id: Some(mandate_obj.mandate_id),
                            mandate_reference_id: None,
                        }),
                    }
                }))
            })
            .await
            .transpose()?;
        let (next_operation, amount): (PaymentUpdateOperation<'a, F>, _) =
            if request.confirm.unwrap_or(false) {
                let amount = {
                    let amount = request
                        .amount
                        .map(Into::into)
                        .unwrap_or(payment_attempt.net_amount.get_order_amount());
                    payment_attempt.net_amount.set_order_amount(amount);
                    payment_intent.amount = amount;
                    let surcharge_amount = request
                        .surcharge_details
                        .as_ref()
                        .map(RequestSurchargeDetails::get_total_surcharge_amount)
                        .or(payment_attempt.get_total_surcharge_amount());
                    amount + surcharge_amount.unwrap_or_default()
                };
                (Box::new(operations::PaymentConfirm), amount.into())
            } else {
                (Box::new(self), amount)
            };

        payment_intent.status = if request
            .payment_method_data
            .as_ref()
            .is_some_and(|payment_method_data| payment_method_data.payment_method_data.is_some())
        {
            if request.confirm.unwrap_or(false) {
                payment_intent.status
            } else {
                storage_enums::IntentStatus::RequiresConfirmation
            }
        } else {
            storage_enums::IntentStatus::RequiresPaymentMethod
        };

        payment_intent.request_external_three_ds_authentication = request
            .request_external_three_ds_authentication
            .or(payment_intent.request_external_three_ds_authentication);

        payment_intent.merchant_order_reference_id = request
            .merchant_order_reference_id
            .clone()
            .or(payment_intent.merchant_order_reference_id);

        Self::populate_payment_attempt_with_request(&mut payment_attempt, request);

        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,
                    merchant_context.get_merchant_account().get_id(),
                    mcd,
                )
                .await
            })
            .await
            .transpose()?;

        // The operation merges mandate data from both request and payment_attempt
        let setup_mandate = mandate_data;
        let mandate_details_present =
            payment_attempt.mandate_details.is_some() || request.mandate_data.is_some();
        helpers::validate_mandate_data_and_future_usage(
            payment_intent.setup_future_usage,
            mandate_details_present,
        )?;
        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(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;

        let surcharge_details = request.surcharge_details.map(|request_surcharge_details| {
            payments::types::SurchargeDetails::from((&request_surcharge_details, &payment_attempt))
        });

        payment_intent.force_3ds_challenge = request
            .force_3ds_challenge
            .or(payment_intent.force_3ds_challenge);

        payment_intent.payment_channel = request
            .payment_channel
            .clone()
            .or(payment_intent.payment_channel);

        payment_intent.enable_partial_authorization = request
            .enable_partial_authorization
            .or(payment_intent.enable_partial_authorization);

        helpers::validate_overcapture_request(
            &request.enable_overcapture,
            &payment_attempt.capture_method,
        )?;
        payment_intent.enable_overcapture = request.enable_overcapture;

        let payment_data = PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount,
            email: request.email.clone(),
            mandate_id,
            mandate_connector,
            token,
            token_data,
            setup_mandate,
            customer_acceptance,
            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,
            ),
            confirm: request.confirm,
            payment_method_data: request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.payment_method_data.clone().map(Into::into)),
            payment_method_token: None,
            payment_method_info,
            force_sync: None,
            all_keys_required: None,
            refunds: vec![],
            disputes: vec![],
            attempts: None,
            sessions_token: vec![],
            card_cvc: request.card_cvc.clone(),
            creds_identifier,
            pm_token: None,
            connector_customer_id: None,
            recurring_mandate_payment_data,
            ephemeral_key: None,
            multiple_capture_data: None,
            redirect_response: None,
            surcharge_details,
            frm_message: None,
            payment_link_data: None,
            incremental_authorization_details: None,
            authorizations: vec![],
            authentication: None,
            recurring_details,
            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: None,
        };

        let get_trackers_response = operations::GetTrackerResponse {
            operation: next_operation,
            customer_details: Some(customer_details),
            payment_data,
            business_profile,
            mandate_type,
        };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, api::PaymentsRequest, PaymentData<F>> for PaymentUpdate {
    #[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: common_enums::enums::MerchantStorageScheme,
    ) -> CustomResult<(PaymentUpdateOperation<'a, F>, 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
    }

    async fn payments_dynamic_tax_calculation<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentData<F>,
        _connector_call_type: &ConnectorCallType,
        business_profile: &domain::Profile,
        merchant_context: &domain::MerchantContext,
    ) -> 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 key_manager_state: &KeyManagerState = &state.into();

            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(
                    key_manager_state,
                    &business_profile.merchant_id,
                    merchant_connector_id,
                    merchant_context.get_merchant_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(
                    key_manager_state,
                    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,
                merchant_context,
                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
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .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,
                }
            })?;

            payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails {
                default: Some(diesel_models::DefaultTax {
                    order_tax_amount: tax_response.order_tax_amount,
                }),
                payment_method_type: 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<(
        PaymentUpdateOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Box::pin(helpers::make_pm_data(
            Box::new(self),
            state,
            payment_data,
            merchant_key_store,
            customer,
            storage_scheme,
            business_profile,
            should_retry_with_pan,
        ))
        .await
    }

    #[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> {
        Ok(())
    }

    async fn get_connector<'a>(
        &'a self,
        _merchant_context: &domain::MerchantContext,
        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,
        _merchant_context: &domain::MerchantContext,
        _payment_data: &mut PaymentData<F>,
    ) -> CustomResult<bool, errors::ApiErrorResponse> {
        Ok(false)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for PaymentUpdate {
    #[cfg(feature = "v2")]
    #[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<(PaymentUpdateOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[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<(PaymentUpdateOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        let is_payment_method_unavailable =
            payment_data.payment_attempt.payment_method_id.is_none()
                && payment_data.payment_intent.status
                    == storage_enums::IntentStatus::RequiresPaymentMethod;

        let payment_method = payment_data.payment_attempt.payment_method;

        let get_attempt_status = || {
            if is_payment_method_unavailable {
                storage_enums::AttemptStatus::PaymentMethodAwaited
            } else {
                storage_enums::AttemptStatus::ConfirmationAwaited
            }
        };
        let profile_id = payment_data
            .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 additional_pm_data = payment_data
            .payment_method_data
            .as_ref()
            .async_map(|payment_method_data| async {
                helpers::get_additional_payment_data(payment_method_data, &*state.store, profile_id)
                    .await
            })
            .await
            .transpose()?
            .flatten();

        let encoded_pm_data = additional_pm_data
            .as_ref()
            .map(Encode::encode_to_value)
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to encode additional pm data")?;

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

        let payment_method_type = payment_data.payment_attempt.payment_method_type;
        let payment_experience = payment_data.payment_attempt.payment_experience;
        let amount_to_capture = payment_data.payment_attempt.amount_to_capture;
        let capture_method = payment_data.payment_attempt.capture_method;
        let payment_method_billing_address_id = payment_data
            .payment_attempt
            .payment_method_billing_address_id
            .clone();

        let surcharge_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.surcharge_amount);
        let tax_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.tax_on_surcharge_amount);
        let network_transaction_id = payment_data.payment_attempt.network_transaction_id.clone();
        payment_data.payment_attempt = state
            .store
            .update_payment_attempt_with_attempt_id(
                payment_data.payment_attempt,
                storage::PaymentAttemptUpdate::Update {
                    currency: payment_data.currency,
                    status: get_attempt_status(),
                    authentication_type: None,
                    payment_method,
                    payment_token: payment_data.token.clone(),
                    payment_method_data: encoded_pm_data,
                    payment_experience,
                    payment_method_type,
                    business_sub_label,
                    amount_to_capture,
                    capture_method,
                    fingerprint_id: None,
                    payment_method_billing_address_id,
                    updated_by: storage_scheme.to_string(),
                    network_transaction_id,
                    net_amount:
                        hyperswitch_domain_models::payments::payment_attempt::NetAmount::new(
                            payment_data.amount.into(),
                            None,
                            None,
                            surcharge_amount,
                            tax_amount,
                        ),
                },
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        let customer_id = customer.clone().map(|c| c.customer_id);

        let intent_status = {
            let current_intent_status = payment_data.payment_intent.status;
            if is_payment_method_unavailable {
                storage_enums::IntentStatus::RequiresPaymentMethod
            } else if !payment_data.confirm.unwrap_or(true)
                || current_intent_status == storage_enums::IntentStatus::RequiresCustomerAction
            {
                storage_enums::IntentStatus::RequiresConfirmation
            } else {
                payment_data.payment_intent.status
            }
        };

        let (shipping_address, billing_address) = (
            payment_data.payment_intent.shipping_address_id.clone(),
            payment_data.payment_intent.billing_address_id.clone(),
        );

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

        let return_url = payment_data.payment_intent.return_url.clone();
        let setup_future_usage = payment_data.payment_intent.setup_future_usage;
        let business_label = payment_data.payment_intent.business_label.clone();
        let business_country = payment_data.payment_intent.business_country;
        let description = payment_data.payment_intent.description.clone();
        let statement_descriptor_name = payment_data
            .payment_intent
            .statement_descriptor_name
            .clone();
        let statement_descriptor_suffix = payment_data
            .payment_intent
            .statement_descriptor_suffix
            .clone();
        let key_manager_state = state.into();
        let billing_details = payment_data
            .address
            .get_payment_billing()
            .async_map(|billing_details| {
                create_encrypted_data(&key_manager_state, key_store, billing_details)
            })
            .await
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt billing details")?;

        let shipping_details = payment_data
            .address
            .get_shipping()
            .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 order_details = payment_data.payment_intent.order_details.clone();
        let metadata = payment_data.payment_intent.metadata.clone();
        let frm_metadata = payment_data.payment_intent.frm_metadata.clone();
        let session_expiry = payment_data.payment_intent.session_expiry;
        let merchant_order_reference_id = payment_data
            .payment_intent
            .merchant_order_reference_id
            .clone();
        payment_data.payment_intent = state
            .store
            .update_payment_intent(
                &state.into(),
                payment_data.payment_intent.clone(),
                storage::PaymentIntentUpdate::Update(Box::new(PaymentIntentUpdateFields {
                    amount: payment_data.amount.into(),
                    currency: payment_data.currency,
                    setup_future_usage,
                    status: intent_status,
                    customer_id: customer_id.clone(),
                    shipping_address_id: shipping_address,
                    billing_address_id: billing_address,
                    return_url,
                    business_country,
                    business_label,
                    description,
                    statement_descriptor_name,
                    statement_descriptor_suffix,
                    order_details,
                    metadata,
                    payment_confirm_source: None,
                    updated_by: storage_scheme.to_string(),
                    fingerprint_id: None,
                    session_expiry,
                    request_external_three_ds_authentication: payment_data
                        .payment_intent
                        .request_external_three_ds_authentication,
                    frm_metadata,
                    customer_details,
                    merchant_order_reference_id,
                    billing_details,
                    shipping_details,
                    is_payment_processor_token_flow: None,
                    tax_details: None,
                    force_3ds_challenge: payment_data.payment_intent.force_3ds_challenge,
                    is_iframe_redirection_enabled: payment_data
                        .payment_intent
                        .is_iframe_redirection_enabled,
                    is_confirm_operation: false, // this is not a confirm operation
                    payment_channel: payment_data.payment_intent.payment_channel,
                    feature_metadata: payment_data
                        .payment_intent
                        .feature_metadata
                        .clone()
                        .map(masking::Secret::new),
                    tax_status: payment_data.payment_intent.tax_status,
                    discount_amount: payment_data.payment_intent.discount_amount,
                    order_date: payment_data.payment_intent.order_date,
                    shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax,
                    duty_amount: payment_data.payment_intent.duty_amount,
                    enable_partial_authorization: payment_data
                        .payment_intent
                        .enable_partial_authorization,
                    enable_overcapture: payment_data.payment_intent.enable_overcapture,
                })),
                key_store,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        let amount = payment_data.amount;
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentUpdate { amount }))
            .with(payment_data.to_event())
            .emit();

        Ok((
            payments::is_confirm(self, payment_data.confirm),
            payment_data,
        ))
    }
}

impl ForeignTryFrom<domain::Customer> for CustomerData {
    type Error = errors::ApiErrorResponse;
    fn foreign_try_from(value: domain::Customer) -> Result<Self, Self::Error> {
        Ok(Self {
            name: value.name.map(|name| name.into_inner()),
            email: value.email.map(Email::from),
            phone: value.phone.map(|ph| ph.into_inner()),
            phone_country_code: value.phone_country_code,
            tax_registration_id: value
                .tax_registration_id
                .map(|tax_registration_id| tax_registration_id.into_inner()),
        })
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, api::PaymentsRequest, PaymentData<F>>
    for PaymentUpdate
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &api::PaymentsRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<(PaymentUpdateOperation<'b, F>, operations::ValidateResult)> {
        helpers::validate_customer_information(request)?;

        if let Some(amount) = request.amount {
            helpers::validate_max_amount(amount)?;
        }
        if let Some(session_expiry) = &request.session_expiry {
            helpers::validate_session_expiry(session_expiry.to_owned())?;
        }
        let payment_id = request
            .payment_id
            .clone()
            .ok_or(report!(errors::ApiErrorResponse::PaymentNotFound))?;

        let request_merchant_id = request.merchant_id.as_ref();
        helpers::validate_merchant_id(
            merchant_context.get_merchant_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(),
        })?;

        helpers::validate_request_amount_and_amount_to_capture(
            request.amount,
            request.amount_to_capture,
            request.surcharge_details,
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "amount_to_capture".to_string(),
            expected_format: "amount_to_capture lesser than or equal to amount".to_string(),
        })?;

        helpers::validate_payment_method_fields_present(request)?;

        let _mandate_type = helpers::validate_mandate(request, false)?;

        helpers::validate_recurring_details_and_token(
            &request.recurring_details,
            &request.payment_token,
            &request.mandate_id,
        )?;
        let _request_straight_through: Option<api::routing::StraightThroughAlgorithm> = request
            .routing
            .clone()
            .map(|val| val.parse_value("RoutingAlgorithm"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidRequestData {
                message: "Invalid straight through routing rules format".to_string(),
            })
            .attach_printable("Invalid straight through routing rules format")?;

        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
                payment_id,
                storage_scheme: merchant_context.get_merchant_account().storage_scheme,
                requeue: matches!(
                    request.retry_action,
                    Some(api_models::enums::RetryAction::Requeue)
                ),
            },
        ))
    }
}

impl PaymentUpdate {
    fn populate_payment_attempt_with_request(
        payment_attempt: &mut storage::PaymentAttempt,
        request: &api::PaymentsRequest,
    ) {
        request
            .business_sub_label
            .clone()
            .map(|bsl| payment_attempt.business_sub_label.replace(bsl));
        request
            .payment_method_type
            .map(|pmt| payment_attempt.payment_method_type.replace(pmt));
        request
            .payment_experience
            .map(|experience| payment_attempt.payment_experience.replace(experience));
        payment_attempt.amount_to_capture = request


// Struct: MpgsAuthType
// File: crates/hyperswitch_connectors/src/connectors/mpgs/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct MpgsAuthType


// Function: delete_by_address_id
// File: crates/diesel_models/src/query/address.rs
// Module: diesel_models

pub fn delete_by_address_id(
        conn: &PgPooledConn,
        address_id: &str,
    ) -> StorageResult<bool>


// File: crates/hyperswitch_domain_models/src/gsm.rs
// Module: hyperswitch_domain_models
// Public structs: 2

use common_utils::{errors::ValidationError, ext_traits::StringExt};
use serde::{self, Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct GatewayStatusMap {
    pub connector: String,
    pub flow: String,
    pub sub_flow: String,
    pub code: String,
    pub message: String,
    pub status: String,
    pub router_error: Option<String>,
    pub unified_code: Option<String>,
    pub unified_message: Option<String>,
    pub error_category: Option<common_enums::ErrorCategory>,
    pub feature_data: common_types::domain::GsmFeatureData,
    pub feature: common_enums::GsmFeature,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GatewayStatusMappingUpdate {
    pub status: Option<String>,
    pub router_error: Option<Option<String>>,
    pub decision: Option<common_enums::GsmDecision>,
    pub step_up_possible: Option<bool>,
    pub unified_code: Option<String>,
    pub unified_message: Option<String>,
    pub error_category: Option<common_enums::ErrorCategory>,
    pub clear_pan_possible: Option<bool>,
    pub feature_data: Option<common_types::domain::GsmFeatureData>,
    pub feature: Option<common_enums::GsmFeature>,
}

impl TryFrom<GatewayStatusMap> for diesel_models::gsm::GatewayStatusMappingNew {
    type Error = error_stack::Report<ValidationError>;

    fn try_from(value: GatewayStatusMap) -> Result<Self, Self::Error> {
        Ok(Self {
            connector: value.connector.to_string(),
            flow: value.flow,
            sub_flow: value.sub_flow,
            code: value.code,
            message: value.message,
            status: value.status.clone(),
            router_error: value.router_error,
            decision: value.feature_data.get_decision().to_string(),
            step_up_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|retry_feature_data| retry_feature_data.is_step_up_possible())
                .unwrap_or(false),
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            clear_pan_possible: value
                .feature_data
                .get_retry_feature_data()
                .map(|retry_feature_data| retry_feature_data.is_clear_pan_possible())
                .unwrap_or(false),
            feature_data: Some(value.feature_data),
            feature: Some(value.feature),
        })
    }
}

impl TryFrom<GatewayStatusMappingUpdate> for diesel_models::gsm::GatewayStatusMappingUpdate {
    type Error = error_stack::Report<ValidationError>;

    fn try_from(value: GatewayStatusMappingUpdate) -> Result<Self, Self::Error> {
        Ok(Self {
            status: value.status,
            router_error: value.router_error,
            decision: value.decision.map(|gsm_decision| gsm_decision.to_string()),
            step_up_possible: value.step_up_possible,
            unified_code: value.unified_code,
            unified_message: value.unified_message,
            error_category: value.error_category,
            clear_pan_possible: value.clear_pan_possible,
            feature_data: value.feature_data,
            feature: value.feature,
        })
    }
}

impl TryFrom<diesel_models::gsm::GatewayStatusMap> for GatewayStatusMap {
    type Error = ValidationError;

    fn try_from(item: diesel_models::gsm::GatewayStatusMap) -> Result<Self, Self::Error> {
        let decision =
            StringExt::<common_enums::GsmDecision>::parse_enum(item.decision, "GsmDecision")
                .map_err(|_| ValidationError::InvalidValue {
                    message: "Failed to parse GsmDecision".to_string(),
                })?;
        let db_feature_data = item.feature_data;

        // The only case where `FeatureData` can be null is for legacy records
        // (i.e., records created before `FeatureData` and related features were introduced).
        // At that time, the only supported feature was `Retry`, so it's safe to default to it.
        let feature_data = match db_feature_data {
            Some(common_types::domain::GsmFeatureData::Retry(data)) => {
                common_types::domain::GsmFeatureData::Retry(data)
            }
            None => common_types::domain::GsmFeatureData::Retry(
                common_types::domain::RetryFeatureData {
                    step_up_possible: item.step_up_possible,
                    clear_pan_possible: item.clear_pan_possible,
                    alternate_network_possible: false,
                    decision,
                },
            ),
        };

        let feature = item.feature.unwrap_or(common_enums::GsmFeature::Retry);
        Ok(Self {
            connector: item.connector,
            flow: item.flow,
            sub_flow: item.sub_flow,
            code: item.code,
            message: item.message,
            status: item.status,
            router_error: item.router_error,
            unified_code: item.unified_code,
            unified_message: item.unified_message,
            error_category: item.error_category,
            feature_data,
            feature,
        })
    }
}


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

use std::marker::PhantomData;

use api_models::enums::{AttemptStatus, FrmSuggestion, IntentStatus};
use async_trait::async_trait;
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};

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

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

type PaymentApproveOperation<'a, F> =
    BoxedOperation<'a, F, api::PaymentsCaptureRequest, PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentData<F>, api::PaymentsCaptureRequest>
    for PaymentApprove
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        _request: &api::PaymentsCaptureRequest,
        merchant_context: &domain::MerchantContext,
        _auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<
        operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData<F>>,
    > {
        let db = &*state.store;
        let key_manager_state = &state.into();
        let merchant_id = merchant_context.get_merchant_account().get_id();
        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
        let (mut payment_intent, payment_attempt, currency, amount);

        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

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

        helpers::validate_payment_status_against_not_allowed_statuses(
            payment_intent.status,
            &[IntentStatus::Failed, IntentStatus::Succeeded],
            "approve",
        )?;

        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 = state
            .store
            .find_business_profile_by_profile_id(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;

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

        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(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        let billing_address = helpers::get_address_by_id(
            state,
            payment_intent.billing_address_id.clone(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        let payment_method_billing = helpers::get_address_by_id(
            state,
            payment_attempt.payment_method_billing_address_id.clone(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id);
        payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id);

        let frm_response = if cfg!(feature = "frm") {
            db.find_fraud_check_by_payment_id(payment_intent.payment_id.clone(), merchant_context.get_merchant_account().get_id().clone())
                .await
                .change_context(errors::ApiErrorResponse::PaymentNotFound)
                .attach_printable_lazy(|| {
                    format!("Error while retrieving frm_response, merchant_id: {}, payment_id: {attempt_id}", merchant_context.get_merchant_account().get_id().get_string_repr())
                })
                .ok()
        } else {
            None
        };

        let payment_data = PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount,
            email: None,
            mandate_id: None,
            mandate_connector: None,
            setup_mandate: None,
            customer_acceptance: None,
            token: None,
            token_data: 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,
            ),
            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: frm_response,
            payment_link_data: None,
            incremental_authorization_details: None,
            authorizations: vec![],
            authentication: None,
            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: 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 + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsCaptureRequest>
    for PaymentApprove
{
    #[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<(PaymentApproveOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        if matches!(frm_suggestion, Some(FrmSuggestion::FrmAuthorizeTransaction)) {
            payment_data.payment_intent.status = IntentStatus::RequiresCapture; // In Approve flow, payment which has payment_capture_method "manual" and attempt status as "Unresolved",
            payment_data.payment_attempt.status = AttemptStatus::Authorized; // We shouldn't call the connector instead we need to update the payment attempt and payment intent.
        }
        let intent_status_update = storage::PaymentIntentUpdate::ApproveUpdate {
            status: payment_data.payment_intent.status,
            merchant_decision: Some(api_models::enums::MerchantDecision::Approved.to_string()),
            updated_by: storage_scheme.to_string(),
        };
        payment_data.payment_intent = state
            .store
            .update_payment_intent(
                &state.into(),
                payment_data.payment_intent,
                intent_status_update,
                key_store,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        state
            .store
            .update_payment_attempt_with_attempt_id(
                payment_data.payment_attempt.clone(),
                storage::PaymentAttemptUpdate::StatusUpdate {
                    status: payment_data.payment_attempt.status,
                    updated_by: storage_scheme.to_string(),
                },
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentApprove))
            .with(payment_data.to_event())
            .emit();

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

impl<F: Send + Clone + Sync> ValidateRequest<F, api::PaymentsCaptureRequest, PaymentData<F>>
    for PaymentApprove
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &api::PaymentsCaptureRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<(PaymentApproveOperation<'b, F>, operations::ValidateResult)> {
        let request_merchant_id = request.merchant_id.as_ref();
        helpers::validate_merchant_id(
            merchant_context.get_merchant_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: merchant_context.get_merchant_account().get_id().to_owned(),
                payment_id: api::PaymentIdType::PaymentIntentId(request.payment_id.clone()),
                storage_scheme: merchant_context.get_merchant_account().storage_scheme,
                requeue: false,
            },
        ))
    }
}


// Struct: GocardlessCustomerResponse
// File: crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct GocardlessCustomerResponse


// Function: list_countries_currencies_for_connector_payment_method_util
// File: crates/router/src/core/payment_methods/cards.rs
// Module: router

pub fn list_countries_currencies_for_connector_payment_method_util(
    connector_filters: settings::ConnectorFilters,
    connector: api_enums::Connector,
    payment_method_type: api_enums::PaymentMethodType,
) -> ListCountriesCurrenciesResponse


// Implementation: impl RoutingEventsResponse
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Methods: 3 total (3 public)

impl RoutingEventsResponse


// File: crates/hyperswitch_connectors/src/connectors/bitpay.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 1

pub mod transformers;
use std::sync::LazyLock;

use api_models::webhooks::IncomingWebhookEvent;
use common_enums::enums;
use common_utils::{
    errors::{CustomResult, ReportSwitchExt},
    ext_traits::ByteSliceExt,
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
        ConnectorValidation,
    },
    configs::Connectors,
    consts, errors,
    events::connector_api_logs::ConnectorEvent,
    types::{PaymentsAuthorizeType, PaymentsSyncType, Response},
    webhooks,
};
use masking::{Mask, PeekInterface};
use transformers as bitpay;

use self::bitpay::BitpayWebhookDetails;
use crate::{constants::headers, types::ResponseRouterData, utils};

#[derive(Clone)]
pub struct Bitpay {
    amount_converter: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
}

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

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

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Bitpay
{
    // Not Implemented (R)
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Bitpay
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        _req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let header = vec![
            (
                headers::CONTENT_TYPE.to_string(),
                PaymentsAuthorizeType::get_content_type(self)
                    .to_string()
                    .into(),
            ),
            (
                headers::X_ACCEPT_VERSION.to_string(),
                "2.0.0".to_string().into(),
            ),
        ];
        Ok(header)
    }
}

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

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

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

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

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

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

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

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response
                .code
                .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
            message: response.error,
            reason: response.message,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Bitpay {}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Bitpay {
    //TODO: implement sessions flow
}

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

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Bitpay {
    fn build_request(
        &self,
        _req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(
            errors::ConnectorError::NotImplemented("Setup Mandate flow for Bitpay".to_string())
                .into(),
        )
    }
}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Bitpay {
    fn get_headers(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/invoices", self.base_url(connectors)))
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;

        let connector_router_data = bitpay::BitpayRouterData::from((amount, req));
        let connector_req = bitpay::BitpayPaymentsRequest::try_from(&connector_router_data)?;

        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?)
                .set_body(PaymentsAuthorizeType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: bitpay::BitpayPaymentsResponse = res
            .response
            .parse_struct("Bitpay PaymentsAuthorizeResponse")
            .switch()?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Bitpay {
    fn get_headers(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let auth = bitpay::BitpayAuthType::try_from(&req.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let connector_id = req
            .request
            .connector_transaction_id
            .get_connector_transaction_id()
            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
        Ok(format!(
            "{}/invoices/{}?token={}",
            self.base_url(connectors),
            connector_id,
            auth.api_key.peek(),
        ))
    }

    fn build_request(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&PaymentsSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(PaymentsSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        let response: bitpay::BitpayPaymentsResponse = res
            .response
            .parse_struct("bitpay PaymentsSyncResponse")
            .switch()?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Bitpay {
    fn build_request(
        &self,
        _req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(errors::ConnectorError::FlowNotSupported {
            flow: "Capture".to_string(),
            connector: "Bitpay".to_string(),
        }
        .into())
    }
}

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

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Bitpay {
    fn build_request(
        &self,
        _req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(
            errors::ConnectorError::NotImplemented("Refund flow not Implemented".to_string())
                .into(),
        )
    }
}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Bitpay {
    // default implementation of build_request method will be executed
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Bitpay {
    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let notif: BitpayWebhookDetails = request
            .body
            .parse_struct("BitpayWebhookDetails")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
            api_models::payments::PaymentIdType::ConnectorTransactionId(notif.data.id),
        ))
    }

    fn get_webhook_event_type(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        let notif: BitpayWebhookDetails = request
            .body
            .parse_struct("BitpayWebhookDetails")
            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
        match notif.event.name {
            bitpay::WebhookEventType::Confirmed | bitpay::WebhookEventType::Completed => {
                Ok(IncomingWebhookEvent::PaymentIntentSuccess)
            }
            bitpay::WebhookEventType::Paid => Ok(IncomingWebhookEvent::PaymentIntentProcessing),
            bitpay::WebhookEventType::Declined => Ok(IncomingWebhookEvent::PaymentIntentFailure),
            bitpay::WebhookEventType::Unknown
            | bitpay::WebhookEventType::Expired
            | bitpay::WebhookEventType::Invalid
            | bitpay::WebhookEventType::Refunded
            | bitpay::WebhookEventType::Resent => Ok(IncomingWebhookEvent::EventNotSupported),
        }
    }

    fn get_webhook_resource_object(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let notif: BitpayWebhookDetails = request
            .body
            .parse_struct("BitpayWebhookDetails")
            .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
        Ok(Box::new(notif))
    }
}
static BITPAY_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> = LazyLock::new(|| {
    let supported_capture_methods = vec![enums::CaptureMethod::Automatic];

    let mut bitpay_supported_payment_methods = SupportedPaymentMethods::new();

    bitpay_supported_payment_methods.add(
        enums::PaymentMethod::Crypto,
        enums::PaymentMethodType::CryptoCurrency,
        PaymentMethodDetails {
            mandates: enums::FeatureStatus::NotSupported,
            refunds: enums::FeatureStatus::Supported,
            supported_capture_methods,
            specific_features: None,
        },
    );

    bitpay_supported_payment_methods
});

static BITPAY_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
        display_name: "Bitpay",
        description:
            "BitPay is a cryptocurrency payment processor that enables businesses to accept Bitcoin and other digital currencies ",
        connector_type: enums::HyperswitchConnectorCategory::AlternativePaymentMethod,
        integration_status: enums::ConnectorIntegrationStatus::Sandbox,
    };

static BITPAY_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 1] = [enums::EventClass::Payments];

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

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

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


// Function: perform_pre_authentication
// File: crates/router/src/core/authentication.rs
// Module: router

pub fn perform_pre_authentication(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    card: hyperswitch_domain_models::payment_method_data::Card,
    token: String,
    business_profile: &domain::Profile,
    acquirer_details: Option<types::AcquirerDetails>,
    payment_id: common_utils::id_type::PaymentId,
    organization_id: common_utils::id_type::OrganizationId,
    force_3ds_challenge: Option<bool>,
    psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
) -> CustomResult<
    hyperswitch_domain_models::router_request_types::authentication::AuthenticationStore,
    ApiErrorResponse,
>


// File: crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs
// Module: hyperswitch_connectors
// Public structs: 10

use common_enums::enums;
use common_utils::{request::Method, types::FloatMajorUnit};
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::PaymentsAuthorizeRequestData,
};

#[derive(Debug, Serialize)]
pub struct BitpayRouterData<T> {
    pub amount: FloatMajorUnit,
    pub router_data: T,
}

impl<T> From<(FloatMajorUnit, T)> for BitpayRouterData<T> {
    fn from((amount, router_data): (FloatMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TransactionSpeed {
    Low,
    #[default]
    Medium,
    High,
}

#[derive(Default, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BitpayPaymentsRequest {
    price: FloatMajorUnit,
    currency: String,
    #[serde(rename = "redirectURL")]
    redirect_url: String,
    #[serde(rename = "notificationURL")]
    notification_url: String,
    transaction_speed: TransactionSpeed,
    token: Secret<String>,
    order_id: String,
}

impl TryFrom<&BitpayRouterData<&types::PaymentsAuthorizeRouterData>> for BitpayPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        get_crypto_specific_payment_data(item)
    }
}

// Auth Struct
pub struct BitpayAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for BitpayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BitpayPaymentStatus {
    #[default]
    New,
    Paid,
    Confirmed,
    Complete,
    Expired,
    Invalid,
}

impl From<BitpayPaymentStatus> for enums::AttemptStatus {
    fn from(item: BitpayPaymentStatus) -> Self {
        match item {
            BitpayPaymentStatus::New => Self::AuthenticationPending,
            BitpayPaymentStatus::Complete | BitpayPaymentStatus::Confirmed => Self::Charged,
            BitpayPaymentStatus::Expired => Self::Failure,
            _ => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ExceptionStatus {
    #[default]
    Unit,
    Bool(bool),
    String(String),
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BitpayPaymentResponseData {
    pub url: Option<url::Url>,
    pub status: BitpayPaymentStatus,
    pub price: FloatMajorUnit,
    pub currency: String,
    pub amount_paid: FloatMajorUnit,
    pub invoice_time: Option<FloatMajorUnit>,
    pub rate_refresh_time: Option<i64>,
    pub expiration_time: Option<i64>,
    pub current_time: Option<i64>,
    pub id: String,
    pub order_id: Option<String>,
    pub low_fee_detected: Option<bool>,
    pub display_amount_paid: Option<String>,
    pub exception_status: ExceptionStatus,
    pub redirect_url: Option<String>,
    pub refund_address_request_pending: Option<bool>,
    pub merchant_name: Option<Secret<String>>,
    pub token: Option<Secret<String>>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct BitpayPaymentsResponse {
    data: BitpayPaymentResponseData,
    facade: Option<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, BitpayPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BitpayPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let redirection_data = item
            .response
            .data
            .url
            .map(|x| RedirectForm::from((x, Method::Get)));
        let connector_id = ResponseId::ConnectorTransactionId(item.response.data.id.clone());
        let attempt_status = item.response.data.status;
        Ok(Self {
            status: enums::AttemptStatus::from(attempt_status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: connector_id,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .data
                    .order_id
                    .or(Some(item.response.data.id)),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct BitpayRefundRequest {
    pub amount: FloatMajorUnit,
}

impl<F> TryFrom<&BitpayRouterData<&types::RefundsRouterData<F>>> for BitpayRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BitpayRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount,
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Processing => Self::Pending,
            //TODO: Review mapping
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for types::RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct BitpayErrorResponse {
    pub error: String,
    pub code: Option<String>,
    pub message: Option<String>,
}

fn get_crypto_specific_payment_data(
    item: &BitpayRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<BitpayPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
    let price = item.amount;
    let currency = item.router_data.request.currency.to_string();
    let redirect_url = item.router_data.request.get_router_return_url()?;
    let notification_url = item.router_data.request.get_webhook_url()?;
    let transaction_speed = TransactionSpeed::Medium;
    let auth_type = item.router_data.connector_auth_type.clone();
    let token = match auth_type {
        ConnectorAuthType::HeaderKey { api_key } => api_key,
        _ => String::default().into(),
    };
    let order_id = item.router_data.connector_request_reference_id.clone();

    Ok(BitpayPaymentsRequest {
        price,
        currency,
        redirect_url,
        notification_url,
        transaction_speed,
        token,
        order_id,
    })
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BitpayWebhookDetails {
    pub event: Event,
    pub data: BitpayPaymentResponseData,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Event {
    pub code: i64,
    pub name: WebhookEventType,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum WebhookEventType {
    #[serde(rename = "invoice_paidInFull")]
    Paid,
    #[serde(rename = "invoice_confirmed")]
    Confirmed,
    #[serde(rename = "invoice_completed")]
    Completed,
    #[serde(rename = "invoice_expired")]
    Expired,
    #[serde(rename = "invoice_failedToConfirm")]
    Invalid,
    #[serde(rename = "invoice_declined")]
    Declined,
    #[serde(rename = "invoice_refundComplete")]
    Refunded,
    #[serde(rename = "invoice_manuallyNotified")]
    Resent,
    #[serde(other)]
    Unknown,
}


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

impl api::Refund for for Katapult


// Function: retrieve_merchant_account
// File: crates/openapi/src/routes/merchant_account.rs
// Module: openapi

pub fn retrieve_merchant_account()


// Function: default_output
// File: crates/euclid/src/frontend/ast/parser.rs
// Module: euclid

pub fn default_output<O: EuclidParsable + 'static>(input: &str) -> ParseResult<&str, O>


// Implementation: impl IncomingWebhook for for Paybox
// File: crates/hyperswitch_connectors/src/connectors/paybox.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl IncomingWebhook for for Paybox


// Function: set_error
// File: crates/hyperswitch_interfaces/src/events/routing_api_logs.rs
// Module: hyperswitch_interfaces

// Documentation: fn set_error

pub fn set_error(&mut self, error: serde_json::Value)


// Function: get_id
// File: crates/hyperswitch_domain_models/src/lib.rs
// Module: hyperswitch_domain_models

pub fn get_id(&self) -> String


// Implementation: impl RefundMetricAccumulator for for CountAccumulator
// File: crates/analytics/src/refunds/accumulator.rs
// Module: analytics
// Methods: 2 total (0 public)

impl RefundMetricAccumulator for for CountAccumulator