File: crates/router/src/core/unified_connector_service.rs
 Public functions: 15

use std::{str::FromStr, time::Instant};

use api_models::admin;
#[cfg(feature = "v2")]
use base64::Engine;
use common_enums::{connector_enums::Connector, AttemptStatus, GatewaySystem, PaymentMethodType};
#[cfg(feature = "v2")]
use common_utils::consts::BASE64_ENGINE;
use common_utils::{errors::CustomResult, ext_traits::ValueExt};
use diesel_models::types::FeatureMetadata;
use error_stack::ResultExt;
use external_services::grpc_client::unified_connector_service::{
    ConnectorAuthMetadata, UnifiedConnectorServiceError,
};
use hyperswitch_connectors::utils::CardData;
#[cfg(feature = "v2")]
use hyperswitch_domain_models::merchant_connector_account::{
    ExternalVaultConnectorMetadata, MerchantConnectorAccountTypeDetails,
};
use hyperswitch_domain_models::{
    merchant_context::MerchantContext,
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_response_types::PaymentsResponseData,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use router_env::{instrument, logger, tracing};
use unified_connector_service_cards::CardNumber;
use unified_connector_service_client::payments::{
    self as payments_grpc, payment_method::PaymentMethod, CardDetails, CardPaymentMethodType,
    PaymentServiceAuthorizeResponse, RewardPaymentMethodType,
};

#[cfg(feature = "v2")]
use crate::types::api::enums as api_enums;
use crate::{
    consts,
    core::{
        errors::{self, RouterResult},
        payments::{
            helpers::{
                is_ucs_enabled, should_execute_based_on_rollout, MerchantConnectorAccountType,
            },
            OperationSessionGetters, OperationSessionSetters,
        },
        utils::get_flow_name,
    },
    events::connector_api_logs::ConnectorEvent,
    routes::SessionState,
    types::transformers::ForeignTryFrom,
    utils,
};

pub mod transformers;

// Re-export webhook transformer types for easier access
pub use transformers::WebhookTransformData;

/// Generic version of should_call_unified_connector_service that works with any type
/// implementing OperationSessionGetters trait
pub async fn should_call_unified_connector_service<F: Clone, T, D>(
    state: &SessionState,
    merchant_context: &MerchantContext,
    router_data: &RouterData<F, T, PaymentsResponseData>,
    payment_data: Option<&D>,
) -> RouterResult<bool>
where
    D: OperationSessionGetters<F>,
{
    // Check basic UCS availability first
    if state.grpc_client.unified_connector_service_client.is_none() {
        router_env::logger::debug!(
            "Unified Connector Service client is not available, skipping UCS decision"
        );
        return Ok(false);
    }

    let ucs_config_key = consts::UCS_ENABLED;
    if !is_ucs_enabled(state, ucs_config_key).await {
        router_env::logger::debug!(
            "Unified Connector Service is not enabled, skipping UCS decision"
        );
        return Ok(false);
    }

    // Apply stickiness logic if payment_data is available
    if let Some(payment_data) = payment_data {
        let previous_gateway_system = extract_gateway_system_from_payment_intent(payment_data);

        match previous_gateway_system {
            Some(GatewaySystem::UnifiedConnectorService) => {
                // Payment intent previously used UCS, maintain stickiness to UCS
                router_env::logger::info!(
                    "Payment gateway system decision: UCS (sticky) - payment intent previously used UCS"
                );
                return Ok(true);
            }
            Some(GatewaySystem::Direct) => {
                // Payment intent previously used Direct, maintain stickiness to Direct (return false for UCS)
                router_env::logger::info!(
                    "Payment gateway system decision: Direct (sticky) - payment intent previously used Direct"
                );
                return Ok(false);
            }
            None => {
                // No previous gateway system set, continue with normal gateway system logic
                router_env::logger::debug!(
                    "UCS stickiness: No previous gateway system set, applying normal gateway system logic"
                );
            }
        }
    }

    // Continue with normal UCS gateway system logic
    let merchant_id = merchant_context
        .get_merchant_account()
        .get_id()
        .get_string_repr();

    let connector_name = router_data.connector.clone();
    let connector_enum = Connector::from_str(&connector_name)
        .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)?;

    let payment_method = router_data.payment_method.to_string();
    let flow_name = get_flow_name::<F>()?;

    let is_ucs_only_connector = state
        .conf
        .grpc_client
        .unified_connector_service
        .as_ref()
        .is_some_and(|config| config.ucs_only_connectors.contains(&connector_enum));

    if is_ucs_only_connector {
        router_env::logger::info!(
            "Payment gateway system decision: UCS (forced) - merchant_id={}, connector={}, payment_method={}, flow={}",
            merchant_id, connector_name, payment_method, flow_name
        );
        return Ok(true);
    }

    let config_key = format!(
        "{}_{}_{}_{}_{}",
        consts::UCS_ROLLOUT_PERCENT_CONFIG_PREFIX,
        merchant_id,
        connector_name,
        payment_method,
        flow_name
    );

    let should_execute = should_execute_based_on_rollout(state, &config_key).await?;

    // Log gateway system decision
    if should_execute {
        router_env::logger::info!(
            "Payment gateway system decision: UCS - merchant_id={}, connector={}, payment_method={}, flow={}",
            merchant_id, connector_name, payment_method, flow_name
        );
    } else {
        router_env::logger::info!(
            "Payment gateway system decision: Direct - merchant_id={}, connector={}, payment_method={}, flow={}",
            merchant_id, connector_name, payment_method, flow_name
        );
    }

    Ok(should_execute)
}

/// Extracts the gateway system from the payment intent's feature metadata
/// Returns None if metadata is missing, corrupted, or doesn't contain gateway_system
fn extract_gateway_system_from_payment_intent<F: Clone, D>(
    payment_data: &D,
) -> Option<GatewaySystem>
where
    D: OperationSessionGetters<F>,
{
    #[cfg(feature = "v1")]
    {
        payment_data
            .get_payment_intent()
            .feature_metadata
            .as_ref()
            .and_then(|metadata| {
                // Try to parse the JSON value as FeatureMetadata
                // Log errors but don't fail the flow for corrupted metadata
                match serde_json::from_value::<FeatureMetadata>(metadata.clone()) {
                    Ok(feature_metadata) => feature_metadata.gateway_system,
                    Err(err) => {
                        router_env::logger::warn!(
                            "Failed to parse feature_metadata for gateway_system extraction: {}",
                            err
                        );
                        None
                    }
                }
            })
    }
    #[cfg(feature = "v2")]
    {
        None // V2 does not use feature metadata for gateway system tracking
    }
}

