File: crates/router/src/services/api.rs
 Public functions: 20
 Public structs: 1

pub mod client;
pub mod generic_link_response;
pub mod request;
use std::{
    collections::{HashMap, HashSet},
    fmt::Debug,
    future::Future,
    str,
    sync::Arc,
    time::{Duration, Instant},
};

use actix_http::header::HeaderMap;
use actix_web::{
    body,
    http::header::{HeaderName, HeaderValue},
    web, FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
};
pub use client::{ApiClient, MockApiClient, ProxyClient};
pub use common_enums::enums::PaymentAction;
pub use common_utils::request::{ContentType, Method, Request, RequestBuilder};
use common_utils::{
    consts::{DEFAULT_TENANT, TENANT_HEADER, X_HS_LATENCY},
    errors::{ErrorSwitch, ReportSwitchExt},
    request::RequestContent,
};
use error_stack::{report, Report, ResultExt};
use hyperswitch_domain_models::router_data_v2::flow_common_types as common_types;
pub use hyperswitch_domain_models::{
    api::{
        ApplicationResponse, GenericExpiredLinkData, GenericLinkFormData, GenericLinkStatusData,
        GenericLinks, PaymentLinkAction, PaymentLinkFormData, PaymentLinkStatusData,
        RedirectionFormData,
    },
    payment_method_data::PaymentMethodData,
    router_response_types::RedirectForm,
};
pub use hyperswitch_interfaces::{
    api::{
        BoxedConnectorIntegration, CaptureSyncMethod, ConnectorIntegration,
        ConnectorIntegrationAny, ConnectorRedirectResponse, ConnectorSpecifications,
        ConnectorValidation,
    },
    connector_integration_v2::{
        BoxedConnectorIntegrationV2, ConnectorIntegrationAnyV2, ConnectorIntegrationV2,
    },
};
use masking::{Maskable, PeekInterface, Secret};
use router_env::{instrument, tracing, tracing_actix_web::RequestId, Tag};
use serde::Serialize;
use serde_json::json;
use tera::{Context, Error as TeraError, Tera};

use super::{
    authentication::AuthenticateAndFetch,
    connector_integration_interface::BoxedConnectorIntegrationInterface,
};
use crate::{
    configs::Settings,
    consts,
    core::{
        api_locking,
        errors::{self, CustomResult},
        payments, unified_connector_service,
    },
    events::{
        api_logs::{ApiEvent, ApiEventMetric, ApiEventsType},
        connector_api_logs::ConnectorEvent,
    },
    headers, logger,
    routes::{
        app::{AppStateInfo, ReqState, SessionStateInfo},
        metrics, AppState, SessionState,
    },
    services::{
        connector_integration_interface::RouterDataConversion,
        generic_link_response::build_generic_link_html,
    },
    types::{self, api, ErrorResponse},
    utils,
};

pub type BoxedPaymentConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::PaymentFlowData, Req, Resp>;
pub type BoxedRefundConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::RefundFlowData, Req, Resp>;
#[cfg(feature = "frm")]
pub type BoxedFrmConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::FrmFlowData, Req, Resp>;
pub type BoxedDisputeConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::DisputesFlowData, Req, Resp>;
pub type BoxedMandateRevokeConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::MandateRevokeFlowData, Req, Resp>;
#[cfg(feature = "payouts")]
pub type BoxedPayoutConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::PayoutFlowData, Req, Resp>;
pub type BoxedWebhookSourceVerificationConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::WebhookSourceVerifyData, Req, Resp>;
pub type BoxedExternalAuthenticationConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::ExternalAuthenticationFlowData, Req, Resp>;
pub type BoxedAuthenticationTokenConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::AuthenticationTokenFlowData, Req, Resp>;
pub type BoxedAccessTokenConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::AccessTokenFlowData, Req, Resp>;
pub type BoxedFilesConnectorIntegrationInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::FilesFlowData, Req, Resp>;
pub type BoxedRevenueRecoveryRecordBackInterface<T, Req, Res> =
    BoxedConnectorIntegrationInterface<T, common_types::RevenueRecoveryRecordBackData, Req, Res>;
pub type BoxedBillingConnectorInvoiceSyncIntegrationInterface<T, Req, Res> =
    BoxedConnectorIntegrationInterface<
        T,
        common_types::BillingConnectorInvoiceSyncFlowData,
        Req,
        Res,
    >;

pub type BoxedUnifiedAuthenticationServiceInterface<T, Req, Resp> =
    BoxedConnectorIntegrationInterface<T, common_types::UasFlowData, Req, Resp>;

pub type BoxedBillingConnectorPaymentsSyncIntegrationInterface<T, Req, Res> =
    BoxedConnectorIntegrationInterface<
        T,
        common_types::BillingConnectorPaymentsSyncFlowData,
        Req,
        Res,
    >;
pub type BoxedVaultConnectorIntegrationInterface<T, Req, Res> =
    BoxedConnectorIntegrationInterface<T, common_types::VaultConnectorFlowData, Req, Res>;

/// Handle UCS webhook response processing
fn handle_ucs_response<T, Req, Resp>(
    router_data: types::RouterData<T, Req, Resp>,
    transform_data_bytes: Vec<u8>,
) -> CustomResult<types::RouterData<T, Req, Resp>, errors::ConnectorError>
where
    T: Clone + Debug + 'static,
    Req: Debug + Clone + 'static,
    Resp: Debug + Clone + 'static,
{
    let webhook_transform_data: unified_connector_service::WebhookTransformData =
        serde_json::from_slice(&transform_data_bytes)
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)
            .attach_printable("Failed to deserialize UCS webhook transform data")?;

    let webhook_content = webhook_transform_data
        .webhook_content
        .ok_or(errors::ConnectorError::ResponseDeserializationFailed)
        .attach_printable("UCS webhook transform data missing webhook_content")?;

    let payment_get_response = match webhook_content.content {
        Some(unified_connector_service_client::payments::webhook_response_content::Content::PaymentsResponse(payments_response)) => {
            Ok(payments_response)
        },
        Some(unified_connector_service_client::payments::webhook_response_content::Content::RefundsResponse(_)) => {
            Err(errors::ConnectorError::ProcessingStepFailed(Some("UCS webhook contains refund response but payment processing was expected".to_string().into())).into())
        },
        Some(unified_connector_service_client::payments::webhook_response_content::Content::DisputesResponse(_)) => {
            Err(errors::ConnectorError::ProcessingStepFailed(Some("UCS webhook contains dispute response but payment processing was expected".to_string().into())).into())
        },
        None => {
            Err(errors::ConnectorError::ResponseDeserializationFailed)
                .attach_printable("UCS webhook content missing payments_response")
        }
    }?;

    let (status, router_data_response, status_code) =
        unified_connector_service::handle_unified_connector_service_response_for_payment_get(
            payment_get_response.clone(),
        )
        .change_context(errors::ConnectorError::ProcessingStepFailed(None))
        .attach_printable("Failed to process UCS webhook response using PSync handler")?;

    let mut updated_router_data = router_data;
    updated_router_data.status = status;

    let _ = router_data_response.map_err(|error_response| {
        updated_router_data.response = Err(error_response);
    });
    updated_router_data.raw_connector_response =
        payment_get_response.raw_connector_response.map(Secret::new);
    updated_router_data.connector_http_status_code = Some(status_code);

    Ok(updated_router_data)
}

fn store_raw_connector_response_if_required<T, Req, Resp>(
    return_raw_connector_response: Option<bool>,
    router_data: &mut types::RouterData<T, Req, Resp>,
    body: &types::Response,
) -> CustomResult<(), errors::ConnectorError>
where
    T: Clone + Debug + 'static,
    Req: Debug + Clone + 'static,
    Resp: Debug + Clone + 'static,
{
    if return_raw_connector_response == Some(true) {
        let mut decoded = String::from_utf8(body.response.as_ref().to_vec())
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        if decoded.starts_with('\u{feff}') {
            decoded = decoded.trim_start_matches('\u{feff}').to_string();
        }
        router_data.raw_connector_response = Some(Secret::new(decoded));
    }
    Ok(())
}

/// Handle the flow by interacting with connector module
/// `connector_request` is applicable only in case if the `CallConnectorAction` is `Trigger`
/// In other cases, It will be created if required, even if it is not passed
#[instrument(skip_all, fields(connector_name, payment_method))]
pub async fn execute_connector_processing_step<
    'b,
    'a,
    T,
    ResourceCommonData: Clone + RouterDataConversion<T, Req, Resp> + 'static,
    Req: Debug + Clone + 'static,
    Resp: Debug + Clone + 'static,
