impl OpenSearchQueryBuilder {
    pub fn new(query_type: OpenSearchQuery, query: String, search_params: Vec<AuthInfo>) -> Self {
        Self {
            query_type,
            query,
            search_params,
            offset: Default::default(),
            count: Default::default(),
            filters: Default::default(),
            time_range: Default::default(),
            case_sensitive_fields: HashSet::from([
                "customer_email.keyword",
                "search_tags.keyword",
                "card_last_4.keyword",
                "payment_id.keyword",
                "amount",
                "customer_id.keyword",
            ]),
        }
    }

    pub fn set_offset_n_count(&mut self, offset: i64, count: i64) -> QueryResult<()> {
        self.offset = Some(offset);
        self.count = Some(count);
        Ok(())
    }

    pub fn set_time_range(&mut self, time_range: OpensearchTimeRange) -> QueryResult<()> {
        self.time_range = Some(time_range);
        Ok(())
    }

    pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec<Value>) -> QueryResult<()> {
        self.filters.push((lhs, rhs));
        Ok(())
    }

    pub fn get_status_field(&self, index: SearchIndex) -> &str {
        match index {
            SearchIndex::Refunds | SearchIndex::SessionizerRefunds => "refund_status.keyword",
            SearchIndex::Disputes | SearchIndex::SessionizerDisputes => "dispute_status.keyword",
            _ => "status.keyword",
        }
    }

    pub fn get_amount_field(&self, index: SearchIndex) -> &str {
        match index {
            SearchIndex::Refunds | SearchIndex::SessionizerRefunds => "refund_amount",
            SearchIndex::Disputes | SearchIndex::SessionizerDisputes => "dispute_amount",
            _ => "amount",
        }
    }

    pub fn build_filter_array(
        &self,
        case_sensitive_filters: Vec<&(String, Vec<Value>)>,
        index: SearchIndex,
    ) -> Vec<Value> {
        let mut filter_array = Vec::new();
        if !self.query.is_empty() {
            filter_array.push(json!({
                "multi_match": {
                    "type": "phrase",
                    "query": self.query,
                    "lenient": true
                }
            }));
        }

        let case_sensitive_json_filters = case_sensitive_filters
            .into_iter()
            .map(|(k, v)| {
                let key = if *k == "amount" {
                    self.get_amount_field(index).to_string()
                } else {
                    k.clone()
                };
                json!({"terms": {key: v}})
            })
            .collect::<Vec<Value>>();

        filter_array.extend(case_sensitive_json_filters);

        if let Some(ref time_range) = self.time_range {
            let range = json!(time_range);
            filter_array.push(json!({
                "range": {
                    "@timestamp": range
                }
            }));
        }

        filter_array
    }

    pub fn build_case_insensitive_filters(
        &self,
        mut payload: Value,
        case_insensitive_filters: &[&(String, Vec<Value>)],
        auth_array: Vec<Value>,
        index: SearchIndex,
    ) -> Value {
        let mut must_array = case_insensitive_filters
            .iter()
            .map(|(k, v)| {
                let key = if *k == "status.keyword" {
                    self.get_status_field(index).to_string()
                } else {
                    k.clone()
                };
                json!({
                    "bool": {
                        "must": [
                            {
                                "bool": {
                                    "should": v.iter().map(|value| {
                                        json!({
                                            "term": {
                                                format!("{}", key): {
                                                    "value": value,
                                                    "case_insensitive": true
                                                }
                                            }
                                        })
                                    }).collect::<Vec<Value>>(),
                                    "minimum_should_match": 1
                                }
                            }
                        ]
                    }
                })
            })
            .collect::<Vec<Value>>();

        must_array.push(json!({ "bool": {
            "must": [
                {
                    "bool": {
                        "should": auth_array,
                        "minimum_should_match": 1
                    }
                }
            ]
        }}));

        if let Some(query) = payload.get_mut("query") {
            if let Some(bool_obj) = query.get_mut("bool") {
                if let Some(bool_map) = bool_obj.as_object_mut() {
                    bool_map.insert("must".to_string(), Value::Array(must_array));
                }
            }
        }

        payload
    }

    pub fn build_auth_array(&self) -> Vec<Value> {
        self.search_params
            .iter()
            .map(|user_level| match user_level {
                AuthInfo::OrgLevel { org_id } => {
                    let must_clauses = vec![json!({
                        "term": {
                            "organization_id.keyword": {
                                "value": org_id
                            }
                        }
                    })];

                    json!({
                        "bool": {
                            "must": must_clauses
                        }
                    })
                }
                AuthInfo::MerchantLevel {
                    org_id,
                    merchant_ids,
                } => {
                    let must_clauses = vec![
                        json!({
                            "term": {
                                "organization_id.keyword": {
                                    "value": org_id
                                }
                            }
                        }),
                        json!({
                            "terms": {
                                "merchant_id.keyword": merchant_ids
                            }
                        }),
                    ];

                    json!({
                        "bool": {
                            "must": must_clauses
                        }
                    })
                }
                AuthInfo::ProfileLevel {
                    org_id,
                    merchant_id,
                    profile_ids,
                } => {
                    let must_clauses = vec![
                        json!({
                            "term": {
                                "organization_id.keyword": {
                                    "value": org_id
                                }
                            }
                        }),
                        json!({
                            "term": {
                                "merchant_id.keyword": {
                                    "value": merchant_id
                                }
                            }
                        }),
                        json!({
                            "terms": {
                                "profile_id.keyword": profile_ids
                            }
                        }),
                    ];

                    json!({
                        "bool": {
                            "must": must_clauses
                        }
                    })
                }
            })
            .collect::<Vec<Value>>()
    }

    /// # Panics
    ///
    /// This function will panic if:
    ///
    /// * The structure of the JSON query is not as expected (e.g., missing keys or incorrect types).
    ///
    /// Ensure that the input data and the structure of the query are valid and correctly handled.
    pub fn construct_payload(&self, indexes: &[SearchIndex]) -> QueryResult<Vec<Value>> {
        let mut query_obj = Map::new();
        let bool_obj = Map::new();

        let (case_sensitive_filters, case_insensitive_filters): (Vec<_>, Vec<_>) = self
            .filters
            .iter()
            .partition(|(k, _)| self.case_sensitive_fields.contains(k.as_str()));

        let should_array = self.build_auth_array();

        query_obj.insert("bool".to_string(), Value::Object(bool_obj.clone()));

        let mut sort_obj = Map::new();
        sort_obj.insert(
            "@timestamp".to_string(),
            json!({
                "order": "desc"
            }),
        );

        Ok(indexes
            .iter()
            .map(|index| {
                let mut payload = json!({
                    "query": query_obj.clone(),
                    "sort": [
                        Value::Object(sort_obj.clone())
                    ]
                });
                let filter_array = self.build_filter_array(case_sensitive_filters.clone(), *index);
                if !filter_array.is_empty() {
                    payload
                        .get_mut("query")
                        .and_then(|query| query.get_mut("bool"))
                        .and_then(|bool_obj| bool_obj.as_object_mut())
                        .map(|bool_map| {
                            bool_map.insert("filter".to_string(), Value::Array(filter_array));
                        });
                }
                payload = self.build_case_insensitive_filters(
                    payload,
                    &case_insensitive_filters,
                    should_array.clone(),
                    *index,
                );
                payload
            })
            .collect::<Vec<Value>>())
    }
}


pub fn get_revenue_recovery_attempt(
        payment_intent: &domain_payments::PaymentIntent,
        revenue_recovery_metadata: &api_payments::PaymentRevenueRecoveryMetadata,
        billing_connector_account: &domain::MerchantConnectorAccount,
        card_info: api_payments::AdditionalCardInfo,
        payment_processor_token: &str,
    ) -> CustomResult<Self, errors::RevenueRecoveryError> {
        let revenue_recovery_data = payment_intent
            .create_revenue_recovery_attempt_data(
                revenue_recovery_metadata.clone(),
                billing_connector_account,
                card_info,
                payment_processor_token,
            )
            .change_context(errors::RevenueRecoveryError::RevenueRecoveryAttemptDataCreateFailed)
            .attach_printable("Failed to build recovery attempt data")?;
        Ok(Self(revenue_recovery_data))
    }