/// Updates the payment intent's feature metadata to track the gateway system being used
#[cfg(feature = "v1")]
pub fn update_gateway_system_in_feature_metadata<F: Clone, D>(
    payment_data: &mut D,
    gateway_system: GatewaySystem,
) -> RouterResult<()>
where
    D: OperationSessionGetters<F> + OperationSessionSetters<F>,
{
    let mut payment_intent = payment_data.get_payment_intent().clone();

    let existing_metadata = payment_intent.feature_metadata.as_ref();

    let mut feature_metadata = existing_metadata
        .and_then(|metadata| serde_json::from_value::<FeatureMetadata>(metadata.clone()).ok())
        .unwrap_or_default();

    feature_metadata.gateway_system = Some(gateway_system);

    let updated_metadata = serde_json::to_value(feature_metadata)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to serialize feature metadata")?;

    payment_intent.feature_metadata = Some(updated_metadata.clone());
    payment_data.set_payment_intent(payment_intent);

    Ok(())
}

pub async fn should_call_unified_connector_service_for_webhooks(
    state: &SessionState,
    merchant_context: &MerchantContext,
    connector_name: &str,
) -> RouterResult<bool> {
    if state.grpc_client.unified_connector_service_client.is_none() {
        logger::debug!(
            connector = connector_name.to_string(),
            "Unified Connector Service client is not available for webhooks"
        );
        return Ok(false);
    }

    let ucs_config_key = consts::UCS_ENABLED;

    if !is_ucs_enabled(state, ucs_config_key).await {
        return Ok(false);
    }

    let merchant_id = merchant_context
        .get_merchant_account()
        .get_id()
        .get_string_repr();

    let config_key = format!(
        "{}_{}_{}_Webhooks",
        consts::UCS_ROLLOUT_PERCENT_CONFIG_PREFIX,
        merchant_id,
        connector_name
    );

    let should_execute = should_execute_based_on_rollout(state, &config_key).await?;

    Ok(should_execute)
}

pub fn build_unified_connector_service_payment_method(
    payment_method_data: hyperswitch_domain_models::payment_method_data::PaymentMethodData,
    payment_method_type: PaymentMethodType,
) -> CustomResult<payments_grpc::PaymentMethod, UnifiedConnectorServiceError> {
    match payment_method_data {
        hyperswitch_domain_models::payment_method_data::PaymentMethodData::Card(card) => {
            let card_exp_month = card
                .get_card_expiry_month_2_digit()
                .attach_printable("Failed to extract 2-digit expiry month from card")
                .change_context(UnifiedConnectorServiceError::InvalidDataFormat {
                    field_name: "card_exp_month",
                })?
                .peek()
                .to_string();

            let card_network = card
                .card_network
                .clone()
                .map(payments_grpc::CardNetwork::foreign_try_from)
                .transpose()?;

            let card_details = CardDetails {
                card_number: Some(
                    CardNumber::from_str(&card.card_number.get_card_no()).change_context(
                        UnifiedConnectorServiceError::RequestEncodingFailedWithReason(
                            "Failed to parse card number".to_string(),
                        ),
                    )?,
                ),
                card_exp_month: Some(card_exp_month.into()),
                card_exp_year: Some(card.get_expiry_year_4_digit().expose().into()),
                card_cvc: Some(card.card_cvc.expose().into()),
                card_holder_name: card.card_holder_name.map(|name| name.expose().into()),
                card_issuer: card.card_issuer.clone(),
                card_network: card_network.map(|card_network| card_network.into()),
                card_type: card.card_type.clone(),
                bank_code: card.bank_code.clone(),
                nick_name: card.nick_name.map(|n| n.expose()),
                card_issuing_country_alpha2: card.card_issuing_country.clone(),
            };

            let grpc_card_type = match payment_method_type {
                PaymentMethodType::Credit => {
                    payments_grpc::card_payment_method_type::CardType::Credit(card_details)
                }
                PaymentMethodType::Debit => {
                    payments_grpc::card_payment_method_type::CardType::Debit(card_details)
                }
                _ => {
                    return Err(UnifiedConnectorServiceError::NotImplemented(format!(
                        "Unimplemented payment method subtype: {payment_method_type:?}"
                    ))
                    .into());
                }
            };

            Ok(payments_grpc::PaymentMethod {
                payment_method: Some(PaymentMethod::Card(CardPaymentMethodType {
                    card_type: Some(grpc_card_type),
                })),
            })
        }
        hyperswitch_domain_models::payment_method_data::PaymentMethodData::Upi(upi_data) => {
            let upi_type = match upi_data {
                hyperswitch_domain_models::payment_method_data::UpiData::UpiCollect(
                    upi_collect_data,
                ) => {
                    let upi_details = payments_grpc::UpiCollect {
                        vpa_id: upi_collect_data.vpa_id.map(|vpa| vpa.expose().into()),
                    };
                    PaymentMethod::UpiCollect(upi_details)
                }
                hyperswitch_domain_models::payment_method_data::UpiData::UpiIntent(_) => {
                    let upi_details = payments_grpc::UpiIntent { app_name: None };
                    PaymentMethod::UpiIntent(upi_details)
                }
            };

            Ok(payments_grpc::PaymentMethod {
                payment_method: Some(upi_type),
            })
        }
        hyperswitch_domain_models::payment_method_data::PaymentMethodData::Reward => {
            match payment_method_type {
                PaymentMethodType::ClassicReward => Ok(payments_grpc::PaymentMethod {
                    payment_method: Some(PaymentMethod::Reward(RewardPaymentMethodType {
                        reward_type: 1,
                    })),
                }),
                PaymentMethodType::Evoucher => Ok(payments_grpc::PaymentMethod {
                    payment_method: Some(PaymentMethod::Reward(RewardPaymentMethodType {
                        reward_type: 2,
                    })),
                }),
                _ => Err(UnifiedConnectorServiceError::NotImplemented(format!(
                    "Unimplemented payment method subtype: {payment_method_type:?}"
                ))
                .into()),
            }
        }
        _ => Err(UnifiedConnectorServiceError::NotImplemented(format!(
            "Unimplemented payment method: {payment_method_data:?}"
        ))
        .into()),
    }
}