>(
    state: &'b SessionState,
    connector_integration: BoxedConnectorIntegrationInterface<T, ResourceCommonData, Req, Resp>,
    req: &'b types::RouterData<T, Req, Resp>,
    call_connector_action: payments::CallConnectorAction,
    connector_request: Option<Request>,
    return_raw_connector_response: Option<bool>,
) -> CustomResult<types::RouterData<T, Req, Resp>, errors::ConnectorError>
where
    T: Clone + Debug + 'static,
    // BoxedConnectorIntegration<T, Req, Resp>: 'b,
{
    // If needed add an error stack as follows
    // connector_integration.build_request(req).attach_printable("Failed to build request");
    tracing::Span::current().record("connector_name", &req.connector);
    tracing::Span::current().record("payment_method", req.payment_method.to_string());
    logger::debug!(connector_request=?connector_request);
    let mut router_data = req.clone();
    match call_connector_action {
        payments::CallConnectorAction::HandleResponse(res) => {
            let response = types::Response {
                headers: None,
                response: res.into(),
                status_code: 200,
            };
            connector_integration.handle_response(req, None, response)
        }
        payments::CallConnectorAction::UCSHandleResponse(transform_data_bytes) => {
            handle_ucs_response(router_data, transform_data_bytes)
        }
        payments::CallConnectorAction::Avoid => Ok(router_data),
        payments::CallConnectorAction::StatusUpdate {
            status,
            error_code,
            error_message,
        } => {
            router_data.status = status;
            let error_response = if error_code.is_some() | error_message.is_some() {
                Some(ErrorResponse {
                    code: error_code.unwrap_or(consts::NO_ERROR_CODE.to_string()),
                    message: error_message.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
                    status_code: 200, // This status code is ignored in redirection response it will override with 302 status code.
                    reason: None,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            } else {
                None
            };
            router_data.response = error_response.map(Err).unwrap_or(router_data.response);
            Ok(router_data)
        }
        payments::CallConnectorAction::Trigger => {
            metrics::CONNECTOR_CALL_COUNT.add(
                1,
                router_env::metric_attributes!(
                    ("connector", req.connector.to_string()),
                    (
                        "flow",
                        std::any::type_name::<T>()
                            .split("::")
                            .last()
                            .unwrap_or_default()
                    ),
                ),
            );

            let connector_request = match connector_request {
                Some(connector_request) => Some(connector_request),
                None => connector_integration
                    .build_request(req, &state.conf.connectors)
                    .inspect_err(|error| {
                        if matches!(
                            error.current_context(),
                            &errors::ConnectorError::RequestEncodingFailed
                                | &errors::ConnectorError::RequestEncodingFailedWithReason(_)
                        ) {
                            metrics::REQUEST_BUILD_FAILURE.add(
                                1,
                                router_env::metric_attributes!((
                                    "connector",
                                    req.connector.clone()
                                )),
                            )
                        }
                    })?,
            };

            match connector_request {
                Some(request) => {
                    let masked_request_body = match &request.body {
                        Some(request) => match request {
                            RequestContent::Json(i)
                            | RequestContent::FormUrlEncoded(i)
                            | RequestContent::Xml(i) => i
                                .masked_serialize()
                                .unwrap_or(json!({ "error": "failed to mask serialize"})),
                            RequestContent::FormData(_) => json!({"request_type": "FORM_DATA"}),
                            RequestContent::RawBytes(_) => json!({"request_type": "RAW_BYTES"}),
                        },
                        None => serde_json::Value::Null,
                    };
                    let request_url = request.url.clone();
                    let request_method = request.method;
                    let current_time = Instant::now();
                    let response =
                        call_connector_api(state, request, "execute_connector_processing_step")
                            .await;
                    let external_latency = current_time.elapsed().as_millis();
                    logger::info!(raw_connector_request=?masked_request_body);
                    let status_code = response
                        .as_ref()
                        .map(|i| {
                            i.as_ref()
                                .map_or_else(|value| value.status_code, |value| value.status_code)
                        })
                        .unwrap_or_default();
                    let mut connector_event = ConnectorEvent::new(
                        state.tenant.tenant_id.clone(),
                        req.connector.clone(),
                        std::any::type_name::<T>(),
                        masked_request_body,
                        request_url,
                        request_method,
                        req.payment_id.clone(),
                        req.merchant_id.clone(),
                        state.request_id.as_ref(),
                        external_latency,
                        req.refund_id.clone(),
                        req.dispute_id.clone(),
                        status_code,
                    );

                    match response {
                        Ok(body) => {
                            let response = match body {
                                Ok(body) => {
                                    let connector_http_status_code = Some(body.status_code);
                                    let handle_response_result = connector_integration
                                        .handle_response(req, Some(&mut connector_event), body.clone())
                                        .inspect_err(|error| {
                                            if error.current_context()
                                            == &errors::ConnectorError::ResponseDeserializationFailed
                                        {
                                            metrics::RESPONSE_DESERIALIZATION_FAILURE.add(

                                                1,
                                                router_env::metric_attributes!((
                                                    "connector",
                                                    req.connector.clone(),
                                                )),
                                            )
                                        }
                                        });
                                    match handle_response_result {
                                        Ok(mut data) => {
                                            state.event_handler().log_event(&connector_event);
                                            data.connector_http_status_code =
                                                connector_http_status_code;
                                            // Add up multiple external latencies in case of multiple external calls within the same request.
                                            data.external_latency = Some(
                                                data.external_latency
                                                    .map_or(external_latency, |val| {
                                                        val + external_latency
                                                    }),
                                            );

                                            store_raw_connector_response_if_required(
                                                return_raw_connector_response,
                                                &mut data,
                                                &body,
                                            )?;

                                            Ok(data)
                                        }
                                        Err(err) => {
                                            connector_event
                                                .set_error(json!({"error": err.to_string()}));

                                            state.event_handler().log_event(&connector_event);
                                            Err(err)
                                        }
                                    }?
                                }
                                Err(body) => {
                                    router_data.connector_http_status_code = Some(body.status_code);
                                    router_data.external_latency = Some(
                                        router_data
                                            .external_latency
                                            .map_or(external_latency, |val| val + external_latency),
                                    );
                                    metrics::CONNECTOR_ERROR_RESPONSE_COUNT.add(
                                        1,
                                        router_env::metric_attributes!((
                                            "connector",
                                            req.connector.clone(),
                                        )),
                                    );

                                    store_raw_connector_response_if_required(
                                        return_raw_connector_response,
                                        &mut router_data,
                                        &body,
                                    )?;

                                    let error = match body.status_code {
                                        500..=511 => {
                                            let error_res = connector_integration
                                                .get_5xx_error_response(
                                                    body,
                                                    Some(&mut connector_event),
                                                )?;
                                            state.event_handler().log_event(&connector_event);
                                            error_res
                                        }
                                        _ => {
                                            let error_res = connector_integration
                                                .get_error_response(
                                                    body,
                                                    Some(&mut connector_event),
                                                )?;
                                            if let Some(status) = error_res.attempt_status {
                                                router_data.status = status;
                                            };
                                            state.event_handler().log_event(&connector_event);
                                            error_res
                                        }
                                    };

                                    router_data.response = Err(error);

                                    router_data
                                }
                            };
                            Ok(response)
                        }
                        Err(error) => {
                            connector_event.set_error(json!({"error": error.to_string()}));
                            state.event_handler().log_event(&connector_event);
                            if error.current_context().is_upstream_timeout() {
                                let error_response = ErrorResponse {
                                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
                                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
                                    reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()),
                                    status_code: 504,
                                    attempt_status: None,
                                    connector_transaction_id: None,
                                    network_advice_code: None,
                                    network_decline_code: None,
                                    network_error_message: None,
                                    connector_metadata: None,
                                };
                                router_data.response = Err(error_response);
                                router_data.connector_http_status_code = Some(504);
                                router_data.external_latency = Some(
                                    router_data
                                        .external_latency
                                        .map_or(external_latency, |val| val + external_latency),
                                );
                                Ok(router_data)
                            } else {
                                Err(error.change_context(
                                    errors::ConnectorError::ProcessingStepFailed(None),
                                ))
                            }
                        }
                    }
                }
                None => Ok(router_data),
            }
        }
    }
}

#[instrument(skip_all)]
pub async fn call_connector_api(
    state: &SessionState,
    request: Request,
    flow_name: &str,
) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> {
    let current_time = Instant::now();
    let headers = request.headers.clone();
    let url = request.url.clone();
    let response = state
        .api_client
        .send_request(state, request, None, true)
        .await;

    match response.as_ref() {
        Ok(resp) => {
            let status_code = resp.status().as_u16();
            let elapsed_time = current_time.elapsed();
            logger::info!(
                ?headers,
                url,
                status_code,
                flow=?flow_name,
                ?elapsed_time
            );
        }
        Err(err) => {
            logger::info!(
                call_connector_api_error=?err
            );
        }
    }

    handle_response(response).await
}

#[instrument(skip_all)]
async fn handle_response(
    response: CustomResult<reqwest::Response, errors::ApiClientError>,
) -> CustomResult<Result<types::Response, types::Response>, errors::ApiClientError> {
    response
        .map(|response| async {
            logger::info!(?response);
            let status_code = response.status().as_u16();
            let headers = Some(response.headers().to_owned());
            match status_code {
                200..=202 | 302 | 204 => {
                    // If needed add log line
                    // logger:: error!( error_parsing_response=?err);
                    let response = response
                        .bytes()
                        .await
                        .change_context(errors::ApiClientError::ResponseDecodingFailed)
                        .attach_printable("Error while waiting for response")?;
                    Ok(Ok(types::Response {
                        headers,
                        response,
                        status_code,
                    }))
                }

                status_code @ 500..=599 => {
                    let bytes = response.bytes().await.map_err(|error| {
                        report!(error)
                            .change_context(errors::ApiClientError::ResponseDecodingFailed)
                            .attach_printable("Client error response received")
                    })?;
                    // let error = match status_code {
                    //     500 => errors::ApiClientError::InternalServerErrorReceived,
                    //     502 => errors::ApiClientError::BadGatewayReceived,
                    //     503 => errors::ApiClientError::ServiceUnavailableReceived,
                    //     504 => errors::ApiClientError::GatewayTimeoutReceived,
                    //     _ => errors::ApiClientError::UnexpectedServerResponse,
                    // };
                    Ok(Err(types::Response {
                        headers,
                        response: bytes,
                        status_code,
                    }))
                }

                status_code @ 400..=499 => {
                    let bytes = response.bytes().await.map_err(|error| {
                        report!(error)
                            .change_context(errors::ApiClientError::ResponseDecodingFailed)
                            .attach_printable("Client error response received")
                    })?;
                    /* let error = match status_code {
                        400 => errors::ApiClientError::BadRequestReceived(bytes),
                        401 => errors::ApiClientError::UnauthorizedReceived(bytes),
                        403 => errors::ApiClientError::ForbiddenReceived,
                        404 => errors::ApiClientError::NotFoundReceived(bytes),
                        405 => errors::ApiClientError::MethodNotAllowedReceived,
                        408 => errors::ApiClientError::RequestTimeoutReceived,
                        422 => errors::ApiClientError::UnprocessableEntityReceived(bytes),
                        429 => errors::ApiClientError::TooManyRequestsReceived,
                        _ => errors::ApiClientError::UnexpectedServerResponse,
                    };
                    Err(report!(error).attach_printable("Client error response received"))
                        */
                    Ok(Err(types::Response {
                        headers,
                        response: bytes,
                        status_code,
                    }))
                }

                _ => Err(report!(errors::ApiClientError::UnexpectedServerResponse)
                    .attach_printable("Unexpected response from server")),
            }
        })?
        .await
}

#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct ApplicationRedirectResponse {
    pub url: String,
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AuthFlow {
    Client,
    Merchant,
}

#[allow(clippy::too_many_arguments)]
#[instrument(
    skip(request, payload, state, func, api_auth, incoming_request_header),
    fields(merchant_id)
)]
pub async fn server_wrap_util<'a, 'b, U, T, Q, F, Fut, E, OErr>(
    flow: &'a impl router_env::types::FlowMetric,
    state: web::Data<AppState>,
    incoming_request_header: &HeaderMap,
    request: &'a HttpRequest,
    payload: T,
    func: F,
    api_auth: &dyn AuthenticateAndFetch<U, SessionState>,
    lock_action: api_locking::LockAction,
) -> CustomResult<ApplicationResponse<Q>, OErr>
where
    F: Fn(SessionState, U, T, ReqState) -> Fut,
    'b: 'a,
    Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>,
    Q: Serialize + Debug + 'a + ApiEventMetric,
    T: Debug + Serialize + ApiEventMetric,
    E: ErrorSwitch<OErr> + error_stack::Context,
    OErr: ResponseError + error_stack::Context + Serialize,
    errors::ApiErrorResponse: ErrorSwitch<OErr>,
{
    let request_id = RequestId::extract(request)
        .await
        .attach_printable("Unable to extract request id from request")
        .change_context(errors::ApiErrorResponse::InternalServerError.switch())?;

    let mut app_state = state.get_ref().clone();

    let start_instant = Instant::now();
    let serialized_request = masking::masked_serialize(&payload)
        .attach_printable("Failed to serialize json request")
        .change_context(errors::ApiErrorResponse::InternalServerError.switch())?;

    let mut event_type = payload.get_api_event_type();
    let tenant_id = if !state.conf.multitenancy.enabled {
        common_utils::id_type::TenantId::try_from_string(DEFAULT_TENANT.to_owned())
            .attach_printable("Unable to get default tenant id")
            .change_context(errors::ApiErrorResponse::InternalServerError.switch())?
    } else {
        let request_tenant_id = incoming_request_header
            .get(TENANT_HEADER)
            .and_then(|value| value.to_str().ok())
            .ok_or_else(|| errors::ApiErrorResponse::MissingTenantId.switch())
            .and_then(|header_value| {
                common_utils::id_type::TenantId::try_from_string(header_value.to_string()).map_err(
                    |_| {
                        errors::ApiErrorResponse::InvalidRequestData {
                            message: format!("`{}` header is invalid", headers::X_TENANT_ID),
                        }
                        .switch()
                    },
                )
            })?;

        state
            .conf
            .multitenancy
            .get_tenant(&request_tenant_id)
            .map(|tenant| tenant.tenant_id.clone())
            .ok_or(
                errors::ApiErrorResponse::InvalidTenant {
                    tenant_id: request_tenant_id.get_string_repr().to_string(),
                }
                .switch(),
            )?
    };
    let locale = utils::get_locale_from_header(&incoming_request_header.clone());
    let mut session_state =
        Arc::new(app_state.clone()).get_session_state(&tenant_id, Some(locale), || {
            errors::ApiErrorResponse::InvalidTenant {
                tenant_id: tenant_id.get_string_repr().to_string(),
            }
            .switch()
        })?;
    session_state.add_request_id(request_id);
    let mut request_state = session_state.get_req_state();

    request_state.event_context.record_info(request_id);
    request_state
        .event_context
        .record_info(("flow".to_string(), flow.to_string()));

    request_state.event_context.record_info((
        "tenant_id".to_string(),
        tenant_id.get_string_repr().to_string(),
    ));

    // Currently auth failures are not recorded as API events
    let (auth_out, auth_type) = api_auth
        .authenticate_and_fetch(request.headers(), &session_state)
        .await
        .switch()?;

    request_state.event_context.record_info(auth_type.clone());

    let merchant_id = auth_type
        .get_merchant_id()
        .cloned()
        .unwrap_or(common_utils::id_type::MerchantId::get_merchant_id_not_found());

    app_state.add_flow_name(flow.to_string());

    tracing::Span::current().record("merchant_id", merchant_id.get_string_repr().to_owned());

    let output = {
        lock_action
            .clone()
            .perform_locking_action(&session_state, merchant_id.to_owned())
            .await
            .switch()?;
        let res = func(session_state.clone(), auth_out, payload, request_state)
            .await
            .switch();
        lock_action
            .free_lock_action(&session_state, merchant_id.to_owned())
            .await
            .switch()?;
        res
    };
    let request_duration = Instant::now()
        .saturating_duration_since(start_instant)
        .as_millis();

    let mut serialized_response = None;
    let mut error = None;
    let mut overhead_latency = None;

    let status_code = match output.as_ref() {
        Ok(res) => {
            if let ApplicationResponse::Json(data) = res {
                serialized_response.replace(
                    masking::masked_serialize(&data)
                        .attach_printable("Failed to serialize json response")
                        .change_context(errors::ApiErrorResponse::InternalServerError.switch())?,
                );
            } else if let ApplicationResponse::JsonWithHeaders((data, headers)) = res {
                serialized_response.replace(
                    masking::masked_serialize(&data)
                        .attach_printable("Failed to serialize json response")
                        .change_context(errors::ApiErrorResponse::InternalServerError.switch())?,
                );

                if let Some((_, value)) = headers.iter().find(|(key, _)| key == X_HS_LATENCY) {
                    if let Ok(external_latency) = value.clone().into_inner().parse::<u128>() {
                        overhead_latency.replace(external_latency);
                    }
                }
            }
            event_type = res.get_api_event_type().or(event_type);

            metrics::request::track_response_status_code(res)
        }
        Err(err) => {
            error.replace(
                serde_json::to_value(err.current_context())
                    .attach_printable("Failed to serialize json response")
                    .change_context(errors::ApiErrorResponse::InternalServerError.switch())
                    .ok()
                    .into(),
            );
            err.current_context().status_code().as_u16().into()
        }
    };

    let infra = extract_mapped_fields(
        &serialized_request,
        state.enhancement.as_ref(),
        state.infra_components.as_ref(),
    );

    let api_event = ApiEvent::new(
        tenant_id,
        Some(merchant_id.clone()),
        flow,
        &request_id,
        request_duration,
        status_code,
        serialized_request,
        serialized_response,
        overhead_latency,
        auth_type,
        error,
        event_type.unwrap_or(ApiEventsType::Miscellaneous),
        request,
        request.method(),
        infra.clone(),
    );

    state.event_handler().log_event(&api_event);

    output
}

