File: crates/hyperswitch_connectors/src/connectors/braintree.rs
 Public structs: 1

pub mod transformers;

use std::sync::LazyLock;

use api_models::webhooks::IncomingWebhookEvent;
use base64::Engine;
use common_enums::{enums, CallConnectorAction, PaymentAction};
use common_utils::{
    consts::BASE64_ENGINE,
    crypto,
    errors::{CustomResult, ParsingError},
    ext_traits::{BytesExt, XmlExt},
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{
        AmountConvertor, StringMajorUnit, StringMajorUnitForConnector, StringMinorUnit,
        StringMinorUnitForConnector,
    },
};
use error_stack::{report, Report, ResultExt};
use hyperswitch_domain_models::{
    api::ApplicationResponse,
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        mandate_revoke::MandateRevoke,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
        CompleteAuthorize,
    },
    router_request_types::{
        AccessTokenRequestData, CompleteAuthorizeData, MandateRevokeRequestData,
        PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData,
        PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData,
        SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, MandateRevokeResponseData, PaymentMethodDetails, PaymentsResponseData,
        RefundsResponseData, SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        MandateRevokeRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData,
        PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData,
        RefundSyncRouterData, RefundsRouterData, TokenizationRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation,
    },
    configs::Connectors,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    disputes::DisputePayload,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{
        MandateRevokeType, PaymentsAuthorizeType, PaymentsCaptureType,
        PaymentsCompleteAuthorizeType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType,
        RefundSyncType, Response, TokenizationType,
    },
    webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails},
};
use masking::{ExposeInterface, Mask, PeekInterface, Secret};
use ring::hmac;
use router_env::logger;
use sha1::{Digest, Sha1};
use transformers::{self as braintree, get_status};

use crate::{
    constants::headers,
    types::ResponseRouterData,
    utils::{
        self, convert_amount, is_mandate_supported, PaymentMethodDataType,
        PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData,
    },
};

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

impl Braintree {
    pub const fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMajorUnitForConnector,
            amount_converter_webhooks: &StringMinorUnitForConnector,
        }
    }
}

pub const BRAINTREE_VERSION: &str = "Braintree-Version";
pub const BRAINTREE_VERSION_VALUE: &str = "2019-01-01";

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Braintree
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![
            (
                headers::CONTENT_TYPE.to_string(),
                self.get_content_type().to_string().into(),
            ),
            (
                BRAINTREE_VERSION.to_string(),
                BRAINTREE_VERSION_VALUE.to_string().into(),
            ),
        ];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }
}

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

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

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

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = braintree::BraintreeAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let auth_key = format!("{}:{}", auth.public_key.peek(), auth.private_key.peek());
        let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key));
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            auth_header.into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: Result<braintree::ErrorResponses, Report<ParsingError>> =
            res.response.parse_struct("Braintree Error Response");

        match response {
            Ok(braintree::ErrorResponses::BraintreeApiErrorResponse(response)) => {
                event_builder.map(|i| i.set_error_response_body(&response));
                router_env::logger::info!(connector_response=?response);

                let error_object = response.api_error_response.errors;
                let error = error_object.errors.first().or(error_object
                    .transaction
                    .as_ref()
                    .and_then(|transaction_error| {
                        transaction_error.errors.first().or(transaction_error
                            .credit_card
                            .as_ref()
                            .and_then(|credit_card_error| credit_card_error.errors.first()))
                    }));
                let (code, message) = error.map_or(
                    (NO_ERROR_CODE.to_string(), NO_ERROR_MESSAGE.to_string()),
                    |error| (error.code.clone(), error.message.clone()),
                );
                Ok(ErrorResponse {
                    status_code: res.status_code,
                    code,
                    message,
                    reason: Some(response.api_error_response.message),
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            }
            Ok(braintree::ErrorResponses::BraintreeErrorResponse(response)) => {
                event_builder.map(|i| i.set_error_response_body(&response));
                router_env::logger::info!(connector_response=?response);

                Ok(ErrorResponse {
                    status_code: res.status_code,
                    code: NO_ERROR_CODE.to_string(),
                    message: NO_ERROR_MESSAGE.to_string(),
                    reason: Some(response.errors),
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            }
            Err(error_msg) => {
                event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
                logger::error!(deserialization_error =? error_msg);
                utils::handle_json_response_deserialization_failure(res, "braintree")
            }
        }
    }
}

impl ConnectorValidation for Braintree {
    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: hyperswitch_domain_models::payment_method_data::PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]);
        is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}

impl api::Payment for Braintree {}