impl Responder {
    let flow = Flow::RoutingRetrieveDictionary;
    Box::pin(oss_api::server_wrap(
        flow,
        state,
        &req,
        query.into_inner(),
        |state, auth: auth::AuthenticationData, query_params, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            routing::retrieve_merchant_routing_dictionary(
                state,
                merchant_context,
                None,
                query_params.clone(),
                transaction_type
                    .or(query_params.transaction_type)
                    .unwrap_or(enums::TransactionType::Payment),
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantRoutingRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct ApplicationInformation {
    status: Option<CybersourcePaymentStatus>,
}


impl api::Refund for Gpayments {}


impl AuthenticationInterface for KafkaStore {
    async fn insert_authentication(
        &self,
        authentication: storage::AuthenticationNew,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let auth = self
            .diesel_store
            .insert_authentication(authentication)
            .await?;

        if let Err(er) = self
            .kafka_producer
            .log_authentication(&auth, None, self.tenant_id.clone())
            .await
        {
            logger::error!(message="Failed to log analytics event for authentication {auth:?}", error_message=?er)
        }

        Ok(auth)
    }

    async fn find_authentication_by_merchant_id_authentication_id(
        &self,
        merchant_id: &id_type::MerchantId,
        authentication_id: &id_type::AuthenticationId,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        self.diesel_store
            .find_authentication_by_merchant_id_authentication_id(merchant_id, authentication_id)
            .await
    }

    async fn find_authentication_by_merchant_id_connector_authentication_id(
        &self,
        merchant_id: id_type::MerchantId,
        connector_authentication_id: String,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        self.diesel_store
            .find_authentication_by_merchant_id_connector_authentication_id(
                merchant_id,
                connector_authentication_id,
            )
            .await
    }

    async fn update_authentication_by_merchant_id_authentication_id(
        &self,
        previous_state: storage::Authentication,
        authentication_update: storage::AuthenticationUpdate,
    ) -> CustomResult<storage::Authentication, errors::StorageError> {
        let auth = self
            .diesel_store
            .update_authentication_by_merchant_id_authentication_id(
                previous_state.clone(),
                authentication_update,
            )
            .await?;

        if let Err(er) = self
            .kafka_producer
            .log_authentication(&auth, Some(previous_state.clone()), self.tenant_id.clone())
            .await
        {
            logger::error!(message="Failed to log analytics event for authentication {auth:?}", error_message=?er)
        }

        Ok(auth)
    }
}

impl AuthenticationInterface for for KafkaStore


pub struct Customers {
    id: Secret<String>,
}


impl super::frm::filters::FrmFilterAnalytics for ClickhouseClient {}

impl super::frm::filters::FrmFilterAnalytics for for ClickhouseClient


pub async fn get_evidence_request_data(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    evidence_request: api_models::disputes::SubmitEvidenceRequest,
    dispute: &diesel_models::dispute::Dispute,
) -> CustomResult<SubmitEvidenceRequestData, errors::ApiErrorResponse> {
    let cancellation_policy_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.cancellation_policy,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let customer_communication_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.customer_communication,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let customer_sifnature_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.customer_signature,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let receipt_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.receipt,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let refund_policy_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.refund_policy,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let service_documentation_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.service_documentation,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let shipping_documentation_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.shipping_documentation,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    let invoice_showing_distinct_transactions_file_info =
        retrieve_file_and_provider_file_id_from_file_id(
            state,
            evidence_request.invoice_showing_distinct_transactions,
            None,
            merchant_context,
            api::FileDataRequired::NotRequired,
        )
        .await?;
    let recurring_transaction_agreement_file_info =
        retrieve_file_and_provider_file_id_from_file_id(
            state,
            evidence_request.recurring_transaction_agreement,
            None,
            merchant_context,
            api::FileDataRequired::NotRequired,
        )
        .await?;
    let uncategorized_file_info = retrieve_file_and_provider_file_id_from_file_id(
        state,
        evidence_request.uncategorized_file,
        None,
        merchant_context,
        api::FileDataRequired::NotRequired,
    )
    .await?;
    Ok(SubmitEvidenceRequestData {
        dispute_id: dispute.dispute_id.clone(),
        dispute_status: dispute.dispute_status,
        connector_dispute_id: dispute.connector_dispute_id.clone(),
        access_activity_log: evidence_request.access_activity_log,
        billing_address: evidence_request.billing_address,
        cancellation_policy: cancellation_policy_file_info.file_data,
        cancellation_policy_provider_file_id: cancellation_policy_file_info.provider_file_id,
        cancellation_policy_disclosure: evidence_request.cancellation_policy_disclosure,
        cancellation_rebuttal: evidence_request.cancellation_rebuttal,
        customer_communication: customer_communication_file_info.file_data,
        customer_communication_provider_file_id: customer_communication_file_info.provider_file_id,
        customer_email_address: evidence_request.customer_email_address,
        customer_name: evidence_request.customer_name,
        customer_purchase_ip: evidence_request.customer_purchase_ip,
        customer_signature: customer_sifnature_file_info.file_data,
        customer_signature_provider_file_id: customer_sifnature_file_info.provider_file_id,
        product_description: evidence_request.product_description,
        receipt: receipt_file_info.file_data,
        receipt_provider_file_id: receipt_file_info.provider_file_id,
        refund_policy: refund_policy_file_info.file_data,
        refund_policy_provider_file_id: refund_policy_file_info.provider_file_id,
        refund_policy_disclosure: evidence_request.refund_policy_disclosure,
        refund_refusal_explanation: evidence_request.refund_refusal_explanation,
        service_date: evidence_request.service_date,
        service_documentation: service_documentation_file_info.file_data,
        service_documentation_provider_file_id: service_documentation_file_info.provider_file_id,
        shipping_address: evidence_request.shipping_address,
        shipping_carrier: evidence_request.shipping_carrier,
        shipping_date: evidence_request.shipping_date,
        shipping_documentation: shipping_documentation_file_info.file_data,
        shipping_documentation_provider_file_id: shipping_documentation_file_info.provider_file_id,
        shipping_tracking_number: evidence_request.shipping_tracking_number,
        invoice_showing_distinct_transactions: invoice_showing_distinct_transactions_file_info
            .file_data,
        invoice_showing_distinct_transactions_provider_file_id:
            invoice_showing_distinct_transactions_file_info.provider_file_id,
        recurring_transaction_agreement: recurring_transaction_agreement_file_info.file_data,
        recurring_transaction_agreement_provider_file_id: recurring_transaction_agreement_file_info
            .provider_file_id,
        uncategorized_file: uncategorized_file_info.file_data,
        uncategorized_file_provider_file_id: uncategorized_file_info.provider_file_id,
        uncategorized_text: evidence_request.uncategorized_text,
        cancellation_policy_file_type: cancellation_policy_file_info.file_type,
        customer_communication_file_type: customer_communication_file_info.file_type,
        customer_signature_file_type: customer_sifnature_file_info.file_type,
        receipt_file_type: receipt_file_info.file_type,
        refund_policy_file_type: refund_policy_file_info.file_type,
        service_documentation_file_type: service_documentation_file_info.file_type,
        shipping_documentation_file_type: shipping_documentation_file_info.file_type,
        invoice_showing_distinct_transactions_file_type:
            invoice_showing_distinct_transactions_file_info.file_type,
        recurring_transaction_agreement_file_type: recurring_transaction_agreement_file_info
            .file_type,
        uncategorized_file_type: uncategorized_file_info.file_type,
    })
}


        api_models::payments::OpenBankingData,
        api_models::payments::OpenBankingSessionToken,
        api_models::payments::BankDebitResponse,
        api_models::payments::BankRedirectResponse,
        api_models::payments::BankTransferResponse,
        api_models::payments::CardRedirectResponse,
        api_models::payments::CardTokenResponse,
        api_models::payments::CryptoResponse,
        api_models::payments::GiftCardResponse,
        api_models::payments::OpenBankingResponse,
        api_models::payments::RealTimePaymentDataResponse,
        api_models::payments::UpiResponse,
        api_models::payments::VoucherResponse,
        api_models::payments::additional_info::CardTokenAdditionalData,
        api_models::payments::additional_info::BankDebitAdditionalData,
        api_models::payments::additional_info::AchBankDebitAdditionalData,
        api_models::payments::additional_info::BacsBankDebitAdditionalData,
        api_models::payments::additional_info::BecsBankDebitAdditionalData,
        api_models::payments::additional_info::SepaBankDebitAdditionalData,
        api_models::payments::additional_info::BankRedirectDetails,
        api_models::payments::additional_info::BancontactBankRedirectAdditionalData,
        api_models::payments::additional_info::BlikBankRedirectAdditionalData,
        api_models::payments::additional_info::GiropayBankRedirectAdditionalData,
        api_models::payments::additional_info::BankTransferAdditionalData,
        api_models::payments::additional_info::PixBankTransferAdditionalData,
        api_models::payments::additional_info::LocalBankTransferAdditionalData,
        api_models::payments::additional_info::GiftCardAdditionalData,
        api_models::payments::additional_info::GivexGiftCardAdditionalData,
        api_models::payments::additional_info::UpiAdditionalData,
        api_models::payments::additional_info::UpiCollectAdditionalData,
        api_models::payments::additional_info::WalletAdditionalDataForCard,
        api_models::payments::WalletResponse,
        api_models::payments::WalletResponseData,
        api_models::payments::PaymentsDynamicTaxCalculationRequest,
        api_models::payments::PaymentsDynamicTaxCalculationResponse,
        api_models::payments::DisplayAmountOnSdk,
        api_models::payments::ErrorDetails,
        api_models::payments::CtpServiceDetails,
        api_models::payments::AdyenConnectorMetadata,
        api_models::payments::AdyenTestingData,
        api_models::feature_matrix::FeatureMatrixListResponse,
        api_models::feature_matrix::FeatureMatrixRequest,
        api_models::feature_matrix::ConnectorFeatureMatrixResponse,
        api_models::feature_matrix::PaymentMethodSpecificFeatures,
        api_models::feature_matrix::CardSpecificFeatures,
        api_models::feature_matrix::SupportedPaymentMethod,
        api_models::payment_methods::PaymentMethodSessionUpdateSavedPaymentMethod,
        api_models::payment_methods::PaymentMethodSessionDeleteSavedPaymentMethod,
        common_utils::types::BrowserInformation,
        api_models::enums::TokenizationType,
        api_models::enums::NetworkTokenizationToggle,
        api_models::payments::PaymentAmountDetailsResponse,
        api_models::payment_methods::PaymentMethodSessionConfirmRequest,
        api_models::payment_methods::PaymentMethodSessionResponse,
        api_models::payment_methods::AuthenticationDetails,
        api_models::process_tracker::revenue_recovery::RevenueRecoveryResponse,
        api_models::enums::ProcessTrackerStatus,
        api_models::proxy::ProxyRequest,
        api_models::proxy::ProxyResponse,
        api_models::proxy::TokenType,
        routes::payments::ForceSync,
        api_models::tokenization::GenericTokenizationRequest,
        api_models::tokenization::GenericTokenizationResponse,
        api_models::tokenization::DeleteTokenDataRequest,
        api_models::tokenization::DeleteTokenDataResponse,
    )),
    modifiers(&SecurityAddon)
)]
// Bypass clippy lint for not being constructed
#[allow(dead_code)]
pub(crate) struct ApiDoc;