#[instrument(
    skip(request, state, func, api_auth, payload),
    fields(request_method, request_url_path, status_code)
)]
pub async fn server_wrap<'a, T, U, Q, F, Fut, E>(
    flow: impl router_env::types::FlowMetric,
    state: web::Data<AppState>,
    request: &'a HttpRequest,
    payload: T,
    func: F,
    api_auth: &dyn AuthenticateAndFetch<U, SessionState>,
    lock_action: api_locking::LockAction,
) -> HttpResponse
where
    F: Fn(SessionState, U, T, ReqState) -> Fut,
    Fut: Future<Output = CustomResult<ApplicationResponse<Q>, E>>,
    Q: Serialize + Debug + ApiEventMetric + 'a,
    T: Debug + Serialize + ApiEventMetric,
    ApplicationResponse<Q>: Debug,
    E: ErrorSwitch<api_models::errors::types::ApiErrorResponse> + error_stack::Context,
{
    let request_method = request.method().as_str();
    let url_path = request.path();

    let unmasked_incoming_header_keys = state.conf().unmasked_headers.keys;

    let incoming_request_header = request.headers();

    let incoming_header_to_log: HashMap<String, HeaderValue> =
        incoming_request_header
            .iter()
            .fold(HashMap::new(), |mut acc, (key, value)| {
                let key = key.to_string();
                if unmasked_incoming_header_keys.contains(&key.as_str().to_lowercase()) {
                    acc.insert(key.clone(), value.clone());
                } else {
                    acc.insert(key.clone(), HeaderValue::from_static("**MASKED**"));
                }
                acc
            });

    tracing::Span::current().record("request_method", request_method);
    tracing::Span::current().record("request_url_path", url_path);

    let start_instant = Instant::now();

    logger::info!(
        tag = ?Tag::BeginRequest, payload = ?payload,
    headers = ?incoming_header_to_log);

    let server_wrap_util_res = server_wrap_util(
        &flow,
        state.clone(),
        incoming_request_header,
        request,
        payload,
        func,
        api_auth,
        lock_action,
    )
    .await
    .map(|response| {
        logger::info!(api_response =? response);
        response
    });

    let res = match server_wrap_util_res {
        Ok(ApplicationResponse::Json(response)) => match serde_json::to_string(&response) {
            Ok(res) => http_response_json(res),
            Err(_) => http_response_err(
                r#"{
                    "error": {
                        "message": "Error serializing response from connector"
                    }
                }"#,
            ),
        },
        Ok(ApplicationResponse::StatusOk) => http_response_ok(),
        Ok(ApplicationResponse::TextPlain(text)) => http_response_plaintext(text),
        Ok(ApplicationResponse::FileData((file_data, content_type))) => {
            http_response_file_data(file_data, content_type)
        }
        Ok(ApplicationResponse::JsonForRedirection(response)) => {
            match serde_json::to_string(&response) {
                Ok(res) => http_redirect_response(res, response),
                Err(_) => http_response_err(
                    r#"{
                    "error": {
                        "message": "Error serializing response from connector"
                    }
                }"#,
                ),
            }
        }
        Ok(ApplicationResponse::Form(redirection_data)) => {
            let config = state.conf();
            build_redirection_form(
                &redirection_data.redirect_form,
                redirection_data.payment_method_data,
                redirection_data.amount,
                redirection_data.currency,
                config,
            )
            .respond_to(request)
            .map_into_boxed_body()
        }

        Ok(ApplicationResponse::GenericLinkForm(boxed_generic_link_data)) => {
            let link_type = boxed_generic_link_data.data.to_string();
            match build_generic_link_html(
                boxed_generic_link_data.data,
                boxed_generic_link_data.locale,
            ) {
                Ok(rendered_html) => {
                    let headers = if !boxed_generic_link_data.allowed_domains.is_empty() {
                        let domains_str = boxed_generic_link_data
                            .allowed_domains
                            .into_iter()
                            .collect::<Vec<String>>()
                            .join(" ");
                        let csp_header = format!("frame-ancestors 'self' {domains_str};");
                        Some(HashSet::from([("content-security-policy", csp_header)]))
                    } else {
                        None
                    };
                    http_response_html_data(rendered_html, headers)
                }
                Err(_) => http_response_err(format!("Error while rendering {link_type} HTML page")),
            }
        }

        Ok(ApplicationResponse::PaymentLinkForm(boxed_payment_link_data)) => {
            match *boxed_payment_link_data {
                PaymentLinkAction::PaymentLinkFormData(payment_link_data) => {
                    match build_payment_link_html(payment_link_data) {
                        Ok(rendered_html) => http_response_html_data(rendered_html, None),
                        Err(_) => http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link html page"
                                }
                            }"#,
                        ),
                    }
                }
                PaymentLinkAction::PaymentLinkStatus(payment_link_data) => {
                    match get_payment_link_status(payment_link_data) {
                        Ok(rendered_html) => http_response_html_data(rendered_html, None),
                        Err(_) => http_response_err(
                            r#"{
                                "error": {
                                    "message": "Error while rendering payment link status page"
                                }
                            }"#,
                        ),
                    }
                }
            }
        }

        Ok(ApplicationResponse::JsonWithHeaders((response, headers))) => {
            let request_elapsed_time = request.headers().get(X_HS_LATENCY).and_then(|value| {
                if value == "true" {
                    Some(start_instant.elapsed())
                } else {
                    None
                }
            });
            let proxy_connector_http_status_code = if state
                .conf
                .proxy_status_mapping
                .proxy_connector_http_status_code
            {
                headers
                    .iter()
                    .find(|(key, _)| key == headers::X_CONNECTOR_HTTP_STATUS_CODE)
                    .and_then(|(_, value)| {
                        match value.clone().into_inner().parse::<u16>() {
                            Ok(code) => match http::StatusCode::from_u16(code) {
                                Ok(status_code) => Some(status_code),
                                Err(err) => {
                                    logger::error!(
                                        "Invalid HTTP status code parsed from connector_http_status_code: {:?}",
                                        err
                                    );
                                    None
                                }
                            },
                            Err(err) => {
                                logger::error!(
                                    "Failed to parse connector_http_status_code from header: {:?}",
                                    err
                                );
                                None
                            }
                        }
                    })
            } else {
                None
            };
            match serde_json::to_string(&response) {
                Ok(res) => http_response_json_with_headers(
                    res,
                    headers,
                    request_elapsed_time,
                    proxy_connector_http_status_code,
                ),
                Err(_) => http_response_err(
                    r#"{
                        "error": {
                            "message": "Error serializing response from connector"
                        }
                    }"#,
                ),
            }
        }
        Err(error) => log_and_return_error_response(error),
    };

    let response_code = res.status().as_u16();
    tracing::Span::current().record("status_code", response_code);

    let end_instant = Instant::now();
    let request_duration = end_instant.saturating_duration_since(start_instant);
    logger::info!(
        tag = ?Tag::EndRequest,
        time_taken_ms = request_duration.as_millis(),
    );
    res
}