pub fn build_unified_connector_service_payment_method_for_external_proxy(
    payment_method_data: hyperswitch_domain_models::payment_method_data::ExternalVaultPaymentMethodData,
    payment_method_type: PaymentMethodType,
) -> CustomResult<payments_grpc::PaymentMethod, UnifiedConnectorServiceError> {
    match payment_method_data {
        hyperswitch_domain_models::payment_method_data::ExternalVaultPaymentMethodData::Card(
            external_vault_card,
        ) => {
            let card_network = external_vault_card
                .card_network
                .clone()
                .map(payments_grpc::CardNetwork::foreign_try_from)
                .transpose()?;
            let card_details = CardDetails {
                card_number: Some(CardNumber::from_str(external_vault_card.card_number.peek()).change_context(
                    UnifiedConnectorServiceError::RequestEncodingFailedWithReason("Failed to parse card number".to_string())
                )?),
                card_exp_month: Some(external_vault_card.card_exp_month.expose().into()),
                card_exp_year: Some(external_vault_card.card_exp_year.expose().into()),
                card_cvc: Some(external_vault_card.card_cvc.expose().into()),
                card_holder_name: external_vault_card.card_holder_name.map(|name| name.expose().into()),
                card_issuer: external_vault_card.card_issuer.clone(),
                card_network: card_network.map(|card_network| card_network.into()),
                card_type: external_vault_card.card_type.clone(),
                bank_code: external_vault_card.bank_code.clone(),
                nick_name: external_vault_card.nick_name.map(|n| n.expose()),
                card_issuing_country_alpha2: external_vault_card.card_issuing_country.clone(),
            };
            let grpc_card_type = match payment_method_type {
                PaymentMethodType::Credit => {
                    payments_grpc::card_payment_method_type::CardType::CreditProxy(card_details)
                }
                PaymentMethodType::Debit => {
                    payments_grpc::card_payment_method_type::CardType::DebitProxy(card_details)
                }
                _ => {
                    return Err(UnifiedConnectorServiceError::NotImplemented(format!(
                        "Unimplemented payment method subtype: {payment_method_type:?}"
                    ))
                    .into());
                }
            };
            Ok(payments_grpc::PaymentMethod {
                payment_method: Some(PaymentMethod::Card(CardPaymentMethodType {
                    card_type: Some(grpc_card_type),
                })),
            })
        }
        hyperswitch_domain_models::payment_method_data::ExternalVaultPaymentMethodData::VaultToken(_) => {
            Err(UnifiedConnectorServiceError::NotImplemented(format!(
                        "Unimplemented payment method subtype: {payment_method_type:?}"
            ))
            .into())
        }
    }
}
pub fn build_unified_connector_service_auth_metadata(
    #[cfg(feature = "v1")] merchant_connector_account: MerchantConnectorAccountType,
    #[cfg(feature = "v2")] merchant_connector_account: MerchantConnectorAccountTypeDetails,
    merchant_context: &MerchantContext,
) -> CustomResult<ConnectorAuthMetadata, UnifiedConnectorServiceError> {
    #[cfg(feature = "v1")]
    let auth_type: ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(UnifiedConnectorServiceError::FailedToObtainAuthType)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    #[cfg(feature = "v2")]
    let auth_type: ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .change_context(UnifiedConnectorServiceError::FailedToObtainAuthType)
        .attach_printable("Failed to obtain ConnectorAuthType")?;

    let connector_name = {
        #[cfg(feature = "v1")]
        {
            merchant_connector_account
                .get_connector_name()
                .ok_or(UnifiedConnectorServiceError::MissingConnectorName)
                .attach_printable("Missing connector name")?
        }

        #[cfg(feature = "v2")]
        {
            merchant_connector_account
                .get_connector_name()
                .map(|connector| connector.to_string())
                .ok_or(UnifiedConnectorServiceError::MissingConnectorName)
                .attach_printable("Missing connector name")?
        }
    };

    let merchant_id = merchant_context
        .get_merchant_account()
        .get_id()
        .get_string_repr();

    match &auth_type {
        ConnectorAuthType::SignatureKey {
            api_key,
            key1,
            api_secret,
        } => Ok(ConnectorAuthMetadata {
            connector_name,
            auth_type: consts::UCS_AUTH_SIGNATURE_KEY.to_string(),
            api_key: Some(api_key.clone()),
            key1: Some(key1.clone()),
            api_secret: Some(api_secret.clone()),
            auth_key_map: None,
            merchant_id: Secret::new(merchant_id.to_string()),
        }),
        ConnectorAuthType::BodyKey { api_key, key1 } => Ok(ConnectorAuthMetadata {
            connector_name,
            auth_type: consts::UCS_AUTH_BODY_KEY.to_string(),
            api_key: Some(api_key.clone()),
            key1: Some(key1.clone()),
            api_secret: None,
            auth_key_map: None,
            merchant_id: Secret::new(merchant_id.to_string()),
        }),
        ConnectorAuthType::HeaderKey { api_key } => Ok(ConnectorAuthMetadata {
            connector_name,
            auth_type: consts::UCS_AUTH_HEADER_KEY.to_string(),
            api_key: Some(api_key.clone()),
            key1: None,
            api_secret: None,
            auth_key_map: None,
            merchant_id: Secret::new(merchant_id.to_string()),
        }),
        ConnectorAuthType::CurrencyAuthKey { auth_key_map } => Ok(ConnectorAuthMetadata {
            connector_name,
            auth_type: consts::UCS_AUTH_CURRENCY_AUTH_KEY.to_string(),
            api_key: None,
            key1: None,
            api_secret: None,
            auth_key_map: Some(auth_key_map.clone()),
            merchant_id: Secret::new(merchant_id.to_string()),
        }),
        _ => Err(UnifiedConnectorServiceError::FailedToObtainAuthType)
            .attach_printable("Unsupported ConnectorAuthType for header injection"),
    }
}

#[cfg(feature = "v2")]
pub fn build_unified_connector_service_external_vault_proxy_metadata(
    external_vault_merchant_connector_account: MerchantConnectorAccountTypeDetails,
) -> CustomResult<String, UnifiedConnectorServiceError> {
    let external_vault_metadata = external_vault_merchant_connector_account
        .get_metadata()
        .ok_or(UnifiedConnectorServiceError::ParsingFailed)
        .attach_printable("Failed to obtain ConnectorMetadata")?;

    let connector_name = external_vault_merchant_connector_account
        .get_connector_name()
        .map(|connector| connector.to_string())
        .ok_or(UnifiedConnectorServiceError::MissingConnectorName)
        .attach_printable("Missing connector name")?; // always get the connector name from this call

    let external_vault_connector = api_enums::VaultConnectors::from_str(&connector_name)
        .change_context(UnifiedConnectorServiceError::InvalidConnectorName)
        .attach_printable("Failed to parse Vault connector")?;

    let unified_service_vault_metdata = match external_vault_connector {
        api_enums::VaultConnectors::Vgs => {
            let vgs_metadata: ExternalVaultConnectorMetadata = external_vault_metadata
                .expose()
                .parse_value("ExternalVaultConnectorMetadata")
                .change_context(UnifiedConnectorServiceError::ParsingFailed)
                .attach_printable("Failed to parse Vgs connector metadata")?;

            Some(external_services::grpc_client::unified_connector_service::ExternalVaultProxyMetadata::VgsMetadata(
                external_services::grpc_client::unified_connector_service::VgsMetadata {
                    proxy_url: vgs_metadata.proxy_url,
                    certificate: vgs_metadata.certificate,
                }
            ))
        }
        api_enums::VaultConnectors::HyperswitchVault => None,
    };

    match unified_service_vault_metdata {
        Some(metdata) => {
            let external_vault_metadata_bytes = serde_json::to_vec(&metdata)
                .change_context(UnifiedConnectorServiceError::ParsingFailed)
                .attach_printable("Failed to convert External vault metadata to bytes")?;

            Ok(BASE64_ENGINE.encode(&external_vault_metadata_bytes))
        }
        None => Err(UnifiedConnectorServiceError::NotImplemented(
            "External vault proxy metadata is not supported for {connector_name}".to_string(),
        )
        .into()),
    }
}