impl api::PaymentAuthorize for Braintree {}
impl api::PaymentSync for Braintree {}
impl api::PaymentVoid for Braintree {}
impl api::PaymentCapture for Braintree {}
impl api::PaymentsCompleteAuthorize for Braintree {}
impl api::PaymentSession for Braintree {}
impl api::ConnectorAccessToken for Braintree {}
impl api::ConnectorMandateRevoke for Braintree {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Braintree {
    // Not Implemented (R)
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Braintree {}

impl api::PaymentToken for Braintree {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Braintree
{
    fn get_headers(
        &self,
        req: &TokenizationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &TokenizationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

    fn get_request_body(
        &self,
        req: &TokenizationRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = transformers::BraintreeTokenRequest::try_from(req)?;

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

    fn build_request(
        &self,
        req: &TokenizationRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&TokenizationType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(TokenizationType::get_headers(self, req, connectors)?)
                .set_body(TokenizationType::get_request_body(self, req, connectors)?)
                .build(),
        ))
    }
    fn handle_response(
        &self,
        data: &TokenizationRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<TokenizationRouterData, errors::ConnectorError>
    where
        PaymentsResponseData: Clone,
    {
        let response: transformers::BraintreeTokenResponse = res
            .response
            .parse_struct("BraintreeTokenResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl api::MandateSetup for Braintree {}

#[allow(dead_code)]
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
    for Braintree
{
    // Not Implemented (R)
    fn build_request(
        &self,
        _req: &RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(
            errors::ConnectorError::NotImplemented("Setup Mandate flow for Braintree".to_string())
                .into(),
        )
    }
}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Braintree {
    fn get_headers(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

    fn get_request_body(
        &self,
        req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_amount_to_capture,
            req.request.currency,
        )?;
        let connector_router_data = braintree::BraintreeRouterData::try_from((amount, req))?;
        let connector_req =
            transformers::BraintreeCaptureRequest::try_from(&connector_router_data)?;

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

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

    fn handle_response(
        &self,
        data: &PaymentsCaptureRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCaptureRouterData, errors::ConnectorError> {
        let response: transformers::BraintreeCaptureResponse = res
            .response
            .parse_struct("Braintree PaymentsCaptureResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

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

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

    fn get_url(
        &self,
        _req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

    fn get_request_body(
        &self,
        req: &PaymentsSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = transformers::BraintreePSyncRequest::try_from(req)?;

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

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

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

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

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

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

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

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;
        let connector_router_data = braintree::BraintreeRouterData::try_from((amount, req))?;
        let connector_req: transformers::BraintreePaymentsRequest =
            transformers::BraintreePaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        match data.request.is_auto_capture()? {
            true => {
                let response: transformers::BraintreePaymentsResponse = res
                    .response
                    .parse_struct("Braintree PaymentsResponse")
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
                event_builder.map(|i| i.set_response_body(&response));
                router_env::logger::info!(connector_response=?response);
                RouterData::try_from(ResponseRouterData {
                    response,
                    data: data.clone(),
                    http_code: res.status_code,
                })
            }
            false => {
                let response: transformers::BraintreeAuthResponse = res
                    .response
                    .parse_struct("Braintree AuthResponse")
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
                event_builder.map(|i| i.set_response_body(&response));
                router_env::logger::info!(connector_response=?response);
                RouterData::try_from(ResponseRouterData {
                    response,
                    data: data.clone(),
                    http_code: res.status_code,
                })
            }
        }
    }

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

impl ConnectorIntegration<MandateRevoke, MandateRevokeRequestData, MandateRevokeResponseData>
    for Braintree
{
    fn get_headers(
        &self,
        req: &MandateRevokeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }
    fn get_url(
        &self,
        _req: &MandateRevokeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }
    fn build_request(
        &self,
        req: &MandateRevokeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&MandateRevokeType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(MandateRevokeType::get_headers(self, req, connectors)?)
                .set_body(MandateRevokeType::get_request_body(self, req, connectors)?)
                .build(),
        ))
    }
    fn get_request_body(
        &self,
        req: &MandateRevokeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = transformers::BraintreeRevokeMandateRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn handle_response(
        &self,
        data: &MandateRevokeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<MandateRevokeRouterData, errors::ConnectorError> {
        let response: transformers::BraintreeRevokeMandateResponse = res
            .response
            .parse_struct("BraintreeRevokeMandateResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Braintree {
    fn get_headers(
        &self,
        req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

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

    fn get_request_body(
        &self,
        req: &PaymentsCancelRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = transformers::BraintreeCancelRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn handle_response(
        &self,
        data: &PaymentsCancelRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCancelRouterData, errors::ConnectorError> {
        let response: transformers::BraintreeCancelResponse = res
            .response
            .parse_struct("Braintree VoidResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl api::Refund for Braintree {}
impl api::RefundExecute for Braintree {}
impl api::RefundSync for Braintree {}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Braintree {
    fn get_headers(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_refund_amount,
            req.request.currency,
        )?;
        let connector_router_data = braintree::BraintreeRouterData::try_from((amount, req))?;
        let connector_req = transformers::BraintreeRefundRequest::try_from(connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }
    fn build_request(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&RefundExecuteType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(RefundExecuteType::get_headers(self, req, connectors)?)
            .set_body(RefundExecuteType::get_request_body(self, req, connectors)?)
            .build();
        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &RefundsRouterData<Execute>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
        let response: transformers::BraintreeRefundResponse = res
            .response
            .parse_struct("Braintree RefundResponse")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Braintree {
    fn get_headers(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(self.base_url(connectors).to_string())
    }

    fn get_request_body(
        &self,
        req: &RefundSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = transformers::BraintreeRSyncRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &RefundSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RouterData<RSync, RefundsData, RefundsResponseData>, errors::ConnectorError>
    {
        let response: transformers::BraintreeRSyncResponse = res
            .response
            .parse_struct("Braintree RefundResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }
    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl IncomingWebhook for Braintree {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
        Ok(Box::new(crypto::HmacSha1))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let notif_item = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

        let signature_pairs: Vec<(&str, &str)> = notif_item
            .bt_signature
            .split('&')
            .collect::<Vec<&str>>()
            .into_iter()
            .map(|pair| pair.split_once('|').unwrap_or(("", "")))
            .collect::<Vec<(_, _)>>();

        let merchant_secret = connector_webhook_secrets
            .additional_secret //public key
            .clone()
            .ok_or(errors::ConnectorError::WebhookVerificationSecretNotFound)?;

        let signature = get_matching_webhook_signature(signature_pairs, merchant_secret.expose())
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?;
        Ok(signature.as_bytes().to_vec())
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let notify = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

        let message = notify.bt_payload.to_string();

        Ok(message.into_bytes())
    }

    async fn verify_webhook_source(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
        _connector_account_details: crypto::Encryptable<Secret<serde_json::Value>>,
        connector_label: &str,
    ) -> CustomResult<bool, errors::ConnectorError> {
        let connector_webhook_secrets = self
            .get_webhook_source_verification_merchant_secret(
                merchant_id,
                connector_label,
                connector_webhook_details,
            )
            .await
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

        let signature = self
            .get_webhook_source_verification_signature(request, &connector_webhook_secrets)
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)?;

        let message = self
            .get_webhook_source_verification_message(
                request,
                merchant_id,
                &connector_webhook_secrets,
            )
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

        let sha1_hash_key = Sha1::digest(&connector_webhook_secrets.secret);

        let signing_key = hmac::Key::new(
            hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
            sha1_hash_key.as_slice(),
        );
        let signed_messaged = hmac::sign(&signing_key, &message);
        let payload_sign: String = hex::encode(signed_messaged);
        Ok(payload_sign.as_bytes().eq(&signature))
    }

    fn get_webhook_object_reference_id(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let notif = get_webhook_object_from_body(_request.body)
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?;

        match response.dispute {
            Some(dispute_data) => Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                api_models::payments::PaymentIdType::ConnectorTransactionId(
                    dispute_data.transaction.id,
                ),
            )),
            None => Err(report!(errors::ConnectorError::WebhookReferenceIdNotFound)),
        }
    }

    fn get_webhook_event_type(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        let notif = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?;

        Ok(get_status(response.kind.as_str()))
    }

    fn get_webhook_resource_object(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let notif = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?;

        Ok(Box::new(response))
    }

    fn get_webhook_api_response(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
        _error_kind: Option<IncomingWebhookFlowError>,


        }

        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
    #[serde(flatten)]
    #[schema(value_type = PaymentLinkConfigRequest)]
    pub default_config: Option<PaymentLinkConfigRequest>,
    /// list of configs for multi theme setup
    pub business_specific_configs: Option<HashMap<String, PaymentLinkConfigRequest>>,
    /// A list of allowed domains (glob patterns) where this link can be embedded / opened from
    #[schema(value_type = Option<HashSet<String>>)]
    pub allowed_domains: Option<HashSet<String>>,
    /// Toggle for HyperSwitch branding visibility
    pub branding_visibility: Option<bool>,
}

impl BusinessPaymentLinkConfig {
    pub fn validate(&self) -> Result<(), &str> {
        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 payment_link_config");
        }

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

        Ok(())
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct PaymentLinkConfigRequest {
    /// custom theme for the payment link
    #[schema(value_type = Option<String>, max_length = 255, example = "#4E6ADD")]
    pub theme: Option<String>,
    /// merchant display logo
    #[schema(value_type = Option<String>, max_length = 255, example = "https://i.pinimg.com/736x/4d/83/5c/4d835ca8aafbbb15f84d07d926fda473.jpg")]
    pub logo: Option<String>,
    /// Custom merchant name for payment link
    #[schema(value_type = Option<String>, max_length = 255, example = "hyperswitch")]
    pub seller_name: Option<String>,
    /// Custom layout for sdk
    #[schema(value_type = Option<String>, max_length = 255, example = "accordion")]
    pub sdk_layout: Option<String>,
    /// Display only the sdk for payment link
    #[schema(default = false, example = true)]
    pub display_sdk_only: Option<bool>,
    /// Enable saved payment method option for payment link
    #[schema(default = false, example = true)]
    pub enabled_saved_payment_method: Option<bool>,
    /// Hide card nickname field option for payment link
    #[schema(default = false, example = true)]
    pub hide_card_nickname_field: Option<bool>,
    /// Show card form by default for payment link
    #[schema(default = true, example = true)]
    pub show_card_form_by_default: Option<bool>,
    /// Dynamic details related to merchant to be rendered in payment link
    pub transaction_details: Option<Vec<PaymentLinkTransactionDetails>>,
    /// Configurations for the background image for details section
    pub background_image: Option<PaymentLinkBackgroundImageConfig>,
    /// Custom layout for details section
    #[schema(value_type = Option<PaymentLinkDetailsLayout>, example = "layout1")]
    pub details_layout: Option<api_enums::PaymentLinkDetailsLayout>,
    /// Text for payment link's handle confirm button
    pub payment_button_text: Option<String>,
    /// Text for customizing message for card terms
    pub custom_message_for_card_terms: Option<String>,
    /// Custom background colour for payment link's handle confirm button
    pub payment_button_colour: Option<String>,
    /// Skip the status screen after payment completion
    pub skip_status_screen: Option<bool>,
    /// Custom text colour for payment link's handle confirm button
    pub payment_button_text_colour: Option<String>,
    /// Custom background colour for the payment link
    pub background_colour: Option<String>,
    /// SDK configuration rules
    pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
    /// Payment link configuration rules
    pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
    /// Flag to enable the button only when the payment form is ready for submission
    pub enable_button_only_on_form_ready: Option<bool>,
    /// Optional header for the SDK's payment form
    pub payment_form_header_text: Option<String>,
    /// Label type in the SDK's payment form
    #[schema(value_type = Option<PaymentLinkSdkLabelType>, example = "floating")]
    pub payment_form_label_type: Option<api_enums::PaymentLinkSdkLabelType>,
    /// Boolean for controlling whether or not to show the explicit consent for storing cards
    #[schema(value_type = Option<PaymentLinkShowSdkTerms>, example = "always")]
    pub show_card_terms: Option<api_enums::PaymentLinkShowSdkTerms>,
    /// Boolean to control payment button text for setup mandate calls
    pub is_setup_mandate_flow: Option<bool>,
    /// Hex color for the CVC icon during error state
    pub color_icon_card_cvc_error: Option<String>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct PaymentLinkTransactionDetails {
    /// Key for the transaction details
    #[schema(value_type = String, max_length = 255, example = "Policy-Number")]
    pub key: String,
    /// Value for the transaction details
    #[schema(value_type = String, max_length = 255, example = "297472368473924")]
    pub value: String,
    /// UI configuration for the transaction details
    pub ui_configuration: Option<TransactionDetailsUiConfiguration>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct TransactionDetailsUiConfiguration {
    /// Position of the key-value pair in the UI
    #[schema(value_type = Option<i8>, example = 5)]
    pub position: Option<i8>,
    /// Whether the key should be bold
    #[schema(default = false, example = true)]
    pub is_key_bold: Option<bool>,
    /// Whether the value should be bold
    #[schema(default = false, example = true)]
    pub is_value_bold: Option<bool>,
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)]
pub struct PaymentLinkBackgroundImageConfig {
    /// URL of the image
    #[schema(value_type = String, example = "https://hyperswitch.io/favicon.ico")]
    pub url: common_utils::types::Url,
    /// Position of the image in the UI
    #[schema(value_type = Option<ElementPosition>, example = "top-left")]
    pub position: Option<api_enums::ElementPosition>,
    /// Size of the image in the UI
    #[schema(value_type = Option<ElementSize>, example = "contain")]
    pub size: Option<api_enums::ElementSize>,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)]
pub struct PaymentLinkConfig {
    /// custom theme for the payment link
    pub theme: String,
    /// merchant display logo
    pub logo: String,
    /// Custom merchant name for payment link
    pub seller_name: String,
    /// Custom layout for sdk
    pub sdk_layout: String,
    /// Display only the sdk for payment link
    pub display_sdk_only: bool,
    /// Enable saved payment method option for payment link
    pub enabled_saved_payment_method: bool,
    /// Hide card nickname field option for payment link
    pub hide_card_nickname_field: bool,
    /// Show card form by default for payment link
    pub show_card_form_by_default: bool,
    /// A list of allowed domains (glob patterns) where this link can be embedded / opened from
    pub allowed_domains: Option<HashSet<String>>,
    /// Dynamic details related to merchant to be rendered in payment link
    pub transaction_details: Option<Vec<PaymentLinkTransactionDetails>>,
    /// Configurations for the background image for details section
    pub background_image: Option<PaymentLinkBackgroundImageConfig>,
    /// Custom layout for details section
    #[schema(value_type = Option<PaymentLinkDetailsLayout>, example = "layout1")]
    pub details_layout: Option<api_enums::PaymentLinkDetailsLayout>,
    /// Toggle for HyperSwitch branding visibility
    pub branding_visibility: Option<bool>,
    /// Text for payment link's handle confirm button
    pub payment_button_text: Option<String>,
    /// Text for customizing message for card terms
    pub custom_message_for_card_terms: Option<String>,
    /// Custom background colour for payment link's handle confirm button
    pub payment_button_colour: Option<String>,
    /// Skip the status screen after payment completion
    pub skip_status_screen: Option<bool>,
    /// Custom text colour for payment link's handle confirm button
    pub payment_button_text_colour: Option<String>,
    /// Custom background colour for the payment link
    pub background_colour: Option<String>,
    /// SDK configuration rules
    pub sdk_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
    /// Payment link configuration rules
    pub payment_link_ui_rules: Option<HashMap<String, HashMap<String, String>>>,
    /// Flag to enable the button only when the payment form is ready for submission
    pub enable_button_only_on_form_ready: bool,
    /// Optional header for the SDK's payment form
    pub payment_form_header_text: Option<String>,
    /// Label type in the SDK's payment form
    #[schema(value_type = Option<PaymentLinkSdkLabelType>, example = "floating")]
    pub payment_form_label_type: Option<api_enums::PaymentLinkSdkLabelType>,
    /// Boolean for controlling whether or not to show the explicit consent for storing cards
    #[schema(value_type = Option<PaymentLinkShowSdkTerms>, example = "always")]
    pub show_card_terms: Option<api_enums::PaymentLinkShowSdkTerms>,
    /// Boolean to control payment button text for setup mandate calls
    pub is_setup_mandate_flow: Option<bool>,
    /// Hex color for the CVC icon during error state
    pub color_icon_card_cvc_error: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct ExtendedCardInfoChoice {
    pub enabled: bool,
}

impl common_utils::events::ApiEventMetric for ExtendedCardInfoChoice {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct ConnectorAgnosticMitChoice {
    pub enabled: bool,
}

impl common_utils::events::ApiEventMetric for ConnectorAgnosticMitChoice {}

impl common_utils::events::ApiEventMetric for payment_methods::PaymentMethodMigrate {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct ExtendedCardInfoConfig {
    /// Merchant public key
    #[schema(value_type = String)]
    pub public_key: Secret<String>,
    /// TTL for extended card info
    #[schema(default = 900, maximum = 7200, value_type = u16)]
    #[serde(default)]
    pub ttl_in_secs: TtlForExtendedCardInfo,
}

#[derive(Debug, serde::Serialize, Clone)]
pub struct TtlForExtendedCardInfo(u16);

impl Default for TtlForExtendedCardInfo {
    fn default() -> Self {
        Self(consts::DEFAULT_TTL_FOR_EXTENDED_CARD_INFO)
    }
}

impl<'de> Deserialize<'de> for TtlForExtendedCardInfo {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = u16::deserialize(deserializer)?;

        // Check if value exceeds the maximum allowed
        if value > consts::MAX_TTL_FOR_EXTENDED_CARD_INFO {
            Err(serde::de::Error::custom(
                "ttl_in_secs must be less than or equal to 7200 (2hrs)",
            ))
        } else {
            Ok(Self(value))
        }
    }
}

impl std::ops::Deref for TtlForExtendedCardInfo {
    type Target = u16;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}


pub struct UnifiedTranslations {
    pub unified_code: String,
    pub unified_message: String,
    pub locale: String,
    pub translation: String,
    pub created_at: PrimitiveDateTime,
    pub last_modified_at: PrimitiveDateTime,
}


pub struct CreditorBank {
    /// Address
    pub address: Option<NordeaAddress>,
    /// Bank code
    pub bank_code: Option<String>,
    /// Business identifier code (BIC) of the creditor bank.
    /// This information is required, if the creditor account number is not in IBAN format.
    #[serde(rename = "bic")]
    pub business_identifier_code: Option<Secret<String>>,
    /// Country of the creditor bank. Only ISO 3166 alpha-2 codes are used.
    pub country: api_models::enums::CountryAlpha2,
    /// Name of the creditor bank.
    #[serde(rename = "name")]
    pub bank_name: Option<String>,
}


pub fn new(
        tenant_id: common_utils::id_type::TenantId,
        routable_connectors: String,
        flow: &str,
        request: serde_json::Value,
        url: String,
        method: ApiMethod,
        payment_id: String,
        profile_id: common_utils::id_type::ProfileId,
        merchant_id: common_utils::id_type::MerchantId,
        request_id: Option<RequestId>,
        routing_engine: RoutingEngine,
    ) -> Self {
        Self {
            tenant_id,
            routable_connectors,
            flow: flow.to_string(),
            request: request.to_string(),
            response: None,
            error: None,
            url,
            method: method.to_string(),
            payment_id,
            profile_id,
            merchant_id,
            created_at: OffsetDateTime::now_utc().unix_timestamp_nanos(),
            status_code: None,
            request_id: request_id
                .map(|i| i.as_hyphenated().to_string())
                .unwrap_or("NO_REQUEST_ID".to_string()),
            routing_engine,
            payment_connector: None,
            routing_approach: None,
        }
    }


pub struct AffirmCompleteAuthorizeResponse {
    pub id: String,
    pub status: AffirmTransactionStatus,
    pub amount: MinorUnit,
    pub amount_refunded: MinorUnit,
    pub authorization_expiration: String,
    pub checkout_id: String,
    pub created: String,
    pub currency: Currency,
    pub events: Vec<TransactionEvent>,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
    pub provider_id: Option<i64>,
    pub remove_tax: Option<bool>,
    pub checkout: Option<Value>,
    pub refund_expires: Option<String>,
    pub remaining_capturable_amount: Option<i64>,
    pub loan_information: Option<LoanInformation>,
    pub user_id: Option<String>,
    pub platform: Option<String>,
}


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


OpenAPI Block Path: paths."/authentication"
{
  "post": {
    "tags": [
      "Authentication"
    ],
    "summary": "Authentication - Create",
    "description": "Create a new authentication for accessing our APIs from your servers.\n",
    "operationId": "Create an Authentication",
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/AuthenticationCreateRequest"
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Authentication created",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/AuthenticationResponse"
            }
          }
        }
      },
      "400": {
        "description": "Invalid data"
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  }
}


pub struct PayoutAttempt {
    pub payout_attempt_id: String,
    pub payout_id: common_utils::id_type::PayoutId,
    pub customer_id: Option<common_utils::id_type::CustomerId>,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub address_id: Option<String>,
    pub connector: Option<String>,
    pub connector_payout_id: Option<String>,
    pub payout_token: Option<String>,
    pub status: storage_enums::PayoutStatus,
    pub is_eligible: Option<bool>,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
    pub business_country: Option<storage_enums::CountryAlpha2>,
    pub business_label: Option<String>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub last_modified_at: PrimitiveDateTime,
    pub profile_id: common_utils::id_type::ProfileId,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub routing_info: Option<serde_json::Value>,
    pub unified_code: Option<UnifiedCode>,
    pub unified_message: Option<UnifiedMessage>,
    pub additional_payout_method_data: Option<payout_method_utils::AdditionalPayoutMethodData>,
    pub merchant_order_reference_id: Option<String>,
}


pub struct ErrorDetails {
    pub field: String,
    pub issue: String,
}


pub struct ArchipelCaptureRequest {
    order: ArchipelCaptureOrderRequest,
}


impl Default for DisputePollingIntervalInHours {
        /// Default for `ShouldCollectCvvDuringPayment` is `true`
        fn default() -> Self {
            Self(DEFAULT_DISPUTE_POLLING_INTERVAL_IN_HOURS)
        }
    }

impl Default for for DisputePollingIntervalInHours


impl ConnectorValidation for Bankofamerica {
    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd = std::collections::HashSet::from([
            PaymentMethodDataType::Card,
            PaymentMethodDataType::ApplePay,
            PaymentMethodDataType::GooglePay,
        ]);
        utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}

impl ConnectorValidation for for Bankofamerica


impl api::PaymentToken for Deutschebank {}

impl api::PaymentToken for for Deutschebank


pub struct PaymentResponse {
    pub id: String,
    pub status: PaymentStatus,
    pub status_reason_information: Option<String>,
    pub authentication: Option<Authentication>,
    pub error_info: Option<ErrorInfo>,
}


pub async fn api_key_revoke(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<(
        common_utils::id_type::MerchantId,
        common_utils::id_type::ApiKeyId,
    )


impl api::MandateSetup for Archipel {}


pub async fn find_optional_resource_by_id<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        find_resource_db_fut: R,
        find_by: FindResourceBy<'_>,
    ) -> error_stack::Result<Option<D>, errors::StorageError>
    where
        D: DomainType,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<Option<M>, DatabaseError>> + Send,
    {
        let database_call = || async {
            find_resource_db_fut.await.map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })
        };
        let storage_scheme = Box::pin(decide_storage_scheme::<T, M>(
            self,
            storage_scheme,
            Op::Find,
        ))
        .await;
        let res = || async {
            match storage_scheme {
                MerchantStorageScheme::PostgresOnly => database_call().await,
                MerchantStorageScheme::RedisKv => {
                    let (field, key) = match find_by {
                        FindResourceBy::Id(field, key) => (field, key),
                        FindResourceBy::LookupId(lookup_id) => {
                            let lookup = fallback_reverse_lookup_not_found!(
                                self.get_lookup_by_lookup_id(&lookup_id, storage_scheme)
                                    .await,
                                database_call().await
                            );
                            (
                                lookup.clone().sk_id,
                                PartitionKey::CombinationKey {
                                    combination: &lookup.clone().pk_id,
                                },
                            )
                        }
                    };

                    Box::pin(try_redis_get_else_try_database_get(
                        async {
                            Box::pin(kv_wrapper(self, KvOperation::<M>::HGet(&field), key))
                                .await?
                                .try_into_hget()
                                .map(Some)
                        },
                        database_call,
                    ))
                    .await
                }
            }
        };
        match res().await? {
            Some(resource) => Ok(Some(
                resource
                    .convert(
                        state,
                        key_store.key.get_inner(),
                        key_store.merchant_id.clone().into(),
                    )
                    .await
                    .change_context(errors::StorageError::DecryptionError)?,
            )),
            None => Ok(None),
        }
    }


impl api::RefundSync for Paypal {}


pub trait HasSchemaParameters {
    fn get_schema_parameters(&self) -> syn::Result<SchemaParameters>;
}


File: crates/api_models/src/webhooks.rs
 Public functions: 6
 Public structs: 3

use common_utils::custom_serde;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

#[cfg(feature = "payouts")]
use crate::payouts;
use crate::{disputes, enums as api_enums, mandates, payments, refunds};

#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Copy)]
#[serde(rename_all = "snake_case")]
pub enum IncomingWebhookEvent {
    /// Authorization + Capture failure
    PaymentIntentFailure,
    /// Authorization + Capture success
    PaymentIntentSuccess,
    PaymentIntentProcessing,
    PaymentIntentPartiallyFunded,
    PaymentIntentCancelled,
    PaymentIntentCancelFailure,
    PaymentIntentAuthorizationSuccess,
    PaymentIntentAuthorizationFailure,
    PaymentIntentCaptureSuccess,
    PaymentIntentCaptureFailure,
    PaymentIntentExpired,
    PaymentActionRequired,
    EventNotSupported,
    SourceChargeable,
    SourceTransactionCreated,
    RefundFailure,
    RefundSuccess,
    DisputeOpened,
    DisputeExpired,
    DisputeAccepted,
    DisputeCancelled,
    DisputeChallenged,
    // dispute has been successfully challenged by the merchant
    DisputeWon,
    // dispute has been unsuccessfully challenged
    DisputeLost,
    MandateActive,
    MandateRevoked,
    EndpointVerification,
    ExternalAuthenticationARes,
    FrmApproved,
    FrmRejected,
    #[cfg(feature = "payouts")]
    PayoutSuccess,
    #[cfg(feature = "payouts")]
    PayoutFailure,
    #[cfg(feature = "payouts")]
    PayoutProcessing,
    #[cfg(feature = "payouts")]
    PayoutCancelled,
    #[cfg(feature = "payouts")]
    PayoutCreated,
    #[cfg(feature = "payouts")]
    PayoutExpired,
    #[cfg(feature = "payouts")]
    PayoutReversed,
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    RecoveryPaymentFailure,
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    RecoveryPaymentSuccess,
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    RecoveryPaymentPending,
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    RecoveryInvoiceCancel,
}

impl IncomingWebhookEvent {
    /// Convert UCS event type integer to IncomingWebhookEvent
    /// Maps from proto WebhookEventType enum values to IncomingWebhookEvent variants
    pub fn from_ucs_event_type(event_type: i32) -> Self {
        match event_type {
            0 => Self::EventNotSupported,
            // Payment intent events
            1 => Self::PaymentIntentFailure,
            2 => Self::PaymentIntentSuccess,
            3 => Self::PaymentIntentProcessing,
            4 => Self::PaymentIntentPartiallyFunded,
            5 => Self::PaymentIntentCancelled,
            6 => Self::PaymentIntentCancelFailure,
            7 => Self::PaymentIntentAuthorizationSuccess,
            8 => Self::PaymentIntentAuthorizationFailure,
            9 => Self::PaymentIntentCaptureSuccess,
            10 => Self::PaymentIntentCaptureFailure,
            11 => Self::PaymentIntentExpired,
            12 => Self::PaymentActionRequired,
            // Source events
            13 => Self::SourceChargeable,
            14 => Self::SourceTransactionCreated,
            // Refund events
            15 => Self::RefundFailure,
            16 => Self::RefundSuccess,
            // Dispute events
            17 => Self::DisputeOpened,
            18 => Self::DisputeExpired,
            19 => Self::DisputeAccepted,
            20 => Self::DisputeCancelled,
            21 => Self::DisputeChallenged,
            22 => Self::DisputeWon,
            23 => Self::DisputeLost,
            // Mandate events
            24 => Self::MandateActive,
            25 => Self::MandateRevoked,
            // Miscellaneous events
            26 => Self::EndpointVerification,
            27 => Self::ExternalAuthenticationARes,
            28 => Self::FrmApproved,
            29 => Self::FrmRejected,
            // Payout events
            #[cfg(feature = "payouts")]
            30 => Self::PayoutSuccess,
            #[cfg(feature = "payouts")]
            31 => Self::PayoutFailure,
            #[cfg(feature = "payouts")]
            32 => Self::PayoutProcessing,
            #[cfg(feature = "payouts")]
            33 => Self::PayoutCancelled,
            #[cfg(feature = "payouts")]
            34 => Self::PayoutCreated,
            #[cfg(feature = "payouts")]
            35 => Self::PayoutExpired,
            #[cfg(feature = "payouts")]
            36 => Self::PayoutReversed,
            _ => Self::EventNotSupported,
        }
    }
}

pub enum WebhookFlow {
    Payment,
    #[cfg(feature = "payouts")]
    Payout,
    Refund,
    Dispute,
    Subscription,
    ReturnResponse,
    BankTransfer,
    Mandate,
    ExternalAuthentication,
    FraudCheck,
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    Recovery,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// This enum tells about the affect a webhook had on an object
pub enum WebhookResponseTracker {
    #[cfg(feature = "v1")]
    Payment {
        payment_id: common_utils::id_type::PaymentId,
        status: common_enums::IntentStatus,
    },
    #[cfg(feature = "v2")]
    Payment {
        payment_id: common_utils::id_type::GlobalPaymentId,
        status: common_enums::IntentStatus,
    },
    #[cfg(feature = "payouts")]
    Payout {
        payout_id: common_utils::id_type::PayoutId,
        status: common_enums::PayoutStatus,
    },
    #[cfg(feature = "v1")]
    Refund {
        payment_id: common_utils::id_type::PaymentId,
        refund_id: String,
        status: common_enums::RefundStatus,
    },
    #[cfg(feature = "v2")]
    Refund {
        payment_id: common_utils::id_type::GlobalPaymentId,
        refund_id: String,
        status: common_enums::RefundStatus,
    },
    #[cfg(feature = "v1")]
    Dispute {
        dispute_id: String,
        payment_id: common_utils::id_type::PaymentId,
        status: common_enums::DisputeStatus,
    },
    #[cfg(feature = "v2")]
    Dispute {
        dispute_id: String,
        payment_id: common_utils::id_type::GlobalPaymentId,
        status: common_enums::DisputeStatus,
    },
    Mandate {
        mandate_id: String,
        status: common_enums::MandateStatus,
    },
    #[cfg(feature = "v1")]
    PaymentMethod {
        payment_method_id: String,
        status: common_enums::PaymentMethodStatus,
    },
    NoEffect,
    Relay {
        relay_id: common_utils::id_type::RelayId,
        status: common_enums::RelayStatus,
    },
}

impl WebhookResponseTracker {
    #[cfg(feature = "v1")]
    pub fn get_payment_id(&self) -> Option<common_utils::id_type::PaymentId> {
        match self {
            Self::Payment { payment_id, .. }
            | Self::Refund { payment_id, .. }
            | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
            Self::NoEffect | Self::Mandate { .. } | Self::PaymentMethod { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
            Self::Relay { .. } => None,
        }
    }

    #[cfg(feature = "v1")]
    pub fn get_payment_method_id(&self) -> Option<String> {
        match self {
            Self::PaymentMethod {
                payment_method_id, ..
            } => Some(payment_method_id.to_owned()),
            Self::Payment { .. }
            | Self::Refund { .. }
            | Self::Dispute { .. }
            | Self::NoEffect
            | Self::Mandate { .. }
            | Self::Relay { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
        }
    }

    #[cfg(feature = "v2")]
    pub fn get_payment_id(&self) -> Option<common_utils::id_type::GlobalPaymentId> {
        match self {
            Self::Payment { payment_id, .. }
            | Self::Refund { payment_id, .. }
            | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
            Self::NoEffect | Self::Mandate { .. } => None,
            #[cfg(feature = "payouts")]
            Self::Payout { .. } => None,
            Self::Relay { .. } => None,
        }
    }
}

impl From<IncomingWebhookEvent> for WebhookFlow {
    fn from(evt: IncomingWebhookEvent) -> Self {
        match evt {
            IncomingWebhookEvent::PaymentIntentFailure
            | IncomingWebhookEvent::PaymentIntentSuccess
            | IncomingWebhookEvent::PaymentIntentProcessing
            | IncomingWebhookEvent::PaymentActionRequired
            | IncomingWebhookEvent::PaymentIntentPartiallyFunded
            | IncomingWebhookEvent::PaymentIntentCancelled
            | IncomingWebhookEvent::PaymentIntentCancelFailure
            | IncomingWebhookEvent::PaymentIntentAuthorizationSuccess
            | IncomingWebhookEvent::PaymentIntentAuthorizationFailure
            | IncomingWebhookEvent::PaymentIntentCaptureSuccess
            | IncomingWebhookEvent::PaymentIntentCaptureFailure
            | IncomingWebhookEvent::PaymentIntentExpired => Self::Payment,
            IncomingWebhookEvent::EventNotSupported => Self::ReturnResponse,
            IncomingWebhookEvent::RefundSuccess | IncomingWebhookEvent::RefundFailure => {
                Self::Refund
            }
            IncomingWebhookEvent::MandateActive | IncomingWebhookEvent::MandateRevoked => {
                Self::Mandate
            }
            IncomingWebhookEvent::DisputeOpened
            | IncomingWebhookEvent::DisputeAccepted
            | IncomingWebhookEvent::DisputeExpired
            | IncomingWebhookEvent::DisputeCancelled
            | IncomingWebhookEvent::DisputeChallenged
            | IncomingWebhookEvent::DisputeWon
            | IncomingWebhookEvent::DisputeLost => Self::Dispute,
            IncomingWebhookEvent::EndpointVerification => Self::ReturnResponse,
            IncomingWebhookEvent::SourceChargeable
            | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer,
            IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication,
            IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => {
                Self::FraudCheck
            }
            #[cfg(feature = "payouts")]
            IncomingWebhookEvent::PayoutSuccess
            | IncomingWebhookEvent::PayoutFailure
            | IncomingWebhookEvent::PayoutProcessing
            | IncomingWebhookEvent::PayoutCancelled
            | IncomingWebhookEvent::PayoutCreated
            | IncomingWebhookEvent::PayoutExpired
            | IncomingWebhookEvent::PayoutReversed => Self::Payout,
            #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
            IncomingWebhookEvent::RecoveryInvoiceCancel
            | IncomingWebhookEvent::RecoveryPaymentFailure
            | IncomingWebhookEvent::RecoveryPaymentPending
            | IncomingWebhookEvent::RecoveryPaymentSuccess => Self::Recovery,
        }
    }
}

pub type MerchantWebhookConfig = std::collections::HashSet<IncomingWebhookEvent>;

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum RefundIdType {
    RefundId(String),
    ConnectorRefundId(String),
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum MandateIdType {
    MandateId(String),
    ConnectorMandateId(String),
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum AuthenticationIdType {
    AuthenticationId(common_utils::id_type::AuthenticationId),
    ConnectorAuthenticationId(String),
}

#[cfg(feature = "payouts")]
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum PayoutIdType {
    PayoutAttemptId(String),
    ConnectorPayoutId(String),
}

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum ObjectReferenceId {
    PaymentId(payments::PaymentIdType),
    RefundId(RefundIdType),
    MandateId(MandateIdType),
    ExternalAuthenticationID(AuthenticationIdType),
    #[cfg(feature = "payouts")]
    PayoutId(PayoutIdType),
    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
    InvoiceId(InvoiceIdType),
}

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum InvoiceIdType {
    ConnectorInvoiceId(String),
}

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
impl ObjectReferenceId {
    pub fn get_connector_transaction_id_as_string(
        self,
    ) -> Result<String, common_utils::errors::ValidationError> {
        match self {
            Self::PaymentId(
                payments::PaymentIdType::ConnectorTransactionId(id)
            ) => Ok(id),
            Self::PaymentId(_)=>Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "ConnectorTransactionId variant of PaymentId is required but received otherr variant",
                },
            ),
            Self::RefundId(_) => Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "PaymentId is required but received RefundId",
                },
            ),
            Self::MandateId(_) => Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "PaymentId is required but received MandateId",
                },
            ),
            Self::ExternalAuthenticationID(_) => Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "PaymentId is required but received ExternalAuthenticationID",
                },
            ),
            #[cfg(feature = "payouts")]
            Self::PayoutId(_) => Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "PaymentId is required but received PayoutId",
                },
            ),
            Self::InvoiceId(_) => Err(
                common_utils::errors::ValidationError::IncorrectValueProvided {
                    field_name: "PaymentId is required but received InvoiceId",
                },
            )
        }
    }
}

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

#[derive(Debug, Serialize, ToSchema)]
pub struct OutgoingWebhook {
    /// The merchant id of the merchant
    #[schema(value_type = String)]
    pub merchant_id: common_utils::id_type::MerchantId,

    /// The unique event id for each webhook
    pub event_id: String,

    /// The type of event this webhook corresponds to.
    #[schema(value_type = EventType)]
    pub event_type: api_enums::EventType,

    /// This is specific to the flow, for ex: it will be `PaymentsResponse` for payments flow
    pub content: OutgoingWebhookContent,

    /// The time at which webhook was sent
    #[serde(default, with = "custom_serde::iso8601")]
    pub timestamp: PrimitiveDateTime,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v1")]
pub enum OutgoingWebhookContent {
    #[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
    PaymentDetails(Box<payments::PaymentsResponse>),
    #[schema(value_type = RefundResponse, title = "RefundResponse")]
    RefundDetails(Box<refunds::RefundResponse>),
    #[schema(value_type = DisputeResponse, title = "DisputeResponse")]
    DisputeDetails(Box<disputes::DisputeResponse>),
    #[schema(value_type = MandateResponse, title = "MandateResponse")]
    MandateDetails(Box<mandates::MandateResponse>),
    #[cfg(feature = "payouts")]
    #[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")]
    PayoutDetails(Box<payouts::PayoutCreateResponse>),
}

#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
#[cfg(feature = "v2")]
pub enum OutgoingWebhookContent {
    #[schema(value_type = PaymentsResponse, title = "PaymentsResponse")]
    PaymentDetails(Box<payments::PaymentsResponse>),
    #[schema(value_type = RefundResponse, title = "RefundResponse")]
    RefundDetails(Box<refunds::RefundResponse>),
    #[schema(value_type = DisputeResponse, title = "DisputeResponse")]
    DisputeDetails(Box<disputes::DisputeResponse>),
    #[schema(value_type = MandateResponse, title = "MandateResponse")]
    MandateDetails(Box<mandates::MandateResponse>),
    #[cfg(feature = "payouts")]
    #[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")]
    PayoutDetails(Box<payouts::PayoutCreateResponse>),
}

#[derive(Debug, Clone, Serialize)]
pub struct ConnectorWebhookSecrets {
    pub secret: Vec<u8>,
    pub additional_secret: Option<masking::Secret<String>>,
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl IncomingWebhookEvent {
    pub fn is_recovery_transaction_event(&self) -> bool {
        matches!(
            self,
            Self::RecoveryPaymentFailure
                | Self::RecoveryPaymentSuccess
                | Self::RecoveryPaymentPending
        )
    }
}


pub struct GiropayBankRedirectAdditionalData {
    #[schema(value_type = Option<String>)]
    /// Masked bank account bic code
    pub bic: Option<MaskedSortCode>,

    /// Partially masked international bank account number (iban) for SEPA
    #[schema(value_type = Option<String>)]
    pub iban: Option<MaskedIban>,

    /// Country for bank payment
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub country: Option<api_enums::CountryAlpha2>,
}


pub trait SerializableSecret: Serialize {}


pub struct CybersourcePaymentsResponse {
    id: String,
    status: Option<CybersourcePaymentStatus>,
    client_reference_information: Option<ClientReferenceInformation>,
    processor_information: Option<ClientProcessorInformation>,
    risk_information: Option<ClientRiskInformation>,
    token_information: Option<CybersourceTokenInformation>,
    error_information: Option<CybersourceErrorInformation>,
}


pub struct HelcimRefundRequest {
    amount: FloatMajorUnit,
    original_transaction_id: u64,
    ip_address: Secret<String, IpAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}