pub fn log_and_return_error_response<T>(error: Report<T>) -> HttpResponse
where
    T: error_stack::Context + Clone + ResponseError,
    Report<T>: EmbedError,
{
    logger::error!(?error);
    HttpResponse::from_error(error.embed().current_context().clone())
}

pub trait EmbedError: Sized {
    fn embed(self) -> Self {
        self
    }
}

impl EmbedError for Report<api_models::errors::types::ApiErrorResponse> {
    fn embed(self) -> Self {
        #[cfg(feature = "detailed_errors")]
        {
            let mut report = self;
            let error_trace = serde_json::to_value(&report).ok().and_then(|inner| {
                serde_json::from_value::<Vec<errors::NestedErrorStack<'_>>>(inner)
                    .ok()
                    .map(Into::<errors::VecLinearErrorStack<'_>>::into)
                    .map(serde_json::to_value)
                    .transpose()
                    .ok()
                    .flatten()
            });


OpenAPI Block Path: components.schemas.CardNetworkTypes
{
  "type": "object",
  "required": [
    "eligible_connectors"
  ],
  "properties": {
    "card_network": {
      "allOf": [
        {
          "$ref": "#/components/schemas/CardNetwork"
        }
      ],
      "nullable": true
    },
    "surcharge_details": {
      "allOf": [
        {
          "$ref": "#/components/schemas/SurchargeDetailsResponse"
        }
      ],
      "nullable": true
    },
    "eligible_connectors": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The list of eligible connectors for a given card network",
      "example": [
        "stripe",
        "adyen"
      ]
    }
  }
}


pub async fn get_user_details(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
    let flow = Flow::GetUserDetails;
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        (),
        |state, user, _, _| user_core::get_user_details(state, user),
        &auth::DashboardNoPermissionAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct Mandates {
    pub supported_payment_methods: SupportedPaymentMethodsForMandate,
    pub update_mandate_supported: SupportedPaymentMethodsForMandate,
}


pub fn get_organization_id_from_header(&self) -> RouterResult<id_type::OrganizationId> {
        self.get_mandatory_header_value_by_key(headers::X_ORGANIZATION_ID)
            .map(|val| val.to_owned())
            .and_then(|organization_id| {
                id_type::OrganizationId::try_from_string(organization_id).change_context(
                    errors::ApiErrorResponse::InvalidRequestData {
                        message: format!("`{}` header is invalid", headers::X_ORGANIZATION_ID),
                    },
                )
            })
    }


pub async fn get_file_purpose(field: &mut Field) -> Option<api::FilePurpose> {
    let purpose = read_string(field).await;
    match purpose.as_deref() {
        Some("dispute_evidence") => Some(api::FilePurpose::DisputeEvidence),
        _ => None,
    }
}


Web Documentation: SDK Reference | Hyperswitch
# Type: Web Doc

Hyperswitch provides frontend integration solutions through SDKs tailored for various frameworks, including React and JavaScript. These SDKs come with pre-built support for the frameworks used in your application, eliminating the need for additional code to ensure compatibility. The following documentation offers detailed information on the APIs of these SDKs, outlining their methods and purposes:
React SDK Reference
: Explore the React SDK methods and their parameters to customize your payment experience.
JavaScript SDK Reference
: Discover the JavaScript SDK methods available for tailoring your payment integration.
React + HTML
JS
These resources provide comprehensive guidance to help you effectively integrate Hyperswitch's payment solutions into your applications.
7 months ago
Was this helpful?

SDK Reference | Hyperswitch


pub fn should_call_connector_create_customer<'a>(
    state: &SessionState,
    connector: &api::ConnectorData,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
) -> (bool, Option<&'a str>) {
    // Check if create customer is required for the connector
    match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(_) => {
            let connector_needs_customer = state
                .conf
                .connector_customer
                .connector_list
                .contains(&connector.connector_name);

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

        // TODO: Construct connector_customer for MerchantConnectorDetails if required by connector.
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            todo!("Handle connector_customer construction for MerchantConnectorDetails");
        }
    }
}


pub async fn create_routing_algorithm_under_profile(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    authentication_profile_id: Option<common_utils::id_type::ProfileId>,
    request: routing_types::RoutingConfigRequest,
    transaction_type: enums::TransactionType,
) -> RouterResponse<routing_types::RoutingDictionaryRecord> {
    use api_models::routing::StaticRoutingAlgorithm as EuclidAlgorithm;

    metrics::ROUTING_CREATE_REQUEST_RECEIVED.add(1, &[]);
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let name = request
        .name
        .get_required_value("name")
        .change_context(errors::ApiErrorResponse::MissingRequiredField { field_name: "name" })
        .attach_printable("Name of config not given")?;

    let description = request
        .description
        .get_required_value("description")
        .change_context(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "description",
        })
        .attach_printable("Description of config not given")?;

    let algorithm = request
        .algorithm
        .clone()
        .get_required_value("algorithm")
        .change_context(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "algorithm",
        })
        .attach_printable("Algorithm of config not given")?;