struct SecurityAddon;

impl utoipa::Modify for SecurityAddon {
    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
        use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};

        if let Some(components) = openapi.components.as_mut() {
            components.add_security_schemes_from_iter([
                (
                    "api_key",
                    SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
                        "api-key",
                        "Use the API key created under your merchant account from the HyperSwitch dashboard. API key is used to authenticate API requests from your merchant server only. Don't expose this key on a website or embed it in a mobile application."
                    ))),
                ),
                (
                    "admin_api_key",
                    SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
                        "api-key",
                        "Admin API keys allow you to perform some privileged actions such as \
                        creating a merchant account and Connector account."
                    ))),
                ),
                (
                    "publishable_key",
                    SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
                        "api-key",
                        "Publishable keys are a type of keys that can be public and have limited \
                        scope of usage."
                    ))),
                ),
                (
                    "ephemeral_key",
                    SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description(
                        "api-key",
                        "Ephemeral keys provide temporary access to singular data, such as access \
                        to a single customer object for a short period of time."
                    ))),
                ),
            ]);
        }
    }
}


pub struct RouterStore<T: DatabaseStore> {
    db_store: T,
    cache_store: Arc<RedisStore>,
    master_encryption_key: StrongSecret<Vec<u8>>,
    pub request_id: Option<String>,
}


File: crates/common_utils/src/id_type/global_id/token.rs
 Public functions: 3

use std::borrow::Cow;

use error_stack::ResultExt;

use crate::errors::{CustomResult, ValidationError};

crate::global_id_type!(
    GlobalTokenId,
    "A global id that can be used to identify a token.

The format will be `<cell_id>_<entity_prefix>_<time_ordered_id>`.

Example: `cell1_tok_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p`"
);

// Database related implementations so that this field can be used directly in the database tables
crate::impl_queryable_id_type!(GlobalTokenId);
crate::impl_to_sql_from_sql_global_id_type!(GlobalTokenId);

impl GlobalTokenId {
    /// Get string representation of the id
    pub fn get_string_repr(&self) -> &str {
        self.0.get_string_repr()
    }

    ///Get GlobalTokenId from a string
    pub fn from_string(token_string: &str) -> CustomResult<Self, ValidationError> {
        let token = super::GlobalId::from_string(Cow::Owned(token_string.to_string()))
            .change_context(ValidationError::IncorrectValueProvided {
                field_name: "GlobalTokenId",
            })?;
        Ok(Self(token))
    }

    /// Generate a new GlobalTokenId from a cell id
    pub fn generate(cell_id: &crate::id_type::CellId) -> Self {
        let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Token);
        Self(global_id)
    }
}

impl crate::events::ApiEventMetric for GlobalTokenId {
    fn get_api_event_type(&self) -> Option<crate::events::ApiEventsType> {
        Some(crate::events::ApiEventsType::Token {
            token_id: Some(self.clone()),
        })
    }
}


File: crates/router/src/types/api/refunds_v2.rs

pub use hyperswitch_interfaces::api::refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2};


pub async fn internal_user_signup(
    state: web::Data<AppState>,
    http_req: HttpRequest,
    json_payload: web::Json<user_api::CreateInternalUserRequest>,
) -> HttpResponse {
    let flow = Flow::InternalUserSignup;
    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &http_req,
        json_payload.into_inner(),
        |state, _, req, _| user_core::create_internal_user(state, req),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentVoid for Iatapay {}


impl ApiClient for ProxyClient {
    fn request(
        &self,
        method: Method,
        url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        self.request_with_certificate(method, url, None, None)
    }

    fn request_with_certificate(
        &self,
        method: Method,
        url: String,
        certificate: Option<masking::Secret<String>>,
        certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        let client_builder = self
            .get_reqwest_client(certificate, certificate_key)
            .change_context(ApiClientError::ClientConstructionFailed)?;
        Ok(Box::new(RouterRequestBuilder {
            inner: Some(client_builder.request(method, url)),
        }))
    }
    async fn send_request(
        &self,
        state: &SessionState,
        request: Request,
        option_timeout_secs: Option<u64>,
        _forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError> {
        http_client::send_request(&state.conf.proxy, request, option_timeout_secs)
            .await
            .switch()
    }

    fn add_request_id(&mut self, request_id: RequestId) {
        self.request_id
            .replace(request_id.as_hyphenated().to_string());
    }

    fn get_request_id(&self) -> Option<String> {
        self.request_id.clone()
    }

    fn add_flow_name(&mut self, _flow_name: String) {}
}


pub fn new(
        payment_method: Option<common_enums::PaymentMethod>,
        payment_method_type: Option<common_enums::PaymentMethodType>,
        authentication_type: Option<common_enums::AuthenticationType>,
        currency: Option<common_enums::Currency>,
        country: Option<common_enums::CountryAlpha2>,
        card_network: Option<String>,
        card_bin: Option<String>,
    ) -> Self {
        Self {
            payment_method,
            payment_method_type,
            authentication_type,
            currency,
            country,
            card_network,
            card_bin,
        }
    }


pub struct BusinessGenericLinkConfig {
    pub domain_name: Option<String>,
    pub allowed_domains: HashSet<String>,
    #[serde(flatten)]
    pub ui_config: common_utils::link_utils::GenericLinkUiConfig,
}


impl PaymentMetricAccumulator for DebitRoutingAccumulator {
    type MetricOutput = (Option<u64>, Option<u64>, Option<u64>);

    fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) {
        if let Some(count) = metrics.count {
            self.transaction_count += u64::try_from(count).unwrap_or(0);
        }
        if let Some(total) = metrics.total.as_ref().and_then(ToPrimitive::to_u64) {
            self.savings_amount += total;
        }
    }

    fn collect(self) -> Self::MetricOutput {
        (
            Some(self.transaction_count),
            Some(self.savings_amount),
            Some(0),
        )
    }
}

impl PaymentMetricAccumulator for for DebitRoutingAccumulator


OpenAPI Block Path: components.schemas.OrganizationType
{
  "type": "string",
  "enum": [
    "standard",
    "platform"
  ]
}


File: crates/router/src/core/utils/refunds_validator.rs
 Public functions: 10

use diesel_models::refund as diesel_refund;
use error_stack::report;
use router_env::{instrument, tracing};
use time::PrimitiveDateTime;

use crate::{
    core::errors::{self, CustomResult, RouterResult},
    types::{
        self,
        api::enums as api_enums,
        storage::{self, enums},
    },
    utils::{self, OptionExt},
};

// Limit constraints for refunds list flow
pub const LOWER_LIMIT: i64 = 1;
pub const UPPER_LIMIT: i64 = 100;
pub const DEFAULT_LIMIT: i64 = 10;

#[derive(Debug, thiserror::Error)]
pub enum RefundValidationError {
    #[error("The payment attempt was not successful")]
    UnsuccessfulPaymentAttempt,
    #[error("The refund amount exceeds the amount captured")]
    RefundAmountExceedsPaymentAmount,
    #[error("The order has expired")]
    OrderExpired,
    #[error("The maximum refund count for this payment attempt")]
    MaxRefundCountReached,
    #[error("There is already another refund request for this payment attempt")]
    DuplicateRefund,
}

#[instrument(skip_all)]
pub fn validate_success_transaction(
    transaction: &storage::PaymentAttempt,
) -> CustomResult<(), RefundValidationError> {
    if transaction.status != enums::AttemptStatus::Charged {
        Err(report!(RefundValidationError::UnsuccessfulPaymentAttempt))?
    }

    Ok(())
}

#[instrument(skip_all)]
pub fn validate_refund_amount(
    amount_captured: i64,
    all_refunds: &[diesel_refund::Refund],
    refund_amount: i64,
) -> CustomResult<(), RefundValidationError> {
    let total_refunded_amount: i64 = all_refunds
        .iter()
        .filter_map(|refund| {
            if refund.refund_status != enums::RefundStatus::Failure
                && refund.refund_status != enums::RefundStatus::TransactionFailure
            {
                Some(refund.refund_amount.get_amount_as_i64())
            } else {
                None
            }
        })
        .sum();

    utils::when(
        refund_amount > (amount_captured - total_refunded_amount),
        || {
            Err(report!(
                RefundValidationError::RefundAmountExceedsPaymentAmount
            ))
        },
    )
}

#[instrument(skip_all)]
pub fn validate_payment_order_age(
    created_at: &PrimitiveDateTime,
    refund_max_age: i64,
) -> CustomResult<(), RefundValidationError> {
    let current_time = common_utils::date_time::now();

    utils::when(
        (current_time - *created_at).whole_days() > refund_max_age,
        || Err(report!(RefundValidationError::OrderExpired)),
    )
}

#[instrument(skip_all)]
pub fn validate_maximum_refund_against_payment_attempt(
    all_refunds: &[diesel_refund::Refund],
    refund_max_attempts: usize,
) -> CustomResult<(), RefundValidationError> {
    utils::when(all_refunds.len() > refund_max_attempts, || {
        Err(report!(RefundValidationError::MaxRefundCountReached))
    })
}

pub fn validate_refund_list(limit: Option<i64>) -> CustomResult<i64, errors::ApiErrorResponse> {
    match limit {
        Some(limit_val) => {
            if !(LOWER_LIMIT..=UPPER_LIMIT).contains(&limit_val) {
                Err(errors::ApiErrorResponse::InvalidRequestData {
                    message: "limit should be in between 1 and 100".to_string(),
                }
                .into())
            } else {
                Ok(limit_val)
            }
        }
        None => Ok(DEFAULT_LIMIT),
    }
}

#[cfg(feature = "v1")]
pub fn validate_for_valid_refunds(
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    connector: api_models::enums::Connector,
) -> RouterResult<()> {
    let payment_method = payment_attempt
        .payment_method
        .as_ref()
        .get_required_value("payment_method")?;

    match payment_method {
        diesel_models::enums::PaymentMethod::PayLater
        | diesel_models::enums::PaymentMethod::Wallet => {
            let payment_method_type = payment_attempt
                .payment_method_type
                .get_required_value("payment_method_type")?;

            utils::when(
                matches!(
                    (connector, payment_method_type),
                    (
                        api_models::enums::Connector::Braintree,
                        diesel_models::enums::PaymentMethodType::Paypal,
                    )
                ),
                || {
                    Err(errors::ApiErrorResponse::RefundNotPossible {
                        connector: connector.to_string(),
                    }
                    .into())
                },
            )
        }
        _ => Ok(()),
    }
}

#[cfg(feature = "v2")]
pub fn validate_for_valid_refunds(
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    connector: api_models::enums::Connector,
) -> RouterResult<()> {
    let payment_method_type = payment_attempt.payment_method_type;

    match payment_method_type {
        diesel_models::enums::PaymentMethod::PayLater
        | diesel_models::enums::PaymentMethod::Wallet => {
            let payment_method_subtype = payment_attempt.payment_method_subtype;

            utils::when(
                matches!(
                    (connector, payment_method_subtype),
                    (
                        api_models::enums::Connector::Braintree,
                        diesel_models::enums::PaymentMethodType::Paypal,
                    )
                ),
                || {
                    Err(errors::ApiErrorResponse::RefundNotPossible {
                        connector: connector.to_string(),
                    }
                    .into())
                },
            )
        }
        _ => Ok(()),
    }
}

pub fn validate_stripe_charge_refund(
    charge_type_option: Option<api_enums::PaymentChargeType>,
    split_refund_request: &Option<common_types::refunds::SplitRefund>,
) -> RouterResult<types::ChargeRefundsOptions> {
    let charge_type = charge_type_option.ok_or_else(|| {
        report!(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Missing `charge_type` in PaymentAttempt.")
    })?;

    let refund_request = match split_refund_request {
        Some(common_types::refunds::SplitRefund::StripeSplitRefund(stripe_split_refund)) => {
            stripe_split_refund
        }
        _ => Err(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "stripe_split_refund",
        })?,
    };

    let options = match charge_type {
        api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Direct) => {
            types::ChargeRefundsOptions::Direct(types::DirectChargeRefund {
                revert_platform_fee: refund_request
                    .revert_platform_fee
                    .get_required_value("revert_platform_fee")?,
            })
        }
        api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Destination) => {
            types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund {
                revert_platform_fee: refund_request
                    .revert_platform_fee
                    .get_required_value("revert_platform_fee")?,
                revert_transfer: refund_request
                    .revert_transfer
                    .get_required_value("revert_transfer")?,
            })
        }
    };

    Ok(options)
}