pub fn handle_unified_connector_service_response_for_payment_authorize(
    response: PaymentServiceAuthorizeResponse,
) -> CustomResult<
    (
        AttemptStatus,
        Result<PaymentsResponseData, ErrorResponse>,
        u16,
    ),
    UnifiedConnectorServiceError,
> {
    let status = AttemptStatus::foreign_try_from(response.status())?;

    let status_code = transformers::convert_connector_service_status_code(response.status_code)?;

    let router_data_response =
        Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;

    Ok((status, router_data_response, status_code))
}

pub fn handle_unified_connector_service_response_for_payment_get(
    response: payments_grpc::PaymentServiceGetResponse,
) -> CustomResult<
    (
        AttemptStatus,
        Result<PaymentsResponseData, ErrorResponse>,
        u16,
    ),
    UnifiedConnectorServiceError,
> {
    let status = AttemptStatus::foreign_try_from(response.status())?;

    let status_code = transformers::convert_connector_service_status_code(response.status_code)?;

    let router_data_response =
        Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;

    Ok((status, router_data_response, status_code))
}

pub fn handle_unified_connector_service_response_for_payment_register(
    response: payments_grpc::PaymentServiceRegisterResponse,
) -> CustomResult<
    (
        AttemptStatus,
        Result<PaymentsResponseData, ErrorResponse>,
        u16,
    ),
    UnifiedConnectorServiceError,
> {
    let status = AttemptStatus::foreign_try_from(response.status())?;

    let status_code = transformers::convert_connector_service_status_code(response.status_code)?;

    let router_data_response =
        Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;

    Ok((status, router_data_response, status_code))
}

pub fn handle_unified_connector_service_response_for_payment_repeat(
    response: payments_grpc::PaymentServiceRepeatEverythingResponse,
) -> CustomResult<
    (
        AttemptStatus,
        Result<PaymentsResponseData, ErrorResponse>,
        u16,
    ),
    UnifiedConnectorServiceError,
> {
    let status = AttemptStatus::foreign_try_from(response.status())?;

    let status_code = transformers::convert_connector_service_status_code(response.status_code)?;

    let router_data_response =
        Result::<PaymentsResponseData, ErrorResponse>::foreign_try_from(response)?;

    Ok((status, router_data_response, status_code))
}

pub fn build_webhook_secrets_from_merchant_connector_account(
    #[cfg(feature = "v1")] merchant_connector_account: &MerchantConnectorAccountType,
    #[cfg(feature = "v2")] merchant_connector_account: &MerchantConnectorAccountTypeDetails,
) -> CustomResult<Option<payments_grpc::WebhookSecrets>, UnifiedConnectorServiceError> {
    // Extract webhook credentials from merchant connector account
    // This depends on how webhook secrets are stored in the merchant connector account

    #[cfg(feature = "v1")]
    let webhook_details = merchant_connector_account
        .get_webhook_details()
        .map_err(|_| UnifiedConnectorServiceError::FailedToObtainAuthType)?;

    #[cfg(feature = "v2")]
    let webhook_details = match merchant_connector_account {
        MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(mca) => {
            mca.connector_webhook_details.as_ref()
        }
        MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None,
    };

    match webhook_details {
        Some(details) => {
            // Parse the webhook details JSON to extract secrets
            let webhook_details: admin::MerchantConnectorWebhookDetails = details
                .clone()
                .parse_value("MerchantConnectorWebhookDetails")
                .change_context(UnifiedConnectorServiceError::FailedToObtainAuthType)
                .attach_printable("Failed to parse MerchantConnectorWebhookDetails")?;

            // Build gRPC WebhookSecrets from parsed details
            Ok(Some(payments_grpc::WebhookSecrets {
                secret: webhook_details.merchant_secret.expose().to_string(),
                additional_secret: webhook_details
                    .additional_secret
                    .map(|secret| secret.expose().to_string()),
            }))
        }
        None => Ok(None),
    }
}

/// High-level abstraction for calling UCS webhook transformation
/// This provides a clean interface similar to payment flow UCS calls
pub async fn call_unified_connector_service_for_webhook(
    state: &SessionState,
    merchant_context: &MerchantContext,
    connector_name: &str,
    body: &actix_web::web::Bytes,
    request_details: &hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails<'_>,
    merchant_connector_account: Option<
        &hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount,
    >,
) -> RouterResult<(
    api_models::webhooks::IncomingWebhookEvent,
    bool,
    WebhookTransformData,
)> {
    let ucs_client = state
        .grpc_client
        .unified_connector_service_client
        .as_ref()
        .ok_or_else(|| {
            error_stack::report!(errors::ApiErrorResponse::WebhookProcessingFailure)
                .attach_printable("UCS client is not available for webhook processing")
        })?;

    // Build webhook secrets from merchant connector account
    let webhook_secrets = merchant_connector_account.and_then(|mca| {
        #[cfg(feature = "v1")]
        let mca_type = MerchantConnectorAccountType::DbVal(Box::new(mca.clone()));
        #[cfg(feature = "v2")]
        let mca_type =
            MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(mca.clone()));

        build_webhook_secrets_from_merchant_connector_account(&mca_type)
            .map_err(|e| {
                logger::warn!(
                    build_error=?e,
                    connector_name=connector_name,
                    "Failed to build webhook secrets from merchant connector account in call_unified_connector_service_for_webhook"
                );
                e
            })
            .ok()
            .flatten()
    });

    // Build UCS transform request using new webhook transformers
    let transform_request = transformers::build_webhook_transform_request(
        body,
        request_details,
        webhook_secrets,
        merchant_context
            .get_merchant_account()
            .get_id()
            .get_string_repr(),
        connector_name,
    )?;

    // Build connector auth metadata
    let connector_auth_metadata = merchant_connector_account
        .map(|mca| {
            #[cfg(feature = "v1")]
            let mca_type = MerchantConnectorAccountType::DbVal(Box::new(mca.clone()));
            #[cfg(feature = "v2")]
            let mca_type = MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(
                mca.clone(),
            ));

            build_unified_connector_service_auth_metadata(mca_type, merchant_context)
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to build UCS auth metadata")?
        .ok_or_else(|| {
            error_stack::report!(errors::ApiErrorResponse::InternalServerError).attach_printable(
                "Missing merchant connector account for UCS webhook transformation",
            )
        })?;

    // Build gRPC headers
    let grpc_headers = external_services::grpc_client::GrpcHeaders {
        tenant_id: state.tenant.tenant_id.get_string_repr().to_string(),
        request_id: Some(utils::generate_id(consts::ID_LENGTH, "webhook_req")),
    };

    // Make UCS call - client availability already verified
    match ucs_client
        .transform_incoming_webhook(transform_request, connector_auth_metadata, grpc_headers)
        .await
    {
        Ok(response) => {
            let transform_response = response.into_inner();
            let transform_data = transformers::transform_ucs_webhook_response(transform_response)?;

            // UCS handles everything internally - event type, source verification, decoding
            Ok((
                transform_data.event_type,
                transform_data.source_verified,
                transform_data,
            ))
        }
        Err(err) => {
            // When UCS is configured, we don't fall back to direct connector processing
            Err(errors::ApiErrorResponse::WebhookProcessingFailure)
                .attach_printable(format!("UCS webhook processing failed: {err}"))
        }
    }
}