    let algorithm_id = common_utils::generate_routing_id_of_default_length();

    let profile_id = request
        .profile_id
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "profile_id",
        })
        .attach_printable("Profile_id not provided")?;

    let business_profile = core_utils::validate_and_get_business_profile(
        db,
        key_manager_state,
        merchant_context.get_merchant_key_store(),
        Some(&profile_id),
        merchant_context.get_merchant_account().get_id(),
    )
    .await?
    .get_required_value("Profile")?;

    core_utils::validate_profile_id_from_auth_layer(authentication_profile_id, &business_profile)?;

    if algorithm.should_validate_connectors_in_routing_config() {
        helpers::validate_connectors_in_routing_config(
            &state,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().get_id(),
            &profile_id,
            &algorithm,
        )
        .await?;
    }

    let mut decision_engine_routing_id: Option<String> = None;

    if let Some(euclid_algorithm) = request.algorithm.clone() {
        let maybe_static_algorithm: Option<StaticRoutingAlgorithm> = match euclid_algorithm {
            EuclidAlgorithm::Advanced(program) => match program.try_into() {
                Ok(internal_program) => Some(StaticRoutingAlgorithm::Advanced(internal_program)),
                Err(e) => {
                    router_env::logger::error!(decision_engine_error = ?e, "decision_engine_euclid");
                    None
                }
            },
            EuclidAlgorithm::Single(conn) => {
                Some(StaticRoutingAlgorithm::Single(Box::new(conn.into())))
            }
            EuclidAlgorithm::Priority(connectors) => {
                let converted: Vec<ConnectorInfo> =
                    connectors.into_iter().map(Into::into).collect();
                Some(StaticRoutingAlgorithm::Priority(converted))
            }
            EuclidAlgorithm::VolumeSplit(splits) => {
                let converted: Vec<VolumeSplit<ConnectorInfo>> =
                    splits.into_iter().map(Into::into).collect();
                Some(StaticRoutingAlgorithm::VolumeSplit(converted))
            }
            EuclidAlgorithm::ThreeDsDecisionRule(_) => {
                router_env::logger::error!(
                    "decision_engine_euclid: ThreeDsDecisionRules are not yet implemented"
                );
                None
            }
        };

        if let Some(static_algorithm) = maybe_static_algorithm {
            let routing_rule = RoutingRule {
                rule_id: Some(algorithm_id.clone().get_string_repr().to_owned()),
                name: name.clone(),
                description: Some(description.clone()),
                created_by: profile_id.get_string_repr().to_string(),
                algorithm: static_algorithm,
                algorithm_for: transaction_type.into(),
                metadata: Some(RoutingMetadata {
                    kind: algorithm.get_kind().foreign_into(),
                }),
            };

            match create_de_euclid_routing_algo(&state, &routing_rule).await {
                Ok(id) => {
                    decision_engine_routing_id = Some(id);
                }
                Err(e)
                    if matches!(
                        e.current_context(),
                        errors::RoutingError::DecisionEngineValidationError(_)
                    ) =>
                {
                    if let errors::RoutingError::DecisionEngineValidationError(msg) =
                        e.current_context()
                    {
                        router_env::logger::error!(
                            decision_engine_euclid_error = ?msg,
                            decision_engine_euclid_request = ?routing_rule,
                            "failed to create rule in decision_engine with validation error"
                        );
                    }
                }
                Err(e) => {
                    router_env::logger::error!(
                        decision_engine_euclid_error = ?e,
                        decision_engine_euclid_request = ?routing_rule,
                        "failed to create rule in decision_engine"
                    );
                }
            }
        }
    }

    if decision_engine_routing_id.is_some() {
        router_env::logger::info!(routing_flow=?"create_euclid_routing_algorithm", is_equal=?"true", "decision_engine_euclid");
    } else {
        router_env::logger::info!(routing_flow=?"create_euclid_routing_algorithm", is_equal=?"false", "decision_engine_euclid");
    }

    let timestamp = common_utils::date_time::now();
    let algo = RoutingAlgorithm {
        algorithm_id: algorithm_id.clone(),
        profile_id,
        merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
        name: name.clone(),
        description: Some(description.clone()),
        kind: algorithm.get_kind().foreign_into(),
        algorithm_data: serde_json::json!(algorithm),
        created_at: timestamp,
        modified_at: timestamp,
        algorithm_for: transaction_type.to_owned(),
        decision_engine_routing_id,
    };
    let record = db
        .insert_routing_algorithm(algo)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?;

    let new_record = record.foreign_into();

    metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add(1, &[]);
    Ok(service_api::ApplicationResponse::Json(new_record))
}


impl KafkaMessage for ApiEvent {
    fn event_type(&self) -> EventType {
        EventType::ApiLogs
    }