pub fn validate_adyen_charge_refund(
    adyen_split_payment_response: &common_types::domain::AdyenSplitData,
    adyen_split_refund_request: &common_types::domain::AdyenSplitData,
) -> RouterResult<()> {
    if adyen_split_refund_request.store != adyen_split_payment_response.store {
        return Err(report!(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "split_payments.adyen_split_payment.store",
        }));
    };

    for refund_split_item in adyen_split_refund_request.split_items.iter() {
        let refund_split_reference = refund_split_item.reference.clone();
        let matching_payment_split_item = adyen_split_payment_response
            .split_items
            .iter()
            .find(|payment_split_item| refund_split_reference == payment_split_item.reference);

        if let Some(payment_split_item) = matching_payment_split_item {
            if let Some((refund_amount, payment_amount)) =
                refund_split_item.amount.zip(payment_split_item.amount)
            {
                if refund_amount > payment_amount {
                    return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                        message: format!(
                            "Invalid refund amount for split item, reference: {refund_split_reference}",

                        ),
                    }));
                }
            }

            if let Some((refund_account, payment_account)) = refund_split_item
                .account
                .as_ref()
                .zip(payment_split_item.account.as_ref())
            {
                if !refund_account.eq(payment_account) {
                    return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                        message: format!(
                            "Invalid refund account for split item, reference: {refund_split_reference}",

                        ),
                    }));
                }
            }

            if refund_split_item.split_type != payment_split_item.split_type {
                return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "Invalid refund split_type for split item, reference: {refund_split_reference}",

                    ),
                }));
            }
        } else {
            return Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                message: format!(
                    "No matching payment split item found for reference: {refund_split_reference}",
                ),
            }));
        }
    }
    Ok(())
}
pub fn validate_xendit_charge_refund(
    xendit_split_payment_response: &common_types::payments::XenditChargeResponseData,
    xendit_split_refund_request: &common_types::domain::XenditSplitSubMerchantData,
) -> RouterResult<Option<String>> {
    match xendit_split_payment_response {
        common_types::payments::XenditChargeResponseData::MultipleSplits(
            payment_sub_merchant_data,
        ) => {
            if payment_sub_merchant_data.for_user_id
                != Some(xendit_split_refund_request.for_user_id.clone())
            {
                return Err(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id",
                }.into());
            }
            Ok(Some(xendit_split_refund_request.for_user_id.clone()))
        }
        common_types::payments::XenditChargeResponseData::SingleSplit(
            payment_sub_merchant_data,
        ) => {
            if payment_sub_merchant_data.for_user_id != xendit_split_refund_request.for_user_id {
                return Err(errors::ApiErrorResponse::InvalidDataValue {
                    field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id",
                }.into());
            }
            Ok(Some(xendit_split_refund_request.for_user_id.clone()))
        }
    }
}


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