/// Extract webhook content from UCS response for further processing
/// This provides a helper function to extract specific data from UCS responses
pub fn extract_webhook_content_from_ucs_response(
    transform_data: &WebhookTransformData,
) -> Option<&unified_connector_service_client::payments::WebhookResponseContent> {
    transform_data.webhook_content.as_ref()
}

/// UCS Event Logging Wrapper Function
/// This function wraps UCS calls with comprehensive event logging.
/// It logs the actual gRPC request/response data, timing, and error information.
#[instrument(skip_all, fields(connector_name, flow_type, payment_id))]
pub async fn ucs_logging_wrapper<T, F, Fut, Req, Resp, GrpcReq, GrpcResp>(
    router_data: RouterData<T, Req, Resp>,
    state: &SessionState,
    grpc_request: GrpcReq,
    handler: F,
) -> RouterResult<RouterData<T, Req, Resp>>
where
    T: std::fmt::Debug + Clone + Send + 'static,
    Req: std::fmt::Debug + Clone + Send + Sync + 'static,
    Resp: std::fmt::Debug + Clone + Send + Sync + 'static,
    GrpcReq: serde::Serialize,
    GrpcResp: serde::Serialize,
    F: FnOnce(RouterData<T, Req, Resp>, GrpcReq) -> Fut + Send,
    Fut: std::future::Future<Output = RouterResult<(RouterData<T, Req, Resp>, GrpcResp)>> + Send,
{
    tracing::Span::current().record("connector_name", &router_data.connector);
    tracing::Span::current().record("flow_type", std::any::type_name::<T>());
    tracing::Span::current().record("payment_id", &router_data.payment_id);

    // Capture request data for logging
    let connector_name = router_data.connector.clone();
    let payment_id = router_data.payment_id.clone();
    let merchant_id = router_data.merchant_id.clone();
    let refund_id = router_data.refund_id.clone();
    let dispute_id = router_data.dispute_id.clone();

    // Log the actual gRPC request with masking
    let grpc_request_body = masking::masked_serialize(&grpc_request)
        .unwrap_or_else(|_| serde_json::json!({"error": "failed_to_serialize_grpc_request"}));

    // Update connector call count metrics for UCS operations
    crate::routes::metrics::CONNECTOR_CALL_COUNT.add(
        1,
        router_env::metric_attributes!(
            ("connector", connector_name.clone()),
            (
                "flow",
                std::any::type_name::<T>()
                    .split("::")
                    .last()
                    .unwrap_or_default()
            ),
        ),
    );

    // Execute UCS function and measure timing
    let start_time = Instant::now();
    let result = handler(router_data, grpc_request).await;
    let external_latency = start_time.elapsed().as_millis();

    // Create and emit connector event after UCS call
    let (status_code, response_body, router_result) = match result {
        Ok((updated_router_data, grpc_response)) => {
            let status = updated_router_data
                .connector_http_status_code
                .unwrap_or(200);

            // Log the actual gRPC response
            let grpc_response_body = serde_json::to_value(&grpc_response).unwrap_or_else(
                |_| serde_json::json!({"error": "failed_to_serialize_grpc_response"}),
            );

            (status, Some(grpc_response_body), Ok(updated_router_data))
        }
        Err(error) => {
            // Update error metrics for UCS calls
            crate::routes::metrics::CONNECTOR_ERROR_RESPONSE_COUNT.add(
                1,
                router_env::metric_attributes!(("connector", connector_name.clone(),)),
            );

            let error_body = serde_json::json!({
                "error": error.to_string(),
                "error_type": "ucs_call_failed"
            });
            (500, Some(error_body), Err(error))
        }
    };

    let mut connector_event = ConnectorEvent::new(
        state.tenant.tenant_id.clone(),
        connector_name,
        std::any::type_name::<T>(),
        grpc_request_body,
        "grpc://unified-connector-service".to_string(),
        common_utils::request::Method::Post,
        payment_id,
        merchant_id,
        state.request_id.as_ref(),
        external_latency,
        refund_id,
        dispute_id,
        status_code,
    );

    // Set response body based on status code
    if let Some(body) = response_body {
        match status_code {
            400..=599 => {
                connector_event.set_error_response_body(&body);
            }
            _ => {
                connector_event.set_response_body(&body);
            }
        }
    }

    // Emit event
    state.event_handler.log_event(&connector_event);

    router_result
}


pub async fn new(
        conf: &redis_interface::RedisSettings,
    ) -> error_stack::Result<Self, redis_interface::errors::RedisError> {
        Ok(Self {
            redis_conn: Arc::new(redis_interface::RedisConnectionPool::new(conf).await?),
        })
    }


pub async fn update_with_id(
        self,
        conn: &PgPooledConn,
        tokenization_record: tokenization_diesel::TokenizationUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            tokenization::dsl::id.eq(self.id.to_owned()),
            tokenization_record,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }


pub fn generate_cargo_instructions() {
    use std::io::Write;

    use vergen::EmitBuilder;

    EmitBuilder::builder()
        .cargo_debug()
        .cargo_opt_level()
        .cargo_target_triple()
        .git_commit_timestamp()
        .git_describe(true, true, None)
        .git_sha(true)
        .rustc_semver()
        .rustc_commit_hash()
        .emit()
        .expect("Failed to generate `cargo` build instructions");

    writeln!(
        &mut std::io::stdout(),
        "cargo:rustc-env=CARGO_PROFILE={}",
        std::env::var("PROFILE").expect("Failed to obtain `cargo` profile")
    )
    .expect("Failed to set `CARGO_PROFILE` environment variable");
}


pub struct RoleInfoResponseWithParentsGroup {
    pub role_id: String,
    pub role_name: String,
    pub entity_type: EntityType,
    pub parent_groups: Vec<ParentGroupInfo>,
    pub role_scope: RoleScope,
}


pub struct Address {
    name: Secret<String>,
    line_1: Secret<String>,
    line_2: Option<Secret<String>>,
    line_3: Option<Secret<String>>,
    city: Option<String>,
    state: Option<Secret<String>>,
    country: Option<String>,
    zip: Option<Secret<String>>,
    phone_number: Option<Secret<String>>,
}


File: crates/hyperswitch_connectors/src/connectors/bitpay.rs
 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)
    }
}


pub fn should_call_payout_connector_create_customer<'a>(
    state: &'a SessionState,
    connector: &'a api::ConnectorData,
    customer: &'a Option<domain::Customer>,
    merchant_connector_id: &'a domain::MerchantConnectorAccountTypeDetails,
) -> (bool, Option<&'a str>) {
    // Check if create customer is required for the connector
    match enums::PayoutConnectors::try_from(connector.connector_name) {
        Ok(connector) => {
            let connector_needs_customer = state
                .conf
                .connector_customer
                .payout_connector_list
                .contains(&connector);

            if connector_needs_customer {
                let connector_customer_details = customer
                    .as_ref()
                    .and_then(|customer| customer.get_connector_customer_id(merchant_connector_id));
                let should_call_connector = connector_customer_details.is_none();
                (should_call_connector, connector_customer_details)
            } else {
                (false, None)
            }
        }
        _ => (false, None),
    }
}


pub fn parse_and_get_metadata<T>(
        &self,
        type_name: &'static str,
    ) -> CustomResult<Option<T>, common_utils::errors::ParsingError>
    where
        T: for<'de> masking::Deserialize<'de>,
    {
        self.metadata
            .clone()
            .map(|metadata| metadata.parse_value(type_name))
            .transpose()
    }


pub struct HelcimPaymentsResponse {
    status: HelcimPaymentStatus,
    transaction_id: u64,
    invoice_number: Option<String>,
    #[serde(rename = "type")]
    transaction_type: HelcimTransactionType,
}


File: crates/router_derive/src/macros/to_encryptable.rs
 Public functions: 1
 Public structs: 1

use std::iter::Iterator;

use quote::{format_ident, quote};
use syn::{parse::Parse, punctuated::Punctuated, token::Comma, Field, Ident, Type as SynType};

use crate::macros::{helpers::get_struct_fields, misc::get_field_type};

pub struct FieldMeta {
    _meta_type: Ident,
    pub value: Ident,
}

impl Parse for FieldMeta {
    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
        let _meta_type: Ident = input.parse()?;
        input.parse::<syn::Token![=]>()?;
        let value: Ident = input.parse()?;
        Ok(Self { _meta_type, value })
    }
}

impl quote::ToTokens for FieldMeta {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.value.to_tokens(tokens);
    }
}

fn get_encryption_ty_meta(field: &Field) -> Option<FieldMeta> {
    let attrs = &field.attrs;

    attrs
        .iter()
        .flat_map(|s| s.parse_args::<FieldMeta>())
        .find(|s| s._meta_type.eq("ty"))
}

fn get_inner_type(path: &syn::TypePath) -> syn::Result<syn::TypePath> {
    path.path
        .segments
        .last()
        .and_then(|segment| match &segment.arguments {
            syn::PathArguments::AngleBracketed(args) => args.args.first(),
            _ => None,
        })
        .and_then(|arg| match arg {
            syn::GenericArgument::Type(SynType::Path(path)) => Some(path.clone()),
            _ => None,
        })
        .ok_or_else(|| {
            syn::Error::new(
                proc_macro2::Span::call_site(),
                "Only path fields are supported",
            )
        })
}

/// This function returns the inner most type recursively
/// For example:
///
/// In the case of `Encryptable<Secret<String>>> this returns String
fn get_inner_most_type(ty: SynType) -> syn::Result<Ident> {
    fn get_inner_type_recursive(path: syn::TypePath) -> syn::Result<syn::TypePath> {
        match get_inner_type(&path) {
            Ok(inner_path) => get_inner_type_recursive(inner_path),
            Err(_) => Ok(path),
        }
    }

    match ty {
        SynType::Path(path) => {
            let inner_path = get_inner_type_recursive(path)?;
            inner_path
                .path
                .segments
                .last()
                .map(|last_segment| last_segment.ident.to_owned())
                .ok_or_else(|| {
                    syn::Error::new(
                        proc_macro2::Span::call_site(),
                        "At least one ident must be specified",
                    )
                })
        }
        _ => Err(syn::Error::new(
            proc_macro2::Span::call_site(),
            "Only path fields are supported",
        )),
    }
}

/// This returns the field which implement #[encrypt] attribute
fn get_encryptable_fields(fields: Punctuated<Field, Comma>) -> Vec<Field> {
    fields
        .into_iter()
        .filter(|field| {
            field
                .attrs
                .iter()
                .any(|attr| attr.path().is_ident("encrypt"))
        })
        .collect()
}

/// This function returns the inner most type of a field
fn get_field_and_inner_types(fields: &[Field]) -> Vec<(Field, Ident)> {
    fields
        .iter()
        .flat_map(|field| {
            get_inner_most_type(field.ty.clone()).map(|field_name| (field.to_owned(), field_name))
        })
        .collect()
}

/// The type of the struct for which the batch encryption/decryption needs to be implemented
#[derive(PartialEq, Copy, Clone)]
enum StructType {
    Encrypted,
    Decrypted,
    DecryptedUpdate,
    FromRequest,
    Updated,
}