    fn key(&self) -> String {
        self.request_id.clone()
    }
}


    /// A boolean value to indicate if redirect to merchant with http post needs to be enabled
    #[schema(default = false, example = true)]
    pub redirect_to_merchant_with_http_post: bool,

    /// Webhook related details
    pub webhook_details: Option<WebhookDetails>,

    /// Metadata is useful for storing additional, unstructured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// The routing algorithm to be used for routing payments to desired connectors
    #[schema(value_type = Option<Object>,example = json!({"type": "single", "data": "stripe"}))]
    pub routing_algorithm: Option<serde_json::Value>,

    /// Will be used to determine the time till which your payment will be active once the payment session starts
    #[schema(example = 900)]
    pub intent_fulfillment_time: Option<i64>,

    /// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
    #[schema(value_type = Option<Object>,example = json!({"type": "single", "data": "signifyd"}))]
    pub frm_routing_algorithm: Option<serde_json::Value>,

    /// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
    #[cfg(feature = "payouts")]
    #[schema(value_type = Option<StaticRoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
    pub payout_routing_algorithm: Option<serde_json::Value>,

    /// Verified Apple Pay domains for a particular profile
    pub applepay_verified_domains: Option<Vec<String>>,

    /// Client Secret Default expiry for all payments created under this profile
    #[schema(example = 900)]
    pub session_expiry: Option<i64>,

    /// Default Payment Link config for all payment links created under this profile
    #[schema(value_type = Option<BusinessPaymentLinkConfig>)]
    pub payment_link_config: Option<BusinessPaymentLinkConfig>,

    /// External 3DS authentication details
    pub authentication_connector_details: Option<AuthenticationConnectorDetails>,

    // Whether to use the billing details passed when creating the intent as payment method billing
    pub use_billing_as_payment_method_billing: Option<bool>,

    /// Merchant's config to support extended card info feature
    pub extended_card_info_config: Option<ExtendedCardInfoConfig>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_billing_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_billing_details_from_wallet_connector: Option<bool>,

    /// Indicates if the MIT (merchant initiated transaction) payments can be made connector
    /// agnostic, i.e., MITs may be processed through different connector than CIT (customer
    /// initiated transaction) based on the routing rules.
    /// If set to `false`, MIT will go through the same connector as the CIT.
    pub is_connector_agnostic_mit_enabled: Option<bool>,

    /// Default payout link config
    #[schema(value_type = Option<BusinessPayoutLinkConfig>)]
    pub payout_link_config: Option<BusinessPayoutLinkConfig>,

    /// These key-value pairs are sent as additional custom headers in the outgoing webhook request.
    #[schema(value_type = Option<Object>, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)]
    pub outgoing_webhook_custom_http_headers: Option<MaskedHeaders>,

    /// Merchant Connector id to be stored for tax_calculator connector
    #[schema(value_type = Option<String>)]
    pub tax_connector_id: Option<id_type::MerchantConnectorAccountId>,

    /// Indicates if tax_calculator connector is enabled or not.
    /// If set to `true` tax_connector_id will be checked.
    pub is_tax_connector_enabled: bool,

    /// Indicates if network tokenization is enabled or not.
    #[schema(default = false, example = false)]
    pub is_network_tokenization_enabled: bool,

    /// Indicates if is_auto_retries_enabled is enabled or not.
    #[schema(default = false, example = false)]
    pub is_auto_retries_enabled: bool,

    /// Maximum number of auto retries allowed for a payment
    pub max_auto_retries_enabled: Option<i16>,

    /// Bool indicating if extended authentication must be requested for all payments
    #[schema(value_type = Option<bool>)]
    pub always_request_extended_authorization:
        Option<primitive_wrappers::AlwaysRequestExtendedAuthorization>,

    /// Indicates if click to pay is enabled or not.
    #[schema(default = false, example = false)]
    pub is_click_to_pay_enabled: bool,

    /// Product authentication ids
    #[schema(value_type = Option<Object>, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)]
    pub authentication_product_ids:
        Option<common_types::payments::AuthenticationConnectorAccountMap>,

    /// Card Testing Guard Configs
    pub card_testing_guard_config: Option<CardTestingGuardConfig>,

    ///Indicates if clear pan retries is enabled or not.
    pub is_clear_pan_retries_enabled: bool,

    /// Indicates if 3ds challenge is forced
    pub force_3ds_challenge: bool,

    /// Indicates if debit routing is enabled or not
    #[schema(value_type = Option<bool>)]
    pub is_debit_routing_enabled: Option<bool>,

    //Merchant country for the profile
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub merchant_business_country: Option<api_enums::CountryAlpha2>,

    /// Indicates if pre network tokenization is enabled or not
    #[schema(default = false, example = false)]
    pub is_pre_network_tokenization_enabled: bool,

    /// Acquirer configs
    #[schema(value_type = Option<Vec<ProfileAcquirerResponse>>)]
    pub acquirer_configs: Option<Vec<ProfileAcquirerResponse>>,

    /// Indicates if the redirection has to open in the iframe
    #[schema(example = false)]
    pub is_iframe_redirection_enabled: Option<bool>,

    /// Four-digit code assigned based on business type to determine processing fees and risk level
    #[schema(value_type = Option<MerchantCategoryCode>, example = "5411")]
    pub merchant_category_code: Option<api_enums::MerchantCategoryCode>,

    /// Merchant country code.
    /// This is a 3-digit ISO 3166-1 numeric country code that represents the country in which the merchant is registered or operates.
    /// Merchants typically receive this value based on their business registration information or during onboarding via payment processors or acquiring banks.
    /// It is used in payment processing, fraud detection, and regulatory compliance to determine regional rules and routing behavior.
    #[schema(value_type = Option<MerchantCountryCode>, example = "840")]
    pub merchant_country_code: Option<common_types::payments::MerchantCountryCode>,

    #[schema(value_type = Option<u32>, example = 2)]
    pub dispute_polling_interval: Option<primitive_wrappers::DisputePollingIntervalInHours>,

    /// Indicates if manual retry for payment is enabled or not
    pub is_manual_retry_enabled: Option<bool>,
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, ToSchema, Serialize)]
pub struct ProfileResponse {
    /// The identifier for Merchant Account
    #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)]
    pub merchant_id: id_type::MerchantId,

    /// The identifier for profile. This must be used for creating merchant accounts, payments and payouts
    #[schema(max_length = 64, value_type = String, example = "pro_abcdefghijklmnopqrstuvwxyz")]
    pub id: id_type::ProfileId,

    /// Name of the profile
    #[schema(max_length = 64)]
    pub profile_name: String,

    /// The URL to redirect after the completion of the operation
    #[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
    pub return_url: Option<common_utils::types::Url>,

    /// A boolean value to indicate if payment response hash needs to be enabled
    #[schema(default = true, example = true)]
    pub enable_payment_response_hash: bool,

    /// Refers to the hash key used for calculating the signature for webhooks and redirect response. If the value is not provided, a value is automatically generated.
    pub payment_response_hash_key: Option<String>,

    /// A boolean value to indicate if redirect to merchant with http post needs to be enabled
    #[schema(default = false, example = true)]
    pub redirect_to_merchant_with_http_post: bool,

    /// Webhook related details
    pub webhook_details: Option<WebhookDetails>,

    /// Metadata is useful for storing additional, unstructured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// Verified Apple Pay domains for a particular profile
    pub applepay_verified_domains: Option<Vec<String>>,

    /// Client Secret Default expiry for all payments created under this profile
    #[schema(example = 900)]
    pub session_expiry: Option<i64>,

    /// Default Payment Link config for all payment links created under this profile
    #[schema(value_type = Option<BusinessPaymentLinkConfig>)]
    pub payment_link_config: Option<BusinessPaymentLinkConfig>,

    /// External 3DS authentication details
    pub authentication_connector_details: Option<AuthenticationConnectorDetails>,

    // Whether to use the billing details passed when creating the intent as payment method billing
    pub use_billing_as_payment_method_billing: Option<bool>,

    /// Merchant's config to support extended card info feature
    pub extended_card_info_config: Option<ExtendedCardInfoConfig>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_shipping_details_from_wallet_connector_if_required: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_billing_details_from_wallet_connector_if_required: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_billing_details_from_wallet_connector: Option<bool>,

    /// Indicates if the MIT (merchant initiated transaction) payments can be made connector
    /// agnostic, i.e., MITs may be processed through different connector than CIT (customer
    /// initiated transaction) based on the routing rules.
    /// If set to `false`, MIT will go through the same connector as the CIT.
    pub is_connector_agnostic_mit_enabled: Option<bool>,

    /// Default payout link config
    #[schema(value_type = Option<BusinessPayoutLinkConfig>)]
    pub payout_link_config: Option<BusinessPayoutLinkConfig>,

    /// These key-value pairs are sent as additional custom headers in the outgoing webhook request.
    #[schema(value_type = Option<Object>, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)]
    pub outgoing_webhook_custom_http_headers: Option<MaskedHeaders>,

    /// Will be used to determine the time till which your payment will be active once the payment session starts
    #[schema(value_type = Option<u32>, example = 900)]
    pub order_fulfillment_time: Option<OrderFulfillmentTime>,

    /// Whether the order fulfillment time is calculated from the origin or the time of creating the payment, or confirming the payment
    #[schema(value_type = Option<OrderFulfillmentTimeOrigin>, example = "create")]
    pub order_fulfillment_time_origin: Option<api_enums::OrderFulfillmentTimeOrigin>,

    /// Merchant Connector id to be stored for tax_calculator connector
    #[schema(value_type = Option<String>)]
    pub tax_connector_id: Option<id_type::MerchantConnectorAccountId>,

    /// Indicates if tax_calculator connector is enabled or not.
    /// If set to `true` tax_connector_id will be checked.
    pub is_tax_connector_enabled: bool,

    /// Indicates if network tokenization is enabled or not.
    #[schema(default = false, example = false)]
    pub is_network_tokenization_enabled: bool,

    /// Indicates if CVV should be collected during payment or not.
    #[schema(value_type = Option<bool>)]
    pub should_collect_cvv_during_payment:
        Option<primitive_wrappers::ShouldCollectCvvDuringPayment>,

    /// Indicates if click to pay is enabled or not.
    #[schema(default = false, example = false)]
    pub is_click_to_pay_enabled: bool,

    /// Product authentication ids
    #[schema(value_type = Option<Object>, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)]
    pub authentication_product_ids:
        Option<common_types::payments::AuthenticationConnectorAccountMap>,

    /// Card Testing Guard Configs
    pub card_testing_guard_config: Option<CardTestingGuardConfig>,

    ///Indicates if clear pan retries is enabled or not.
    pub is_clear_pan_retries_enabled: bool,

    /// Indicates if debit routing is enabled or not
    #[schema(value_type = Option<bool>)]
    pub is_debit_routing_enabled: Option<bool>,

    //Merchant country for the profile
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub merchant_business_country: Option<api_enums::CountryAlpha2>,

    /// Indicates if the redirection has to open in the iframe
    #[schema(example = false)]
    pub is_iframe_redirection_enabled: Option<bool>,

    /// Indicates if external vault is enabled or not.
    pub is_external_vault_enabled: Option<bool>,

    /// External Vault Connector Details
    pub external_vault_connector_details: Option<ExternalVaultConnectorDetails>,

    /// Four-digit code assigned based on business type to determine processing fees and risk level
    #[schema(value_type = Option<MerchantCategoryCode>, example = "5411")]
    pub merchant_category_code: Option<api_enums::MerchantCategoryCode>,

    /// Merchant country code.
    /// This is a 3-digit ISO 3166-1 numeric country code that represents the country in which the merchant is registered or operates.
    /// Merchants typically receive this value based on their business registration information or during onboarding via payment processors or acquiring banks.
    /// It is used in payment processing, fraud detection, and regulatory compliance to determine regional rules and routing behavior.
    #[schema(value_type = Option<MerchantCountryCode>, example = "840")]
    pub merchant_country_code: Option<common_types::payments::MerchantCountryCode>,

    /// Enable split payments, i.e., split the amount between multiple payment methods
    #[schema(value_type = SplitTxnsEnabled, default = "skip")]
    pub split_txns_enabled: common_enums::SplitTxnsEnabled,
}