pub fn build_payout_link_status_html(
    link_data: &GenericLinkStatusData,
    locale: &str,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let mut tera = Tera::default();
    let mut context = Context::new();

    // Insert dynamic context in CSS
    let css_dynamic_context = "{{ color_scheme }}";
    let css_template =
        include_str!("../../core/generic_link/payout_link/status/styles.css").to_string();
    let final_css = format!("{css_dynamic_context}\n{css_template}");
    let _ = tera.add_raw_template("payout_link_status_styles", &final_css);
    context.insert("color_scheme", &link_data.css_data);

    let css_style_tag = tera
        .render("payout_link_status_styles", &context)
        .map(|css| format!("<style>{css}</style>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status CSS template")?;

    // Insert dynamic context in JS
    let js_dynamic_context = "{{ script_data }}";
    let js_template =
        include_str!("../../core/generic_link/payout_link/status/script.js").to_string();
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("payout_link_status_script", &final_js);
    context.insert("script_data", &link_data.js_data);
    context::insert_locales_in_context_for_payout_link_status(&mut context, locale);
    let js_script_tag = tera
        .render("payout_link_status_script", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status JS template")?;

    // Build HTML
    let html_template =
        include_str!("../../core/generic_link/payout_link/status/index.html").to_string();
    let _ = tera.add_raw_template("payout_status_link", &html_template);
    context.insert("css_style_tag", &css_style_tag);
    context.insert("js_script_tag", &js_script_tag);

    tera.render("payout_status_link", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status HTML template")
}


OpenAPI Block Path: components.schemas.ThreeDSDecision
{
  "type": "string",
  "description": "Enum representing the possible outcomes of the 3DS Decision Rule Engine.",
  "enum": [
    "no_three_ds",
    "challenge_requested",
    "challenge_preferred",
    "three_ds_exemption_requested_tra",
    "three_ds_exemption_requested_low_value",
    "issuer_three_ds_exemption_requested"
  ]
}


pub async fn verify_connector_credentials(
    state: SessionState,
    req: VerifyConnectorRequest,
    _profile_id: Option<common_utils::id_type::ProfileId>,
) -> errors::RouterResponse<()> {
    let boxed_connector = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &req.connector_name.to_string(),
        api::GetToken::Connector,
        None,
    )
    .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven)?;

    let card_details = utils::get_test_card_details(req.connector_name)?.ok_or(
        errors::ApiErrorResponse::FlowNotSupported {
            flow: "Verify credentials".to_string(),
            connector: req.connector_name.to_string(),
        },
    )?;

    match req.connector_name {
        Connector::Stripe => {
            connector::Stripe::verify(
                &state,
                types::VerifyConnectorData {
                    connector: boxed_connector.connector,
                    connector_auth: req.connector_account_details.foreign_into(),
                    card_details,
                },
            )
            .await
        }
        Connector::Paypal => connector::Paypal::get_access_token(
            &state,
            types::VerifyConnectorData {
                connector: boxed_connector.connector,
                connector_auth: req.connector_account_details.foreign_into(),
                card_details,
            },
        )
        .await
        .map(|_| services::ApplicationResponse::StatusOk),
        _ => Err(errors::ApiErrorResponse::FlowNotSupported {
            flow: "Verify credentials".to_string(),
            connector: req.connector_name.to_string(),
        }
        .into()),
    }
}


pub struct ThunesErrorResponse {
    pub status_code: u16,
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
}


pub struct HyperswitchVaultCreateRequest {
    customer_id: String,
}


pub struct IncomingWebhookDetails {
    pub object_reference_id: ObjectReferenceId,
    pub resource_object: Vec<u8>,
}


pub fn server(state: AppState) -> Scope {
        web::scope("/v2/organizations")
            .app_data(web::Data::new(state))
            .service(web::resource("").route(web::post().to(admin::organization_create)))
            .service(
                web::scope("/{id}")
                    .service(
                        web::resource("")
                            .route(web::get().to(admin::organization_retrieve))
                            .route(web::put().to(admin::organization_update)),
                    )
                    .service(
                        web::resource("/merchant-accounts")
                            .route(web::get().to(admin::merchant_account_list)),
                    ),
            )
    }


impl api::PaymentAuthorize for Paysafe {}

impl api::PaymentAuthorize for for Paysafe


pub struct Evoucher {
    pub password_evoucher: String,
    pub username_evoucher: String,
    pub merchant_id_evoucher: String,
}


impl api::PaymentSync for Globepay {}


impl super::payments::filters::PaymentFilterAnalytics for ClickhouseClient {}

impl super::payments::filters::PaymentFilterAnalytics for for ClickhouseClient


pub struct RecurrenceRequest {
    action: NexixpayRecurringAction,
    contract_id: Secret<String>,
    contract_type: ContractType,
}


impl<K, V> IntoIter<K, V> {
    fn new(iter: vec::IntoIter<V>) -> Self {
        Self {
            inner: iter.enumerate(),
            _marker: PhantomData,
        }
    }
}


impl Responder {
        let flow = AnalyticsFlow::GetAuthEventFilters;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            json_payload.into_inner(),
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let profile_id = auth
                    .profile_id
                    .ok_or(report!(UserErrors::JwtProfileIdMissing))
                    .change_context(AnalyticsError::AccessForbiddenError)?;

                let auth: AuthInfo = AuthInfo::ProfileLevel {
                    org_id: org_id.clone(),
                    merchant_id: merchant_id.clone(),
                    profile_ids: vec![profile_id.clone()],
                };
                analytics::auth_events::get_filters(&state.pool, req, &auth)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::ProfileAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }


OpenAPI Block Path: components.schemas.AcquirerDetails
{
  "type": "object",
  "properties": {
    "acquirer_bin": {
      "type": "string",
      "description": "The bin of the card.",
      "example": "123456",
      "nullable": true
    },
    "acquirer_merchant_id": {
      "type": "string",
      "description": "The merchant id of the card.",
      "example": "merchant_abc",
      "nullable": true
    },
    "merchant_country_code": {
      "type": "string",
      "description": "The country code of the card.",
      "example": "US/34456",
      "nullable": true
    }
  }
}


pub struct AdditionalData {
    masked_pan: String,
    card_id: Secret<String>,
    card_id4: Option<Secret<String>>,
    card_expiry_date: Option<Secret<String>>,
}


pub async fn connector_retrieve() {}


impl api::Refund for Stax {}


impl api::RefundExecute for Placetopay {}

impl api::RefundExecute for for Placetopay


pub struct CeleroMandateFields {
    pub card_on_file_indicator: Option<CardOnFileIndicator>,
    pub initiated_by: Option<InitiatedBy>,
    pub initial_transaction_id: Option<String>,
    pub stored_credential_indicator: Option<StoredCredentialIndicator>,
    pub billing_method: Option<BillingMethod>,
}


Documentation: add_connector.md
# Type: Doc File

# Guide to Integrating a Connector

## Table of Contents

1. [Introduction](#introduction)
2. [Prerequisites](#prerequisites)
3. [Development Environment Setup & Configuration](#development-environment-setup--configuration)
4. [Create a Connector](#create-a-connector)
5. [Test the Connection](#test-the-connection)
6. [Folder Structure After Running the Script](#folder-structure-after-running-the-script)
7. [Common Payment Flow Types](#common-payment-flow-types)
8. [Integrate a New Connector](#integrate-a-new-connector)
9. [Code Walkthrough](#code-walkthrough)
10. [Error Handling in Hyperswitch Connectors](#error-handling-in-hyperswitch-connectors)
11. [Implementing the Connector Interface](#implementing-the-connector-interface)
12. [ConnectorCommon: The Foundation Trait](#connectorcommon-the-foundation-trait)
13. [ConnectorIntegration – The Payment Flow Orchestrator](#connectorintegration--the-payment-flow-orchestrator)
14. [Method-by-Method Breakdown](#method-by-method-breakdown)
15. [Connector Traits Overview](#connector-traits-overview)
16. [Derive Traits](#derive-traits)
17. [Connector Utility Functions](#connector-utility-functions)
18. [Connector Configuration for Control Center Integration](#connector-configuration-for-control-center-integration)
19. [Control Center Frontend Integration](#control-center-frontend-integration)
20. [Test the Connector Integration](#test-the-connector-integration)

## Introduction

This guide provides instructions on integrating a new connector with Router, from setting up the environment to implementing API interactions. In this document you’ll learn how to:

* Scaffold a new connector template
* Define Rust request/response types directly from your PSP’s JSON schema
* Implement transformers and the `ConnectorIntegration` trait for both standard auth and tokenization-first flows
* Enforce PII best practices (Secret wrappers, common\_utils::pii types) and robust error-handling
* Update the Control-Center (ConnectorTypes.res, ConnectorUtils.res, icons)
* Validate your connector with end-to-end tests

By the end, you’ll learn how to create a fully functional, production-ready connector—from blank slate to live in the Control-Center.

## Prerequisites

* Before you begin, ensure you’ve completed the initial setup in our [Hyperswitch Contributor Guide](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/docs/CONTRIBUTING.md?plain=1#L1), which covers cloning, tool installation, and access.
* You should also understanding [connectors and payment methods](https://hyperswitch.io/pm-list).
* Familiarity with the Connector API you’re integrating
* A locally set up and running Router repository
* API credentials for testing (sign up for sandbox/UAT credentials on the connector’s website).
* Need help? Join the [Hyperswitch Slack Channel](https://inviter.co/hyperswitch-slack). We also have weekly office hours every Thursday at 8:00 AM PT (11:00 AM ET, 4:00 PM BST, 5:00 PM CEST, and 8:30 PM IST). Link to office hours are shared in the **#general channel**.

## Development Environment Setup & Configuration

This guide will walk you through your environment setup and configuration.

### Clone the Hyperswitch monorepo

```bash
git clone git@github.com:juspay/hyperswitch.git
cd hyperswitch
```

### Rust Environment & Dependencies Setup

Before running Hyperswitch locally, make sure your Rust environment and system dependencies are properly configured.

**Follow the guide**:

[Configure Rust and install required dependencies based on your OS](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-a-rust-environment-and-other-dependencies)

**Quick links by OS**:
* [Ubuntu-based systems](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-dependencies-on-ubuntu-based-systems)
* [Windows (WSL2)](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-dependencies-on-windows-ubuntu-on-wsl2)
* [Windows (native)](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-dependencies-on-windows)
* [macOS](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-dependencies-on-macos)

**All OS Systems**:
* [Set up the database](https://github.com/juspay/hyperswitch/blob/main/docs/try_local_system.md#set-up-the-database)

* Set up the Rust nightly toolchain installed for code formatting:

```bash
rustup toolchain install nightly
```

* Install [Protobuf](https://protobuf.dev/installation/)

Install cargo-generate for creating project templates:

```bash
cargo install cargo-generate
```

If you've completed the setup, you should now have:

* ✅ Rust & Cargo
* ✅ `cargo-generate`
* ✅ PostgreSQL (with a user and database created)
* ✅ Redis
* ✅ `diesel_cli`
* ✅ The `just` command runner
* ✅ Database migrations applied
* ✅ Set up the Rust nightly toolchain
* ✅ Installed Protobuf

Compile and run the application using cargo:

```bash
cargo run
```

## Create a Connector
From the root of the project, generate a new connector by running the following command. Use a single-word name for your `ConnectorName`:

```bash
sh scripts/add_connector.sh <ConnectorName> <ConnectorBaseUrl>
```

When you run the script, you should see that some files were created

```bash
# Done! New project created /absolute/path/hyperswitch/crates/hyperswitch_connectors/src/connectors/connectorname
```

> ⚠ **Warning**
> Don’t be alarmed if you see test failures at this stage.
> Tests haven’t been implemented for your new connector yet, so failures are expected.
> You can safely ignore output like this:
>
> ```bash
> test result: FAILED. 0 passed; 20 failed; 0 ignored; 0 measured; 1759 filtered out; finished in 0.10s
> ```
> You can also ignore GRPC errors too.

## Test the connection
Once you've successfully created your connector using the `add_connector.sh` script, you can verify the integration by starting the Hyperswitch Router Service:

```bash
cargo r
```

This launches the router application locally on `port 8080`, providing access to the complete Hyperswitch API. You can now test your connector implementation by making HTTP requests to the payment endpoints for operations like:

- Payment authorization and capture
- Payment synchronization
- Refund processing
- Webhook handling

Once your connector logic is implemented, this environment lets you ensure it behaves correctly within the Hyperswitch orchestration flow—before moving to staging or production.

### Verify Server Health

Once the Hyperswitch Router Service is running, you can verify it's operational by checking the health endpoint in a separate terminal window:

```bash
curl --head --request GET 'http://localhost:8080/health'
```
> **Action Item**
> After creating the connector, run a health check to ensure everything is working smoothly.

### Folder Structure After Running the Script
When you run the script, it creates a specific folder structure for your new connector. Here's what gets generated:

**Main Connector Files**

The script creates the primary connector structure in the hyperswitch_connectors crate:

crates/hyperswitch_connectors/src/connectors/
├── <connector_name>/
│   └── transformers.rs
└── <connector_name>.rs

#### Test Files

The script also generates test files in the router crate:

crates/router/tests/connectors/
└── <connector_name>.rs

**What Each File Contains**

- `<connector_name>.rs`: The main connector implementation file where you implement the connector traits
- `transformers.rs`: Contains data structures and conversion logic between Hyperswitch's internal format and your payment processor's API format
- **Test file**: [Contains boilerplate test cases for your connector](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/connector-template/test.rs#L1-L36).

## Common Payment Flow Types

As you build your connector, you’ll encounter different payment flow patterns.
This section gives you:

- A quick reference table for all flows
- Examples of the two most common patterns: **Tokenization‑first** and **Direct Authorization**

> For full details, see [Connector Payment Flow documentation](https://docs.hyperswitch.io/learn-more/hyperswitch-architecture/connector-payment-flows) or ask us in Slack.

---

### 1. Flow Summary Table

| Flow Name           | Description                                      | Implementation in Hyperswitch |
|---------------------|--------------------------------------------------|--------------------------------|
| **Access Token**      | Obtain OAuth access token                        | [crates/hyperswitch_interfaces/src/types.rs#L7](https://github.com/juspay/hyperswitch/blob/06dc66c62e33c1c56c42aab18a7959e1648d6fae/crates/hyperswitch_interfaces/src/types.rs#L7) |
| **Tokenization**      | Exchange credentials for a payment token         | [crates/hyperswitch_interfaces/src/types.rs#L148](https://github.com/juspay/hyperswitch/blob/06dc66c62e33c1c56c42aab18a7959e1648d6fae/crates/hyperswitch_interfaces/src/types.rs#L148) |
| **Customer Creation** | Create or update customer records                | [crates/router/src/types.rs#L40](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L40) |
| **Pre‑Processing**    | Validation or enrichment before auth             | [crates/router/src/types.rs#L41](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L41) |
| **Authorization**     | Authorize and immediately capture payment        | [crates/hyperswitch_interfaces/src/types.rs#L12](https://github.com/juspay/hyperswitch/blob/06dc66c62e33c1c56c42aab18a7959e1648d6fae/crates/hyperswitch_interfaces/src/types.rs#L12) |
| **Authorization‑Only**| Authorize payment for later capture              | [crates/router/src/types.rs#L39](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L39) |
| **Capture**           | Capture a previously authorized payment          | [crates/router/src/types.rs#L39](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L39) |
| **Refund**            | Issue a refund                                   | [crates/router/src/types.rs#L44](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L44) |
| **Webhook Handling**  | Process asynchronous events from PSP             | [crates/router/src/types.rs#L45](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/types.rs#L45) |

---
### Flow Type Definitions

Each flow type corresponds to specific request/response data structures and connector integration patterns. All flows follow a standardized pattern with associated:

- **Request data types** (e.g., `PaymentsAuthorizeData`)
- **Response data types** (e.g., `PaymentsResponseData`)
- **Router data wrappers** for connector communication

### 2. Pattern: Tokenization‑First

Some PSPs require payment data to be tokenized before it can be authorized.
This is a **two‑step process**:

1. **Tokenization** – e.g., Billwerk’s implementation:
   - [Tokenization](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L178-L271)
   - [Authorization](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L273-L366)

2. **Authorization** – Uses the returned token rather than raw payment details.

> Most PSPs don’t require this; see the next section for direct authorization.

---

### 3. Pattern: Direct Authorization

Many connectors skip tokenization and send payment data directly in the authorization request.

- **Authorize.net** – [code](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/authorizedotnet.rs#L401-L497)
  Builds `CreateTransactionRequest` directly from payment data in `get_request_body()`.

- **Helcim** – [code](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/helcim.rs#L295-L385)
  Chooses purchase (auto‑capture) or preauth endpoint in `get_url()` and processes payment data directly.

- **Deutsche Bank** – [code](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/deutschebank.rs#L330-L461)
  Selects flow based on 3DS and payment type (card or direct debit).

**Key differences from tokenization‑first:**
- Single API call – No separate token step
- No token storage – No token management required
- Immediate processing – `get_request_body()` handles payment data directly

All implement the same `ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData>` pattern.

## Integrate a New Connector

Integrating a connector is mainly an API integration task. You'll define request and response types and implement required traits.

This section covers card payments via Billwerk. Review the API reference and test APIs before starting. You can leverage these examples for your connector of choice.

### 1. Build Payment Request and Response from JSON Schema

To generate Rust types from your connector’s OpenAPI or JSON schema, you’ll need to install the [OpenAPI Generator](https://openapi-generator.tech/).

### Example (macOS using Homebrew):

```bash
brew install openapi-generator
```
> 💡 **Note:**
> On **Linux**, you can install OpenAPI Generator using `apt`, `snap`, or by downloading the JAR from the [official site](https://openapi-generator.tech/docs/installation).
> On **Windows**, use [Scoop](https://scoop.sh/) or manually download the JAR file.

### 2. **Download the OpenAPI Specification from your connector**

First, obtain the OpenAPI specification from your payment processor's developer documentation. Most processors provide these specifications at standardized endpoints.

```bash
curl -o <ConnectorName>-openapi.json <schema-url>
```
**Specific Example**:

For Billwerk (using their sandbox environment):

```bash
curl -o billwerk-openapi.json https://sandbox.billwerk.com/swagger/v1/swagger.json
```
For other connectors, check their developer documentation for similar endpoints like:

- `/swagger/v1/swagger.json`
- `/openapi.json`
- `/api-docs`

After running the complete command, you'll have:

`crates/hyperswitch_connectors/src/connectors/{CONNECTORNAME}/temp.rs `

This single file contains all the Rust structs and types generated from your payment processor's OpenAPI specification.

The generated `temp.rs` file typically contains:

- **Request structs**: Data structures for API requests
- **Response structs**: Data structures for API responses
- **Enum types**: Status codes, payment methods, error types
- **Nested objects**: Complex data structures used within requests/responses
- **Serde annotations**: Serialization/deserialization attributes.

Otherwise, you can manually define it and create the `crates/hyperswitch_connectors/src/connectors/{CONNECTOR_NAME}/temp.rs ` file. We highly recommend using the `openapi-generator` for ease.

#### Usage in Connector Development

You can then copy and adapt these generated structs into your connector's `transformers.rs` file, following the pattern shown in the connector integration documentation. The generated code serves as a starting point that you customize for your specific connector implementation needs.

### 3. **Configure Required Environment Variables**

Set up the necessary environment variables for the OpenAPI generation process:

#### Connector name (must match the name used in add_connector.sh script)

```bash
export CONNECTOR_NAME="ConnectorName"
```

#### Path to the downloaded OpenAPI specification
```bash
export SCHEMA_PATH="/absolute/path/to/your/connector-openapi.json"
```

## Code Walkthrough

We'll walk through the `transformer.rs` file, and what needs to be implemented.

### 1. **Converts Hyperswitch's internal payment data into your connector's API request format**
 This part of the code takes your internal representation of a payment request, pulls out the token, gathers all the customer and payment fields, and packages them into a clean, JSON-serializable struct ready to send to your connector of choice (in this case Billwerk). You'll have to implement the customer and payment fields, as necessary.

 The code below extracts customer data and constructs a payment request:

```rust
//TODO: Fill the struct with respective fields
// Auth Struct

impl TryFrom<&ConnectorAuthType> for NadinebillwerkAuthType {
    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()),
        }
    }
}

```
Here's an implementation example with the Billwerk connector:

```rust
#[derive(Debug, Serialize)]
pub struct NadinebillwerkCustomerObject {
    handle: Option<id_type::CustomerId>,
    email: Option<Email>,
    address: Option<Secret<String>>,
    address2: Option<Secret<String>>,
    city: Option<String>,
    country: Option<common_enums::CountryAlpha2>,
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
}

impl TryFrom<&NadinebillwerkRouterData<&PaymentsAuthorizeRouterData>>
    for NadinebillwerkPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &NadinebillwerkRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {

        if item.router_data.is_three_ds() {
            return Err(errors::ConnectorError::NotImplemented(
                "Three_ds payments through Billwerk".to_string(),
            )
            .into());
        };

          let source = match item.router_data.get_payment_method_token()? {
            PaymentMethodToken::Token(pm_token) => Ok(pm_token),
            _ => Err(errors::ConnectorError::MissingRequiredField {
                field_name: "payment_method_token",
            }),
        }?;
        Ok(Self {
            handle: item.router_data.connector_request_reference_id.clone(),
            amount: item.amount,
            source,
            currency: item.router_data.request.currency,
            customer: NadinebillwerkCustomerObject {
                handle: item.router_data.customer_id.clone(),
                email: item.router_data.request.email.clone(),
                address: item.router_data.get_optional_billing_line1(),
                address2: item.router_data.get_optional_billing_line2(),
                city: item.router_data.get_optional_billing_city(),
                country: item.router_data.get_optional_billing_country(),
                first_name: item.router_data.get_optional_billing_first_name(),
                last_name: item.router_data.get_optional_billing_last_name(),
            },
            metadata: item.router_data.request.metadata.clone().map(Into::into),
            settle: item.router_data.request.is_auto_capture()?,
        })
    }
}
```

2. **Handle Response Mapping**

Response mapping is a critical component of connector implementation that translates payment processor–specific statuses into Hyperswitch’s standardized internal representation. This ensures consistent payment state management across all integrated payment processors.

**Define Payment Status Enum**

Create an enum that represents all possible payment statuses returned by your payment processor’s API. This enum should match the exact status values specified in your connector’s API documentation.

```rust
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BillwerkPaymentState {
    Created,
    Authorized,
    Pending,
    Settled,
    Failed,
    Cancelled,
}
```
The enum uses `#[serde(rename_all = "lowercase")]` to automatically handle JSON serialization/deserialization in the connector’s expected format.

**Implement Status Conversion**

Implement From <ConnectorStatus> for Hyperswitch’s `AttemptStatus` enum. Below is an example implementation:

```rs
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BillwerkPaymentState {
    Created,
    Authorized,
    Pending,
    Settled,
    Failed,
    Cancelled,
}

impl From<BillwerkPaymentState> for enums::AttemptStatus {
    fn from(item: BillwerkPaymentState) -> Self {
        match item {
            BillwerkPaymentState::Created | BillwerkPaymentState::Pending => Self::Pending,
            BillwerkPaymentState::Authorized => Self::Authorized,
            BillwerkPaymentState::Settled => Self::Charged,
            BillwerkPaymentState::Failed => Self::Failure,
            BillwerkPaymentState::Cancelled => Self::Voided,
        }
    }
}

```

| Connector Status       | Hyperswitch Status            | Description                          |
|------------------------|-------------------------------|--------------------------------------|
| `Created`, `Pending`   | `AttemptStatus::Pending`      | Payment is being processed           |
| `Authorized`           | `AttemptStatus::Authorized`   | Payment authorized, awaiting capture |
| `Settled`              | `AttemptStatus::Charged`      | Payment successfully completed       |
| `Failed`               | `AttemptStatus::Failure`      | Payment failed or was declined       |
| `Cancelled`            | `AttemptStatus::Voided`       | Payment was cancelled/voided         |

> **Note:** Default status should be `Pending`. Only explicit success or failure from the connector should mark the payment as `Charged` or `Failure`.

3. **Mapping Billwerk API Responses (or any PSPs) to Hyperswitch Internal Specification**

Billwerk, like most payment service providers (PSPs), has its own proprietary API response format with custom fields, naming conventions, and nested structures. However, Hyperswitch is designed to be connector-agnostic: it expects all connectors to normalize external data into a consistent internal format, so it can process payments uniformly across all supported PSPs.

The response struct acts as the translator between these two systems. This process ensures that regardless of which connector you're using, Hyperswitch can process payment responses consistently.

```rs
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BillwerkPaymentsResponse {
    state: BillwerkPaymentState,
    handle: String,
    error: Option<String>,
    error_state: Option<String>,
}
```
**Key Fields Explained**:

- **state**: Payment status using the enum we defined earlier
- **handle**: Billwerk's unique transaction identifier
- **error & error_state**: Optional error information for failure scenarios

The `try_from` function converts connector-specific, like Billwerk, response data into Hyperswitch's standardized format:

```rs
impl<F, T> TryFrom<ResponseRouterData<F, BillwerkPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BillwerkPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let error_response = if item.response.error.is_some() || item.response.error_state.is_some()
        {
            Some(ErrorResponse {
                code: item
                    .response
                    .error_state
                    .clone()
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: item
                    .response
                    .error_state
                    .unwrap_or(NO_ERROR_MESSAGE.to_string()),
                reason: item.response.error,
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: Some(item.response.handle.clone()),
            })
        } else {
            None
        };
        let payments_response = PaymentsResponseData::TransactionResponse {
            resource_id: ResponseId::ConnectorTransactionId(item.response.handle.clone()),
            redirection_data: Box::new(None),
            mandate_reference: Box::new(None),
            connector_metadata: None,
            network_txn_id: None,
            connector_response_reference_id: Some(item.response.handle),
            incremental_authorization_allowed: None,
            charges: None,
        };
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.state),
            response: error_response.map_or_else(|| Ok(payments_response), Err),
            ..item.data
        })
    }
}
```
### Transformation Logic:

- **Error Handling**: Checks for error conditions first and creates appropriate error responses
- **Status Mapping**: Converts BillwerkPaymentState to standardized AttemptStatus using our enum mapping
- **Data Extraction**: Maps PSP-specific fields to Hyperswitch's PaymentsResponseData structure
- **Metadata Preservation**: Ensures important transaction details are retained

#### Critical Response Fields

The transformation populates these essential Hyperswitch fields:

- **resource_id**: Maps to connector transaction ID for future operations
- **connector_response_reference_id**: Preserves PSP's reference for dashboard linking
- **status**: Standardized payment status for consistent processing
- **redirection_data**: Handles 3DS or other redirect flows
- **network_txn_id**: Captures network-level transaction identifiers

### Field Mapping Patterns:

Each critical response field requires specific implementation patterns to ensure consistent behavior across all Hyperswitch connectors.

- **connector_request_reference_id**: This field carries the merchant’s reference ID and is populated during request construction. It is sent to the PSP to support end-to-end transaction traceability.

```rs
reference: item.router_data.connector_request_reference_id.clone(),
```

- **connector_response_reference_id**: Stores the payment processor’s transaction reference and is used for downstream reconciliation and dashboard visibility. Prefer the PSP's designated reference field if available; otherwise, fall back to the transaction ID. This ensures accurate linkage across merchant dashboards, support tools, and internal systems.

```rs
connector_response_reference_id: item.response.reference.or(Some(item.response.id)),
```

- **resource_id**: Defines the primary resource identifier used for subsequent operations such as captures, refunds, and syncs. Typically sourced from the connector’s transaction ID. If the transaction ID is unavailable, use ResponseId::NoResponseId as a fallback to preserve type safety.

```rs
`resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()),
```

- **redirection_data**: Captures redirection details required for authentication flows such as 3DS. If the connector provides a redirect URL, populate this field accordingly. For advanced flows involving form submissions, construct a `RedirectForm::Form` using the target endpoint, HTTP method, and form fields.

```rs
let redirection_data = item.response.links.redirect.map(|href| {
    services::RedirectForm::from((href.redirection_url, services::Method::Get))
});
```

- **network_txn_id**: Stores the transaction identifier issued by the underlying payment network (e.g., Visa, Mastercard). This field is optional but highly useful for advanced reconciliation, chargeback handling, and network-level dispute resolution—especially when the network ID differs from the PSP’s transaction ID.

```rs
network_txn_id: item.response.network_transaction_id.clone(),
```

4. **Error Handling in Hyperswitch Connectors**

Hyperswitch connectors implement a structured error-handling mechanism that categorizes HTTP error responses by type. By distinguishing between client-side errors (4xx) and server-side errors (5xx), the system enables more precise handling strategies tailored to the source of the failure.

**Error Response Structure**

Billwerk defines its error response format to capture failure information from API calls. You can find this in the `transformer.rs file`:

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

- **code**: Optional integer error code from Billwerk
- **error**: Required string describing the error
- **message**: Optional additional error messagecode: Optional integer error code from Billwerk
- **error**: Required string describing the error
message: Optional additional error message

**Error Handling Methods**

Hyperswitch uses separate methods for different HTTP error types:

- **4xx Client Errors**: [`get_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L692) handles authentication failures, validation errors, and malformed requests.
- **5xx Server Errors**:
[`get_5xx_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L700) handles internal server errors with potential retry logic.

Both methods delegate to [`build_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L136) for consistent processing.

**Error Processing Flow**

The [`build_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L136) struct serves as the intermediate data structure that bridges Billwerk's API error format and Hyperswitch's standardized error format by taking the `BillwerkErrorResponse` struct as input:

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

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

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response
                .code
                .map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()),
            message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()),
            reason: Some(response.error),
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
        })
    }
}
```
The method performs these key operations:

- Parses the HTTP response - Deserializes the raw HTTP response into a BillwerkErrorResponse struct using `parse_struct("BillwerkErrorResponse")`

- Logs the response - Records the connector response for debugging via `event_builder` and `router_env::logger::info!`

- Transforms error format - Maps Billwerk's error fields to Hyperswitch's standardized `ErrorResponse` structure with appropriate fallbacks:
- - Uses `response.code` maps to `code` (with `NO_ERROR_CODE fallback`)
- - Uses `response.message` maps to `message` (with `NO_ERROR_MESSAGE fallback`)
- -  Maps `response.error` to the `reason` field

> [!NOTE]
> When the connector provides only a single error message field, populate both the `message` and `reason` fields in the `ErrorResponse` with the same value. The `message` field is used for smart retries logic, while the `reason` field is displayed on the Hyperswitch dashboard.

### Automatic Error Routing

Hyperswitch's core API automatically routes errors based on HTTP status codes. You can find the details here: [`crates/router/src/services/api.rs`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/router/src/services/api.rs#L1).

- 4xx → [`get_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L256)
- 5xx → [`get_5xx_error_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L264)
- 2xx → [`handle_response`](https://github.com/juspay/hyperswitch/blob/2309c5311cb9a01ef371f3a3ef7c62c88a043696/crates/hyperswitch_connectors/src/connectors/billwerk.rs#L332)

### Integration Pattern


impl ProfileUpdateInternal {
    pub fn apply_changeset(self, source: Profile) -> Profile {
        let Self {
            profile_name,
            modified_at,
            return_url,
            enable_payment_response_hash,
            payment_response_hash_key,
            redirect_to_merchant_with_http_post,
            webhook_details,
            metadata,
            is_recon_enabled,
            applepay_verified_domains,
            payment_link_config,
            session_expiry,
            authentication_connector_details,
            payout_link_config,
            is_extended_card_info_enabled,
            extended_card_info_config,
            is_connector_agnostic_mit_enabled,
            use_billing_as_payment_method_billing,
            collect_shipping_details_from_wallet_connector,
            collect_billing_details_from_wallet_connector,
            outgoing_webhook_custom_http_headers,
            always_collect_billing_details_from_wallet_connector,
            always_collect_shipping_details_from_wallet_connector,
            tax_connector_id,
            is_tax_connector_enabled,
            routing_algorithm_id,
            order_fulfillment_time,
            order_fulfillment_time_origin,
            frm_routing_algorithm_id,
            payout_routing_algorithm_id,
            default_fallback_routing,
            should_collect_cvv_during_payment,
            is_network_tokenization_enabled,
            is_auto_retries_enabled,
            max_auto_retries_enabled,
            is_click_to_pay_enabled,
            authentication_product_ids,
            three_ds_decision_manager_config,
            card_testing_guard_config,
            card_testing_secret_key,
            is_clear_pan_retries_enabled,
            is_debit_routing_enabled,
            merchant_business_country,
            revenue_recovery_retry_algorithm_type,
            revenue_recovery_retry_algorithm_data,
            is_iframe_redirection_enabled,
            is_external_vault_enabled,
            external_vault_connector_details,
            merchant_category_code,
            merchant_country_code,
            split_txns_enabled,
        } = self;
        Profile {
            id: source.id,
            merchant_id: source.merchant_id,
            profile_name: profile_name.unwrap_or(source.profile_name),
            created_at: source.created_at,
            modified_at,
            return_url: return_url.or(source.return_url),
            enable_payment_response_hash: enable_payment_response_hash
                .unwrap_or(source.enable_payment_response_hash),
            payment_response_hash_key: payment_response_hash_key
                .or(source.payment_response_hash_key),
            redirect_to_merchant_with_http_post: redirect_to_merchant_with_http_post
                .unwrap_or(source.redirect_to_merchant_with_http_post),
            webhook_details: webhook_details.or(source.webhook_details),
            metadata: metadata.or(source.metadata),
            is_recon_enabled: is_recon_enabled.unwrap_or(source.is_recon_enabled),
            applepay_verified_domains: applepay_verified_domains
                .or(source.applepay_verified_domains),
            payment_link_config: payment_link_config.or(source.payment_link_config),
            session_expiry: session_expiry.or(source.session_expiry),
            authentication_connector_details: authentication_connector_details
                .or(source.authentication_connector_details),
            payout_link_config: payout_link_config.or(source.payout_link_config),
            is_extended_card_info_enabled: is_extended_card_info_enabled
                .or(source.is_extended_card_info_enabled),
            is_connector_agnostic_mit_enabled: is_connector_agnostic_mit_enabled
                .or(source.is_connector_agnostic_mit_enabled),
            extended_card_info_config: extended_card_info_config
                .or(source.extended_card_info_config),
            use_billing_as_payment_method_billing: use_billing_as_payment_method_billing
                .or(source.use_billing_as_payment_method_billing),
            collect_shipping_details_from_wallet_connector:
                collect_shipping_details_from_wallet_connector
                    .or(source.collect_shipping_details_from_wallet_connector),
            collect_billing_details_from_wallet_connector:
                collect_billing_details_from_wallet_connector
                    .or(source.collect_billing_details_from_wallet_connector),
            outgoing_webhook_custom_http_headers: outgoing_webhook_custom_http_headers
                .or(source.outgoing_webhook_custom_http_headers),
            always_collect_billing_details_from_wallet_connector:
                always_collect_billing_details_from_wallet_connector
                    .or(always_collect_billing_details_from_wallet_connector),
            always_collect_shipping_details_from_wallet_connector:
                always_collect_shipping_details_from_wallet_connector
                    .or(always_collect_shipping_details_from_wallet_connector),
            tax_connector_id: tax_connector_id.or(source.tax_connector_id),
            is_tax_connector_enabled: is_tax_connector_enabled.or(source.is_tax_connector_enabled),
            routing_algorithm_id: routing_algorithm_id.or(source.routing_algorithm_id),
            order_fulfillment_time: order_fulfillment_time.or(source.order_fulfillment_time),
            order_fulfillment_time_origin: order_fulfillment_time_origin
                .or(source.order_fulfillment_time_origin),
            frm_routing_algorithm_id: frm_routing_algorithm_id.or(source.frm_routing_algorithm_id),
            payout_routing_algorithm_id: payout_routing_algorithm_id
                .or(source.payout_routing_algorithm_id),
            default_fallback_routing: default_fallback_routing.or(source.default_fallback_routing),
            should_collect_cvv_during_payment: should_collect_cvv_during_payment
                .or(source.should_collect_cvv_during_payment),
            version: source.version,
            dynamic_routing_algorithm: None,
            is_network_tokenization_enabled: is_network_tokenization_enabled
                .unwrap_or(source.is_network_tokenization_enabled),
            is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled),
            max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled),
            always_request_extended_authorization: None,
            is_click_to_pay_enabled: is_click_to_pay_enabled
                .unwrap_or(source.is_click_to_pay_enabled),
            authentication_product_ids: authentication_product_ids
                .or(source.authentication_product_ids),
            three_ds_decision_manager_config: three_ds_decision_manager_config
                .or(source.three_ds_decision_manager_config),
            card_testing_guard_config: card_testing_guard_config
                .or(source.card_testing_guard_config),
            card_testing_secret_key: card_testing_secret_key.or(source.card_testing_secret_key),
            is_clear_pan_retries_enabled: is_clear_pan_retries_enabled
                .unwrap_or(source.is_clear_pan_retries_enabled),
            force_3ds_challenge: None,
            is_debit_routing_enabled: is_debit_routing_enabled
                .unwrap_or(source.is_debit_routing_enabled),
            merchant_business_country: merchant_business_country
                .or(source.merchant_business_country),
            revenue_recovery_retry_algorithm_type: revenue_recovery_retry_algorithm_type
                .or(source.revenue_recovery_retry_algorithm_type),
            revenue_recovery_retry_algorithm_data: revenue_recovery_retry_algorithm_data
                .or(source.revenue_recovery_retry_algorithm_data),
            is_iframe_redirection_enabled: is_iframe_redirection_enabled
                .or(source.is_iframe_redirection_enabled),
            is_external_vault_enabled: is_external_vault_enabled
                .or(source.is_external_vault_enabled),
            external_vault_connector_details: external_vault_connector_details
                .or(source.external_vault_connector_details),
            three_ds_decision_rule_algorithm: None,
            acquirer_config_map: None,
            merchant_category_code: merchant_category_code.or(source.merchant_category_code),
            merchant_country_code: merchant_country_code.or(source.merchant_country_code),
            dispute_polling_interval: None,
            split_txns_enabled: split_txns_enabled.or(source.split_txns_enabled),
            is_manual_retry_enabled: None,
        }
    }
}


pub struct UnifiedAuthenticationServicePreAuthenticateRequest {
    pub authenticate_by: String,
    pub session_id: common_utils::id_type::AuthenticationId,
    pub source_authentication_id: common_utils::id_type::AuthenticationId,
    pub authentication_info: Option<AuthenticationInfo>,
    pub service_details: Option<CtpServiceDetails>,
    pub customer_details: Option<CustomerDetails>,
    pub pmt_details: Option<PaymentDetails>,
    pub auth_creds: UnifiedAuthenticationServiceAuthType,
    pub transaction_details: Option<TransactionDetails>,
    pub acquirer_details: Option<Acquirer>,
    pub billing_address: Option<Address>,
}


OpenAPI Block Path: components.schemas.KakaoPayRedirection
{
  "type": "object"
}