impl StructType {
    /// Generates the fields for temporary structs which consists of the fields that should be
    /// encrypted/decrypted
    fn generate_struct_fields(self, fields: &[(Field, Ident)]) -> Vec<proc_macro2::TokenStream> {
        fields
            .iter()
            .map(|(field, inner_ty)| {
                let provided_ty = get_encryption_ty_meta(field);
                let is_option = get_field_type(field.ty.clone())
                    .map(|f| f.eq("Option"))
                    .unwrap_or_default();
                let ident = &field.ident;
                let inner_ty = if let Some(ref ty) = provided_ty {
                    &ty.value
                } else {
                    inner_ty
                };
                match (self, is_option) {
                    (Self::Encrypted, true) => quote! { pub #ident: Option<Encryption> },
                    (Self::Encrypted, false) => quote! { pub #ident: Encryption },
                    (Self::Decrypted, true) => {
                        quote! { pub #ident: Option<Encryptable<Secret<#inner_ty>>> }
                    }
                    (Self::Decrypted, false) => {
                        quote! { pub #ident: Encryptable<Secret<#inner_ty>> }
                    }
                    (Self::DecryptedUpdate, _) => {
                        quote! { pub #ident: Option<Encryptable<Secret<#inner_ty>>> }
                    }
                    (Self::FromRequest, true) => {
                        quote! { pub #ident: Option<Secret<#inner_ty>> }
                    }
                    (Self::FromRequest, false) => quote! { pub #ident: Secret<#inner_ty> },
                    (Self::Updated, _) => quote! { pub #ident: Option<Secret<#inner_ty>> },
                }
            })
            .collect()
    }

    /// Generates the ToEncryptable trait implementation
    fn generate_impls(
        self,
        gen1: proc_macro2::TokenStream,
        gen2: proc_macro2::TokenStream,
        gen3: proc_macro2::TokenStream,
        impl_st: proc_macro2::TokenStream,
        inner: &[Field],
    ) -> proc_macro2::TokenStream {
        let map_length = inner.len();

        let to_encryptable_impl = inner.iter().flat_map(|field| {
            get_field_type(field.ty.clone()).map(|field_ty| {
                let is_option = field_ty.eq("Option");
                let field_ident = &field.ident;
                let field_ident_string = field_ident.as_ref().map(|s| s.to_string());

                if is_option || self == Self::Updated {
                    quote! { self.#field_ident.map(|s| map.insert(#field_ident_string.to_string(), s)) }
                } else {
                    quote! { map.insert(#field_ident_string.to_string(), self.#field_ident) }
                }
            })
        });

        let from_encryptable_impl = inner.iter().flat_map(|field| {
            get_field_type(field.ty.clone()).map(|field_ty| {
                let is_option = field_ty.eq("Option");
                let field_ident = &field.ident;
                let field_ident_string = field_ident.as_ref().map(|s| s.to_string());

                if is_option || self == Self::Updated {
                    quote! { #field_ident: map.remove(#field_ident_string) }
                } else {
                    quote! {
                        #field_ident: map.remove(#field_ident_string).ok_or(
                            error_stack::report!(common_utils::errors::ParsingError::EncodeError(
                                "Unable to convert from HashMap",
                            ))
                        )?
                    }
                }
            })
        });

        quote! {
            impl ToEncryptable<#gen1, #gen2, #gen3> for #impl_st {
                fn to_encryptable(self) -> FxHashMap<String, #gen3> {
                    let mut map = FxHashMap::with_capacity_and_hasher(#map_length, Default::default());
                    #(#to_encryptable_impl;)*
                    map
                }

                fn from_encryptable(
                    mut map: FxHashMap<String, Encryptable<#gen2>>,
                ) -> CustomResult<#gen1, common_utils::errors::ParsingError> {
                    Ok(#gen1 {
                        #(#from_encryptable_impl,)*
                    })
                }
            }
        }
    }
}

/// This function generates the temporary struct and ToEncryptable impls for the temporary structs
fn generate_to_encryptable(
    struct_name: Ident,
    fields: Vec<Field>,
) -> syn::Result<proc_macro2::TokenStream> {
    let struct_types = [
        // The first two are to be used as return types we do not need to implement ToEncryptable
        // on it
        ("Decrypted", StructType::Decrypted),
        ("DecryptedUpdate", StructType::DecryptedUpdate),
        ("FromRequestEncryptable", StructType::FromRequest),
        ("Encrypted", StructType::Encrypted),
        ("UpdateEncryptable", StructType::Updated),
    ];

    let inner_types = get_field_and_inner_types(&fields);

    let inner_type = inner_types.first().ok_or_else(|| {
        syn::Error::new(
            proc_macro2::Span::call_site(),
            "Please use the macro with attribute #[encrypt] on the fields you want to encrypt",
        )
    })?;

    let provided_ty = get_encryption_ty_meta(&inner_type.0)
        .map(|ty| ty.value.clone())
        .unwrap_or(inner_type.1.clone());

    let structs = struct_types.iter().map(|(prefix, struct_type)| {
        let name = format_ident!("{}{}", prefix, struct_name);
        let temp_fields = struct_type.generate_struct_fields(&inner_types);
        quote! {
            pub struct #name {
                #(#temp_fields,)*
            }
        }
    });

    // These implementations shouldn't be implemented Decrypted and DecryptedUpdate temp structs
    // So skip the first two entries in the list
    let impls = struct_types
        .iter()
        .skip(2)
        .map(|(prefix, struct_type)| {
            let name = format_ident!("{}{}", prefix, struct_name);

            let impl_block = if *struct_type != StructType::DecryptedUpdate
                || *struct_type != StructType::Decrypted
            {
                let (gen1, gen2, gen3) = match struct_type {
                    StructType::FromRequest => {
                        let decrypted_name = format_ident!("Decrypted{}", struct_name);
                        (
                            quote! { #decrypted_name },
                            quote! { Secret<#provided_ty> },
                            quote! { Secret<#provided_ty> },
                        )
                    }
                    StructType::Encrypted => {
                        let decrypted_name = format_ident!("Decrypted{}", struct_name);
                        (
                            quote! { #decrypted_name },
                            quote! { Secret<#provided_ty> },
                            quote! { Encryption },
                        )
                    }
                    StructType::Updated => {
                        let decrypted_update_name = format_ident!("DecryptedUpdate{}", struct_name);
                        (
                            quote! { #decrypted_update_name },
                            quote! { Secret<#provided_ty> },
                            quote! { Secret<#provided_ty> },
                        )
                    }
                    //Unreachable statement
                    _ => (quote! {}, quote! {}, quote! {}),
                };

                struct_type.generate_impls(gen1, gen2, gen3, quote! { #name }, &fields)
            } else {
                quote! {}
            };

            Ok(quote! {
                #impl_block
            })
        })
        .collect::<syn::Result<Vec<_>>>()?;

    Ok(quote! {
        #(#structs)*
        #(#impls)*
    })
}

pub fn derive_to_encryption(
    input: syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
    let struct_name = input.ident;
    let fields = get_encryptable_fields(get_struct_fields(input.data)?);

    generate_to_encryptable(struct_name, fields)
}


pub async fn submit_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    req: dispute_models::SubmitEvidenceRequest,
) -> RouterResponse<dispute_models::DisputeResponse> {
    let db = &state.store;
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &req.dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: req.dispute_id.clone(),
        })?;
    core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
    let dispute_id = dispute.dispute_id.clone();
    common_utils::fp_utils::when(
        !core_utils::should_proceed_with_submit_evidence(
            dispute.dispute_stage,
            dispute.dispute_status,
        ),
        || {
            metrics::EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]);
            Err(errors::ApiErrorResponse::DisputeStatusValidationFailed {
                reason: format!(
                "Evidence cannot be submitted because the dispute is in {} stage and has {} status",
                dispute.dispute_stage, dispute.dispute_status
            ),
            })
        },
    )?;
    let submit_evidence_request_data =
        transformers::get_evidence_request_data(&state, &merchant_context, req, &dispute).await?;

    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &dispute.payment_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

    let payment_attempt = db
        .find_payment_attempt_by_attempt_id_merchant_id(
            &dispute.attempt_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
    let connector_data = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &dispute.connector,
        api::GetToken::Connector,
        dispute.merchant_connector_id.clone(),
    )?;

    let connector_integration: services::BoxedDisputeConnectorIntegrationInterface<
        api::Evidence,
        SubmitEvidenceRequestData,
        SubmitEvidenceResponse,
    > = connector_data.connector.get_connector_integration();
    let router_data = core_utils::construct_submit_evidence_router_data(
        &state,
        &payment_intent,
        &payment_attempt,
        &merchant_context,
        &dispute,
        submit_evidence_request_data,
    )
    .await?;
    let response = services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_dispute_failed_response()
    .attach_printable("Failed while calling submit evidence connector api")?;
    let submit_evidence_response =
        response
            .response
            .map_err(|err| errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: dispute.connector.clone(),
                status_code: err.status_code,
                reason: err.reason,
            })?;
    //Defend Dispute Optionally if connector expects to defend / submit evidence in a separate api call
    let (dispute_status, connector_status) = if connector_data
        .connector_name
        .requires_defend_dispute()
    {
        let connector_integration_defend_dispute: services::BoxedDisputeConnectorIntegrationInterface<
                api::Defend,
                DefendDisputeRequestData,
                DefendDisputeResponse,
            > = connector_data.connector.get_connector_integration();
        let defend_dispute_router_data = core_utils::construct_defend_dispute_router_data(
            &state,
            &payment_intent,
            &payment_attempt,
            &merchant_context,
            &dispute,
        )
        .await?;
        let defend_response = services::execute_connector_processing_step(
            &state,
            connector_integration_defend_dispute,
            &defend_dispute_router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_dispute_failed_response()
        .attach_printable("Failed while calling defend dispute connector api")?;
        let defend_dispute_response = defend_response.response.map_err(|err| {
            errors::ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: dispute.connector.clone(),
                status_code: err.status_code,
                reason: err.reason,
            }
        })?;
        (
            defend_dispute_response.dispute_status,
            defend_dispute_response.connector_status,
        )
    } else {
        (
            submit_evidence_response.dispute_status,
            submit_evidence_response.connector_status,
        )
    };
    let update_dispute = diesel_models::dispute::DisputeUpdate::StatusUpdate {
        dispute_status,
        connector_status,
    };
    let updated_dispute = db
        .update_dispute(dispute.clone(), update_dispute)
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.to_owned(),
        })
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    let dispute_response = api_models::disputes::DisputeResponse::foreign_from(updated_dispute);
    Ok(services::ApplicationResponse::Json(dispute_response))
}


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(", "),
                })
            }
        }
    }
}

impl ValidateStatusForOperation for for PaymentGet


pub struct ForteAuthType {
    pub(super) api_access_id: Secret<String>,
    pub(super) organization_id: Secret<String>,
    pub(super) location_id: Secret<String>,
    pub(super) api_secret_key: Secret<String>,
}


impl Webhooks {
    pub fn server(config: AppState) -> Scope {
        use api_models::webhooks as webhook_type;

        #[allow(unused_mut)]
        let mut route = web::scope("/webhooks")
            .app_data(web::Data::new(config))
            .service(
                web::resource("/network_token_requestor/ref")
                    .route(
                        web::post().to(receive_network_token_requestor_incoming_webhook::<
                            webhook_type::OutgoingWebhook,
                        >),
                    )
                    .route(
                        web::get().to(receive_network_token_requestor_incoming_webhook::<
                            webhook_type::OutgoingWebhook,
                        >),
                    )
                    .route(
                        web::put().to(receive_network_token_requestor_incoming_webhook::<
                            webhook_type::OutgoingWebhook,
                        >),
                    ),
            )
            .service(
                web::resource("/{merchant_id}/{connector_id_or_name}")
                    .route(
                        web::post().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>),
                    )
                    .route(web::get().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>))
                    .route(
                        web::put().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>),
                    ),
            );

        #[cfg(feature = "frm")]
        {
            route = route.service(
                web::resource("/frm_fulfillment")
                    .route(web::post().to(frm_routes::frm_fulfillment)),
            );
        }

        route
    }
}