#[cfg(feature = "v1")]
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProfileUpdate {
    /// The name of profile
    #[schema(max_length = 64)]
    pub profile_name: Option<String>,

    /// The URL to redirect after the completion of the operation
    #[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
    pub return_url: Option<url::Url>,

    /// A boolean value to indicate if payment response hash needs to be enabled
    #[schema(default = true, example = true)]
    pub enable_payment_response_hash: Option<bool>,

    /// Refers to the hash key used for calculating the signature for webhooks and redirect response.
    pub payment_response_hash_key: Option<String>,

    /// A boolean value to indicate if redirect to merchant with http post needs to be enabled
    #[schema(default = false, example = true)]
    pub redirect_to_merchant_with_http_post: Option<bool>,

    /// Webhook related details
    pub webhook_details: Option<WebhookDetails>,

    /// Metadata is useful for storing additional, unstructured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// The routing algorithm to be used for routing payments to desired connectors
    #[schema(value_type = Option<Object>,example = json!({"type": "single", "data": "stripe"}))]
    pub routing_algorithm: Option<serde_json::Value>,

    /// Will be used to determine the time till which your payment will be active once the payment session starts
    #[schema(example = 900)]
    pub intent_fulfillment_time: Option<u32>,

    /// The frm routing algorithm to be used for routing payments to desired FRM's
    #[schema(value_type = Option<Object>,example = json!({"type": "single", "data": "signifyd"}))]
    pub frm_routing_algorithm: Option<serde_json::Value>,

    /// The routing algorithm to be used to process the incoming request from merchant to outgoing payment processor or payment method. The default is 'Custom'
    #[cfg(feature = "payouts")]
    #[schema(value_type = Option<StaticRoutingAlgorithm>,example = json!({"type": "single", "data": "wise"}))]
    pub payout_routing_algorithm: Option<serde_json::Value>,

    /// Verified Apple Pay domains for a particular profile
    pub applepay_verified_domains: Option<Vec<String>>,

    /// Client Secret Default expiry for all payments created under this profile
    #[schema(example = 900)]
    pub session_expiry: Option<u32>,

    /// Default Payment Link config for all payment links created under this profile
    pub payment_link_config: Option<BusinessPaymentLinkConfig>,

    /// External 3DS authentication details
    pub authentication_connector_details: Option<AuthenticationConnectorDetails>,

    /// Merchant's config to support extended card info feature
    pub extended_card_info_config: Option<ExtendedCardInfoConfig>,

    // Whether to use the billing details passed when creating the intent as payment method billing
    pub use_billing_as_payment_method_billing: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_billing_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_billing_details_from_wallet_connector: Option<bool>,

    /// Bool indicating if extended authentication must be requested for all payments
    #[schema(value_type = Option<bool>)]
    pub always_request_extended_authorization:
        Option<primitive_wrappers::AlwaysRequestExtendedAuthorization>,

    /// Indicates if the MIT (merchant initiated transaction) payments can be made connector
    /// agnostic, i.e., MITs may be processed through different connector than CIT (customer
    /// initiated transaction) based on the routing rules.
    /// If set to `false`, MIT will go through the same connector as the CIT.
    pub is_connector_agnostic_mit_enabled: Option<bool>,

    /// Default payout link config
    #[schema(value_type = Option<BusinessPayoutLinkConfig>)]
    pub payout_link_config: Option<BusinessPayoutLinkConfig>,

    /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. It is recommended not to use more than four key-value pairs.
    #[schema(value_type = Option<Object>, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)]
    pub outgoing_webhook_custom_http_headers: Option<HashMap<String, String>>,

    /// Merchant Connector id to be stored for tax_calculator connector
    #[schema(value_type = Option<String>)]
    pub tax_connector_id: Option<id_type::MerchantConnectorAccountId>,

    /// Indicates if tax_calculator connector is enabled or not.
    /// If set to `true` tax_connector_id will be checked.
    pub is_tax_connector_enabled: Option<bool>,

    /// Indicates if dynamic routing is enabled or not.
    #[serde(default)]
    pub dynamic_routing_algorithm: Option<serde_json::Value>,

    /// Indicates if network tokenization is enabled or not.
    pub is_network_tokenization_enabled: Option<bool>,

    /// Indicates if is_auto_retries_enabled is enabled or not.
    pub is_auto_retries_enabled: Option<bool>,

    /// Maximum number of auto retries allowed for a payment
    pub max_auto_retries_enabled: Option<u8>,

    /// Indicates if click to pay is enabled or not.
    #[schema(default = false, example = false)]
    pub is_click_to_pay_enabled: Option<bool>,

    /// Product authentication ids
    #[schema(value_type = Option<Object>, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)]
    pub authentication_product_ids:
        Option<common_types::payments::AuthenticationConnectorAccountMap>,

    /// Card Testing Guard Configs
    pub card_testing_guard_config: Option<CardTestingGuardConfig>,

    ///Indicates if clear pan retries is enabled or not.
    pub is_clear_pan_retries_enabled: Option<bool>,

    /// Indicates if 3ds challenge is forced
    pub force_3ds_challenge: Option<bool>,

    /// Indicates if debit routing is enabled or not
    #[schema(value_type = Option<bool>)]
    pub is_debit_routing_enabled: Option<bool>,

    //Merchant country for the profile
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub merchant_business_country: Option<api_enums::CountryAlpha2>,

    /// Indicates if the redirection has to open in the iframe
    #[schema(example = false)]
    pub is_iframe_redirection_enabled: Option<bool>,

    /// Indicates if pre network tokenization is enabled or not
    #[schema(default = false, example = false)]
    pub is_pre_network_tokenization_enabled: Option<bool>,

    /// Four-digit code assigned based on business type to determine processing fees and risk level
    #[schema(value_type = Option<MerchantCategoryCode>, example = "5411")]
    pub merchant_category_code: Option<api_enums::MerchantCategoryCode>,

    /// Merchant country code.
    /// This is a 3-digit ISO 3166-1 numeric country code that represents the country in which the merchant is registered or operates.
    /// Merchants typically receive this value based on their business registration information or during onboarding via payment processors or acquiring banks.
    /// It is used in payment processing, fraud detection, and regulatory compliance to determine regional rules and routing behavior.
    #[schema(value_type = Option<MerchantCountryCode>, example = "840")]
    pub merchant_country_code: Option<common_types::payments::MerchantCountryCode>,

    #[schema(value_type = Option<u32>, example = 2)]
    pub dispute_polling_interval: Option<primitive_wrappers::DisputePollingIntervalInHours>,

    /// Indicates if manual retry for payment is enabled or not
    pub is_manual_retry_enabled: Option<bool>,
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug, Deserialize, ToSchema, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProfileUpdate {
    /// The name of profile
    #[schema(max_length = 64)]
    pub profile_name: Option<String>,

    /// The URL to redirect after the completion of the operation
    #[schema(value_type = Option<String>, max_length = 255, example = "https://www.example.com/success")]
    pub return_url: Option<common_utils::types::Url>,

    /// A boolean value to indicate if payment response hash needs to be enabled
    #[schema(default = true, example = true)]
    pub enable_payment_response_hash: Option<bool>,

    /// Refers to the hash key used for calculating the signature for webhooks and redirect response. If the value is not provided, a value is automatically generated.
    pub payment_response_hash_key: Option<String>,

    /// A boolean value to indicate if redirect to merchant with http post needs to be enabled
    #[schema(default = false, example = true)]
    pub redirect_to_merchant_with_http_post: Option<bool>,

    /// Webhook related details
    pub webhook_details: Option<WebhookDetails>,

    /// Metadata is useful for storing additional, unstructured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// Will be used to determine the time till which your payment will be active once the payment session starts
    #[schema(value_type = Option<u32>, example = 900)]
    pub order_fulfillment_time: Option<OrderFulfillmentTime>,

    /// Whether the order fulfillment time is calculated from the origin or the time of creating the payment, or confirming the payment
    #[schema(value_type = Option<OrderFulfillmentTimeOrigin>, example = "create")]
    pub order_fulfillment_time_origin: Option<api_enums::OrderFulfillmentTimeOrigin>,

    /// Verified Apple Pay domains for a particular profile
    pub applepay_verified_domains: Option<Vec<String>>,

    /// Client Secret Default expiry for all payments created under this profile
    #[schema(example = 900)]
    pub session_expiry: Option<u32>,

    /// Default Payment Link config for all payment links created under this profile
    pub payment_link_config: Option<BusinessPaymentLinkConfig>,

    /// External 3DS authentication details
    pub authentication_connector_details: Option<AuthenticationConnectorDetails>,

    /// Merchant's config to support extended card info feature
    pub extended_card_info_config: Option<ExtendedCardInfoConfig>,

    // Whether to use the billing details passed when creating the intent as payment method billing
    pub use_billing_as_payment_method_billing: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_shipping_details_from_wallet_connector_if_required: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector only if it is required field for connector (Eg. Apple Pay, Google Pay etc)
    #[schema(default = false, example = false)]
    pub collect_billing_details_from_wallet_connector_if_required: Option<bool>,

    /// A boolean value to indicate if customer shipping details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_shipping_details_from_wallet_connector: Option<bool>,

    /// A boolean value to indicate if customer billing details needs to be collected from wallet
    /// connector irrespective of connector required fields (Eg. Apple pay, Google pay etc)
    #[schema(default = false, example = false)]
    pub always_collect_billing_details_from_wallet_connector: Option<bool>,

    /// Indicates if the MIT (merchant initiated transaction) payments can be made connector
    /// agnostic, i.e., MITs may be processed through different connector than CIT (customer
    /// initiated transaction) based on the routing rules.
    /// If set to `false`, MIT will go through the same connector as the CIT.
    pub is_connector_agnostic_mit_enabled: Option<bool>,

    /// Default payout link config
    #[schema(value_type = Option<BusinessPayoutLinkConfig>)]
    pub payout_link_config: Option<BusinessPayoutLinkConfig>,

    /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. It is recommended not to use more than four key-value pairs.
    #[schema(value_type = Option<Object>, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)]
    pub outgoing_webhook_custom_http_headers: Option<HashMap<String, String>>,

    /// Merchant Connector id to be stored for tax_calculator connector
    #[schema(value_type = Option<String>)]
    pub tax_connector_id: Option<id_type::MerchantConnectorAccountId>,

    /// Indicates if tax_calculator connector is enabled or not.
    /// If set to `true` tax_connector_id will be checked.
    pub is_tax_connector_enabled: Option<bool>,

    /// Indicates if network tokenization is enabled or not.
    pub is_network_tokenization_enabled: Option<bool>,

    /// Indicates if click to pay is enabled or not.
    #[schema(default = false, example = false)]
    pub is_click_to_pay_enabled: Option<bool>,

    /// Product authentication ids
    #[schema(value_type = Option<Object>, example = r#"{ "click_to_pay": "mca_ushduqwhdohwd", "netcetera": "mca_kwqhudqwd" }"#)]
    pub authentication_product_ids:
        Option<common_types::payments::AuthenticationConnectorAccountMap>,

    /// Card Testing Guard Configs
    pub card_testing_guard_config: Option<CardTestingGuardConfig>,

    ///Indicates if clear pan retries is enabled or not.
    pub is_clear_pan_retries_enabled: Option<bool>,

    /// Indicates if debit routing is enabled or not
    #[schema(value_type = Option<bool>)]
    pub is_debit_routing_enabled: Option<bool>,

    //Merchant country for the profile
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub merchant_business_country: Option<api_enums::CountryAlpha2>,

    /// Indicates if external vault is enabled or not.
    pub is_external_vault_enabled: Option<bool>,

    /// External Vault Connector Details
    pub external_vault_connector_details: Option<ExternalVaultConnectorDetails>,

    /// Four-digit code assigned based on business type to determine processing fees and risk level
    #[schema(value_type = Option<MerchantCategoryCode>, example = "5411")]
    pub merchant_category_code: Option<api_enums::MerchantCategoryCode>,

    /// Merchant country code.
    /// This is a 3-digit ISO 3166-1 numeric country code that represents the country in which the merchant is registered or operates.
    /// Merchants typically receive this value based on their business registration information or during onboarding via payment processors or acquiring banks.
    /// It is used in payment processing, fraud detection, and regulatory compliance to determine regional rules and routing behavior.
    #[schema(value_type = Option<MerchantCountryCode>, example = "840")]
    pub merchant_country_code: Option<common_types::payments::MerchantCountryCode>,

    /// Indicates the state of revenue recovery algorithm type
    #[schema(value_type = Option<RevenueRecoveryAlgorithmType>, example = "cascading")]
    pub revenue_recovery_retry_algorithm_type:
        Option<common_enums::enums::RevenueRecoveryAlgorithmType>,

    /// Enable split payments, i.e., split the amount between multiple payment methods
    #[schema(value_type = Option<SplitTxnsEnabled>, default = "skip")]
    pub split_txns_enabled: Option<common_enums::SplitTxnsEnabled>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct BusinessCollectLinkConfig {
    #[serde(flatten)]
    pub config: BusinessGenericLinkConfig,

    /// List of payment methods shown on collect UI
    #[schema(value_type = Vec<EnabledPaymentMethod>, example = r#"[{"payment_method": "bank_transfer", "payment_method_types": ["ach", "bacs", "sepa"]}]"#)]
    pub enabled_payment_methods: Vec<link_utils::EnabledPaymentMethod>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct BusinessPayoutLinkConfig {
    #[serde(flatten)]
    pub config: BusinessGenericLinkConfig,

    /// Form layout of the payout link
    #[schema(value_type = Option<UIWidgetFormLayout>, max_length = 255, example = "tabs")]
    pub form_layout: Option<api_enums::UIWidgetFormLayout>,

    /// Allows for removing any validations / pre-requisites which are necessary in a production environment
    #[schema(value_type = Option<bool>, default = false)]
    pub payout_test_mode: Option<bool>,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct MaskedHeaders(HashMap<String, String>);

impl MaskedHeaders {
    fn mask_value(value: &str) -> String {
        let value_len = value.len();

        let masked_value = if value_len <= 4 {
            "*".repeat(value_len)
        } else {
            value
                .char_indices()
                .map(|(index, ch)| {
                    if index < 2 || index >= value_len - 2 {
                        // Show the first two and last two characters, mask the rest with '*'
                        ch
                    } else {
                        // Mask the remaining characters
                        '*'
                    }
                })
                .collect::<String>()
        };

        masked_value
    }

    pub fn from_headers(headers: HashMap<String, Secret<String>>) -> Self {
        let masked_headers = headers
            .into_iter()
            .map(|(key, value)| (key, Self::mask_value(value.peek())))
            .collect();

        Self(masked_headers)
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct BusinessGenericLinkConfig {
    /// Custom domain name to be used for hosting the link
    pub domain_name: Option<String>,

    /// A list of allowed domains (glob patterns) where this link can be embedded / opened from
    pub allowed_domains: HashSet<String>,

    #[serde(flatten)]
    #[schema(value_type = GenericLinkUiConfig)]
    pub ui_config: link_utils::GenericLinkUiConfig,
}

impl BusinessGenericLinkConfig {
    pub fn validate(&self) -> Result<(), &str> {
        // Validate host domain name
        let host_domain_valid = self
            .domain_name
            .clone()
            .map(|host_domain| link_utils::validate_strict_domain(&host_domain))
            .unwrap_or(true);
        if !host_domain_valid {
            return Err("Invalid host domain name received in payout_link_config");
        }

        let are_allowed_domains_valid = self
            .allowed_domains
            .clone()
            .iter()
            .all(|allowed_domain| link_utils::validate_wildcard_domain(allowed_domain));
        if !are_allowed_domains_valid {
            return Err("Invalid allowed domain names received in payout_link_config");
        }

        Ok(())
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct BusinessPaymentLinkConfig {
    /// Custom domain name to be used for hosting the link in your own domain
    pub domain_name: Option<String>,
    /// Default payment link config for all future payment link


impl NoonOrderNvp {
    pub fn new(metadata: &serde_json::Value) -> Self {
        let metadata_as_string = metadata.to_string();
        let hash_map: std::collections::BTreeMap<String, serde_json::Value> =
            serde_json::from_str(&metadata_as_string).unwrap_or(std::collections::BTreeMap::new());
        let inner = hash_map
            .into_iter()
            .enumerate()
            .map(|(index, (hs_key, hs_value))| {
                let noon_key = format!("{}", index + 1);
                // to_string() function on serde_json::Value returns a string with "" quotes. Noon doesn't allow this. Hence get_value_as_string function
                let noon_value = format!("{hs_key}={}", get_value_as_string(&hs_value));
                (noon_key, Secret::new(noon_value))
            })
            .collect();
        Self { inner }
    }
}


pub struct AdjustedBy {
    id: i32,
    #[serde(rename = "type")]
    adjusted_by_type: String,
    approval: i32,
    message: String,
    amount: FloatMajorUnit,
    created: String,
    url: String,
}


impl api::PaymentCapture for Novalnet {}

impl api::PaymentCapture for for Novalnet


pub fn get_surcharge_details(&self, surcharge_key: SurchargeKey) -> Option<&SurchargeDetails> {
        self.surcharge_results.get(&surcharge_key)
    }


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

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

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

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

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

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

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        let errors_list = response.errors.clone().unwrap_or_default();
        let option_error_code_message = get_error_code_error_message_based_on_priority(
            self.clone(),
            errors_list
                .into_iter()
                .map(|errors| errors.into())
                .collect(),
        );
        match response.errors {
            Some(errors) => Ok(ErrorResponse {
                status_code: res.status_code,
                code: option_error_code_message
                    .clone()
                    .map(|error_code_message| error_code_message.error_code)
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                message: option_error_code_message
                    .clone()
                    .map(|error_code_message| error_code_message.error_code)
                    .unwrap_or(NO_ERROR_CODE.to_string()),
                reason: Some(
                    errors
                        .iter()
                        .map(|error| format!("{} : {}", error.code, error.message))
                        .collect::<Vec<String>>()
                        .join(", "),
                ),
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
            None => Ok(ErrorResponse {
                status_code: res.status_code,
                code: NO_ERROR_CODE.to_string(),
                message: NO_ERROR_MESSAGE.to_string(),
                reason: None,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        }
    }
}


pub struct SamsungPayPaymentInformation {
    fluid_data: FluidData,
    tokenized_card: SamsungPayTokenizedCard,
}