pub struct CeleroPaymentsRequest {
    idempotency_key: String,
    #[serde(rename = "type")]
    transaction_type: TransactionType,
    amount: MinorUnit, // CeleroCommerce expects integer cents
    currency: Currency,
    payment_method: CeleroPaymentMethod,
    #[serde(skip_serializing_if = "Option::is_none")]
    billing_address: Option<CeleroAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    shipping_address: Option<CeleroAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    create_vault_record: Option<bool>,
    // CIT/MIT fields
    #[serde(skip_serializing_if = "Option::is_none")]
    card_on_file_indicator: Option<CardOnFileIndicator>,
    #[serde(skip_serializing_if = "Option::is_none")]
    initiated_by: Option<InitiatedBy>,
    #[serde(skip_serializing_if = "Option::is_none")]
    initial_transaction_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    stored_credential_indicator: Option<StoredCredentialIndicator>,
    #[serde(skip_serializing_if = "Option::is_none")]
    billing_method: Option<BillingMethod>,
}


/// Returns the decision
    pub fn get_decision(&self) -> ThreeDSDecision {
        self.decision
    }


pub fn new(proxy_config: &Proxy) -> CustomResult<Self, ApiClientError> {
        let client = client::get_client_builder(proxy_config)
            .switch()?
            .build()
            .change_context(ApiClientError::InvalidProxyConfiguration)?;
        Ok(Self {
            proxy_config: proxy_config.clone(),
            client,
            request_id: None,
        })
    }


pub struct CybersourceRefundResponse {
    id: String,
    status: CybersourceRefundStatus,
    error_information: Option<CybersourceErrorInformation>,
}


impl<const T: u8> PaymentToken for DummyConnector<T> {}

impl PaymentToken for for DummyConnector