File: crates/hyperswitch_connectors/src/connectors/globalpay/transformers.rs
 Public structs: 3

use common_utils::{
    crypto::{self, GenerateDigest},
    errors::ParsingError,
    pii,
    request::Method,
    types::{AmountConvertor, MinorUnit, StringMinorUnit, StringMinorUnitForConnector},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data,
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData},
    router_flow_types::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{
        MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsSyncRouterData, RefreshTokenRouterData, RefundExecuteRouterData, RefundsRouterData,
        TokenizationRouterData,
    },
};
use hyperswitch_interfaces::{
    consts::{self, NO_ERROR_MESSAGE},
    errors,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use rand::distributions::DistString;
use serde::{Deserialize, Serialize};
use url::Url;

use super::{
    requests::{
        self, ApmProvider, GlobalPayRouterData, GlobalpayCancelRouterData,
        GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest, Initiator, PaymentMethodData,
        Sequence, StoredCredential,
    },
    response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse, GlobalpayRefreshTokenResponse},
};
use crate::{
    connectors::globalpay::{
        requests::CommonPaymentMethodData, response::GlobalpayPaymentMethodsResponse,
    },
    types::{PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, construct_captures_response_hashmap, CardData, ForeignTryFrom,
        MultipleCaptureSyncResponse, PaymentMethodTokenizationRequestData,
        PaymentsAuthorizeRequestData, RouterData as _, WalletData,
    },
};

impl<T> From<(StringMinorUnit, T)> for GlobalPayRouterData<T> {
    fn from((amount, item): (StringMinorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

impl<T> From<(Option<StringMinorUnit>, T)> for GlobalpayCancelRouterData<T> {
    fn from((amount, item): (Option<StringMinorUnit>, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

type Error = error_stack::Report<errors::ConnectorError>;

#[derive(Debug, Serialize, Deserialize)]
pub struct GlobalPayMeta {
    account_name: Secret<String>,
}

impl TryFrom<&Option<pii::SecretSerdeValue>> for GlobalPayMeta {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(meta_data: &Option<pii::SecretSerdeValue>) -> Result<Self, Self::Error> {
        let metadata: Self = utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "metadata",
            })?;
        Ok(metadata)
    }
}

impl TryFrom<&GlobalPayRouterData<&PaymentsAuthorizeRouterData>> for GlobalpayPaymentsRequest {
    type Error = Error;

    fn try_from(
        item: &GlobalPayRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if item.router_data.is_three_ds() {
            return Err(errors::ConnectorError::NotSupported {
                message: "3DS flow".to_string(),
                connector: "Globalpay",
            }
            .into());
        }

        let metadata = GlobalPayMeta::try_from(&item.router_data.connector_meta_data)?;
        let account_name = metadata.account_name;

        let (initiator, stored_credential, connector_mandate_id) =
            if item.router_data.request.is_mandate_payment() {
                let connector_mandate_id =
                    item.router_data
                        .request
                        .mandate_id
                        .as_ref()
                        .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id {
                            Some(api_models::payments::MandateReferenceId::ConnectorMandateId(
                                connector_mandate_ids,
                            )) => connector_mandate_ids.get_connector_mandate_id(),
                            _ => None,
                        });

                let initiator = Some(match item.router_data.request.off_session {
                    Some(true) => Initiator::Merchant,
                    _ => Initiator::Payer,
                });

                let stored_credential = Some(StoredCredential {
                    model: Some(if connector_mandate_id.is_some() {
                        requests::Model::Recurring
                    } else {
                        requests::Model::Unscheduled
                    }),
                    sequence: Some(if connector_mandate_id.is_some() {
                        Sequence::Subsequent
                    } else {
                        Sequence::First
                    }),
                });

                (initiator, stored_credential, connector_mandate_id)
            } else {
                (None, None, None)
            };

        let payment_method = match &item.router_data.request.payment_method_data {
            payment_method_data::PaymentMethodData::Card(ccard) => {
                requests::GlobalPayPaymentMethodData::Common(CommonPaymentMethodData {
                    payment_method_data: PaymentMethodData::Card(requests::Card {
                        number: ccard.card_number.clone(),
                        expiry_month: ccard.card_exp_month.clone(),
                        expiry_year: ccard.get_card_expiry_year_2_digit()?,
                        cvv: ccard.card_cvc.clone(),
                    }),
                    entry_mode: Default::default(),
                })
            }

            payment_method_data::PaymentMethodData::Wallet(wallet_data) => {
                requests::GlobalPayPaymentMethodData::Common(CommonPaymentMethodData {
                    payment_method_data: get_wallet_data(wallet_data)?,
                    entry_mode: Default::default(),
                })
            }

            payment_method_data::PaymentMethodData::BankRedirect(bank_redirect) => {
                requests::GlobalPayPaymentMethodData::Common(CommonPaymentMethodData {
                    payment_method_data: PaymentMethodData::try_from(bank_redirect)?,
                    entry_mode: Default::default(),
                })
            }

            payment_method_data::PaymentMethodData::MandatePayment => {
                requests::GlobalPayPaymentMethodData::Mandate(requests::MandatePaymentMethodData {
                    entry_mode: Default::default(),
                    id: connector_mandate_id,
                })
            }

            _ => Err(errors::ConnectorError::NotImplemented(
                "Payment methods".to_string(),
            ))?,
        };

        Ok(Self {
            account_name,
            amount: Some(item.amount.to_owned()),
            currency: item.router_data.request.currency.to_string(),
            reference: item.router_data.connector_request_reference_id.to_string(),
            country: item.router_data.get_billing_country()?,
            capture_mode: Some(requests::CaptureMode::from(
                item.router_data.request.capture_method,
            )),
            payment_method,
            notifications: Some(requests::Notifications {
                return_url: get_return_url(item.router_data),
                status_url: item.router_data.request.webhook_url.clone(),
                cancel_url: get_return_url(item.router_data),
            }),
            stored_credential,
            channel: Default::default(),
            initiator,
        })
    }
}

impl TryFrom<&GlobalPayRouterData<&PaymentsCaptureRouterData>>
    for requests::GlobalpayCaptureRequest
{
    type Error = Error;
    fn try_from(
        value: &GlobalPayRouterData<&PaymentsCaptureRouterData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: Some(value.amount.to_owned()),
            capture_sequence: value
                .router_data
                .request
                .multiple_capture_data
                .clone()
                .map(|mcd| {
                    if mcd.capture_sequence == 1 {
                        Sequence::First
                    } else {
                        Sequence::Subsequent
                    }
                }),
            reference: value
                .router_data
                .request
                .multiple_capture_data
                .as_ref()
                .map(|mcd| mcd.capture_reference.clone()),
        })
    }
}

impl TryFrom<&TokenizationRouterData> for requests::GlobalPayPaymentMethodsRequest {
    type Error = Error;
    fn try_from(item: &TokenizationRouterData) -> Result<Self, Self::Error> {
        if !item.request.is_mandate_payment() {
            return Err(errors::ConnectorError::FlowNotSupported {
                flow: "Tokenization apart from Mandates".to_string(),
                connector: "Globalpay".to_string(),
            }
            .into());
        }
        Ok(Self {
            reference: item.connector_request_reference_id.clone(),
            usage_mode: Some(requests::UsageMode::Multiple),
            card: match &item.request.payment_method_data {
                payment_method_data::PaymentMethodData::Card(card_data) => Some(requests::Card {
                    number: card_data.card_number.clone(),
                    expiry_month: card_data.card_exp_month.clone(),
                    expiry_year: card_data.get_card_expiry_year_2_digit()?,
                    cvv: card_data.card_cvc.clone(),
                }),
                _ => None,
            },
        })
    }
}

impl TryFrom<&GlobalpayCancelRouterData<&PaymentsCancelRouterData>>
    for requests::GlobalpayCancelRequest
{
    type Error = Error;
    fn try_from(
        value: &GlobalpayCancelRouterData<&PaymentsCancelRouterData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: value.amount.clone(),
        })
    }
}

pub struct GlobalpayAuthType {
    pub app_id: Secret<String>,
    pub key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for GlobalpayAuthType {
    type Error = Error;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
                app_id: key1.to_owned(),
                key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

impl TryFrom<GlobalpayRefreshTokenResponse> for AccessToken {
    type Error = error_stack::Report<ParsingError>;

    fn try_from(item: GlobalpayRefreshTokenResponse) -> Result<Self, Self::Error> {
        Ok(Self {
            token: item.token,
            expires: item.seconds_to_expire,
        })
    }
}

impl TryFrom<&RefreshTokenRouterData> for GlobalpayRefreshTokenRequest {
    type Error = Error;

    fn try_from(item: &RefreshTokenRouterData) -> Result<Self, Self::Error> {
        let globalpay_auth = GlobalpayAuthType::try_from(&item.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)
            .attach_printable("Could not convert connector_auth to globalpay_auth")?;

        let nonce = rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
        let nonce_with_api_key = format!("{}{}", nonce, globalpay_auth.key.peek());
        let secret_vec = crypto::Sha512
            .generate_digest(nonce_with_api_key.as_bytes())
            .change_context(errors::ConnectorError::RequestEncodingFailed)
            .attach_printable("error creating request nonce")?;

        let secret = hex::encode(secret_vec);

        Ok(Self {
            app_id: globalpay_auth.app_id,
            nonce: Secret::new(nonce),
            secret: Secret::new(secret),
            grant_type: "client_credentials".to_string(),
        })
    }
}

impl From<GlobalpayPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: GlobalpayPaymentStatus) -> Self {
        match item {
            GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Charged,
            GlobalpayPaymentStatus::Declined | GlobalpayPaymentStatus::Rejected => Self::Failure,
            GlobalpayPaymentStatus::Preauthorized => Self::Authorized,
            GlobalpayPaymentStatus::Reversed => Self::Voided,
            GlobalpayPaymentStatus::Initiated => Self::AuthenticationPending,
            GlobalpayPaymentStatus::Pending => Self::Pending,
        }
    }
}

impl From<GlobalpayPaymentStatus> for common_enums::RefundStatus {
    fn from(item: GlobalpayPaymentStatus) -> Self {
        match item {
            GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Success,
            GlobalpayPaymentStatus::Declined | GlobalpayPaymentStatus::Rejected => Self::Failure,
            GlobalpayPaymentStatus::Initiated | GlobalpayPaymentStatus::Pending => Self::Pending,
            _ => Self::Pending,
        }
    }
}

impl From<Option<common_enums::CaptureMethod>> for requests::CaptureMode {
    fn from(capture_method: Option<common_enums::CaptureMethod>) -> Self {
        match capture_method {
            Some(common_enums::CaptureMethod::Manual) => Self::Later,
            Some(common_enums::CaptureMethod::ManualMultiple) => Self::Multiple,
            _ => Self::Auto,
        }
    }
}

// }

impl<F, T> TryFrom<ResponseRouterData<F, GlobalpayPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = Error;

    fn try_from(
        item: ResponseRouterData<F, GlobalpayPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = common_enums::AttemptStatus::from(item.response.status);

        let redirect_url = item
            .response
            .payment_method
            .as_ref()
            .and_then(|payment_method| {
                payment_method
                    .apm
                    .as_ref()
                    .and_then(|apm| apm.redirect_url.as_ref())
            })
            .filter(|redirect_str| !redirect_str.is_empty())
            .map(|url| {
                Url::parse(url).change_context(errors::ConnectorError::FailedToObtainIntegrationUrl)
            })
            .transpose()?;

        let redirection_data = redirect_url.map(|url| RedirectForm::from((url, Method::Get)));
        let payment_method_token = item.data.get_payment_method_token();
        let status_code = item.http_code;

        let mandate_reference = payment_method_token.ok().and_then(|token| match token {
            PaymentMethodToken::Token(token_string) => Some(MandateReference {
                connector_mandate_id: Some(token_string.clone().expose()),
                payment_method_id: None,
                mandate_metadata: None,
                connector_mandate_request_reference_id: None,
            }),
            _ => None,
        });

        let response = match status {
            common_enums::AttemptStatus::Failure => Err(Box::new(ErrorResponse {
                message: item
                    .response
                    .payment_method
                    .as_ref()
                    .and_then(|payment_method| payment_method.message.clone())
                    .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                code: item
                    .response
                    .payment_method
                    .as_ref()
                    .and_then(|payment_method| payment_method.result.clone())
                    .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
                reason: item
                    .response
                    .payment_method
                    .as_ref()
                    .and_then(|payment_method| payment_method.message.clone()),
                status_code,
                attempt_status: Some(status),
                connector_transaction_id: Some(item.response.id.clone()),
                network_decline_code: item
                    .response
                    .payment_method
                    .as_ref()
                    .and_then(|payment_method| payment_method.result.clone()),
                network_advice_code: None,
                network_error_message: item
                    .response
                    .payment_method
                    .as_ref()
                    .and_then(|payment_method| payment_method.message.clone()),
                connector_metadata: None,
            })),
            _ => Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(mandate_reference),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item.response.reference.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
        };

        Ok(Self {
            status,
            response: response.map_err(|err| *err),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, GlobalpayPaymentMethodsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<F, GlobalpayPaymentMethodsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let token = item
            .response
            .payment_method_token_id
            .clone()
            .unwrap_or_default();

        Ok(Self {
            response: Ok(PaymentsResponseData::TokenizationResponse {
                token: token.expose(),
            }),
            ..item.data
        })
    }
}

impl
    ForeignTryFrom<(
        PaymentsSyncResponseRouterData<GlobalpayPaymentsResponse>,
        bool,
    )> for PaymentsSyncRouterData
{
    type Error = Error;

    fn foreign_try_from(
        (value, is_multiple_capture_sync): (
            PaymentsSyncResponseRouterData<GlobalpayPaymentsResponse>,
            bool,
        ),
    ) -> Result<Self, Self::Error> {
        if is_multiple_capture_sync {
            let capture_sync_response_list =
                construct_captures_response_hashmap(vec![value.response])?;
            Ok(Self {
                response: Ok(PaymentsResponseData::MultipleCaptureResponse {
                    capture_sync_response_list,
                }),
                ..value.data
            })
        } else {
            Self::try_from(value)
        }
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, GlobalpayRefreshTokenResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<ParsingError>;
    fn try_from(
        item: ResponseRouterData<F, GlobalpayRefreshTokenResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(AccessToken {
                token: item.response.token,
                expires: item.response.seconds_to_expire,
            }),
            ..item.data
        })
    }
}

impl<F> TryFrom<&GlobalPayRouterData<&RefundsRouterData<F>>> for requests::GlobalpayRefundRequest {
    type Error = Error;
    fn try_from(item: &GlobalPayRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount.to_owned(),
        })
    }
}

impl TryFrom<RefundsResponseRouterData<Execute, GlobalpayPaymentsResponse>>
    for RefundExecuteRouterData
{
    type Error = Error;
    fn try_from(
        item: RefundsResponseRouterData<Execute, GlobalpayPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id,
                refund_status: common_enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, GlobalpayPaymentsResponse>>
    for RefundsRouterData<RSync>
{
    type Error = Error;
    fn try_from(
        item: RefundsResponseRouterData<RSync, GlobalpayPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id,
                refund_status: common_enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct GlobalpayErrorResponse {
    pub error_code: String,
    pub detailed_error_code: String,
    pub detailed_error_description: String,
}

fn get_return_url(item: &PaymentsAuthorizeRouterData) -> Option<String> {
    match item.request.payment_method_data.clone() {
        payment_method_data::PaymentMethodData::Wallet(
            payment_method_data::WalletData::PaypalRedirect(_),
        ) => {
            // Return URL handling for PayPal via Globalpay:
            // - PayPal inconsistency: Return URLs work with HTTP, but cancel URLs require HTTPS
            // - Local development: When testing locally, expose your server via HTTPS and replace
            //   the base URL with an HTTPS URL to ensure proper cancellation flow
            // - Refer to commit 6499d429da87 for more information
            item.request.complete_authorize_url.clone()
        }
        _ => item.request.router_return_url.clone(),
    }
}

fn get_wallet_data(
    wallet_data: &payment_method_data::WalletData,
) -> Result<PaymentMethodData, Error> {
    match wallet_data {
        payment_method_data::WalletData::PaypalRedirect(_) => {
            Ok(PaymentMethodData::Apm(requests::Apm {
                provider: Some(ApmProvider::Paypal),
            }))
        }
        payment_method_data::WalletData::GooglePay(_) => {
            Ok(PaymentMethodData::DigitalWallet(requests::DigitalWallet {
                provider: Some(requests::DigitalWalletProvider::PayByGoogle),
                payment_token: wallet_data.get_wallet_token_as_json("Google Pay".to_string())?,
            }))
        }
        _ => Err(errors::ConnectorError::NotImplemented(
            "Payment method".to_string(),
        ))?,
    }
}

impl TryFrom<&payment_method_data::BankRedirectData> for PaymentMethodData {
    type Error = Error;
    fn try_from(value: &payment_method_data::BankRedirectData) -> Result<Self, Self::Error> {
        match value {
            payment_method_data::BankRedirectData::Eps { .. } => Ok(Self::Apm(requests::Apm {
                provider: Some(ApmProvider::Eps),
            })),
            payment_method_data::BankRedirectData::Giropay { .. } => Ok(Self::Apm(requests::Apm {
                provider: Some(ApmProvider::Giropay),
            })),
            payment_method_data::BankRedirectData::Ideal { .. } => Ok(Self::Apm(requests::Apm {
                provider: Some(ApmProvider::Ideal),
            })),
            payment_method_data::BankRedirectData::Sofort { .. } => Ok(Self::Apm(requests::Apm {
                provider: Some(ApmProvider::Sofort),
            })),
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

impl MultipleCaptureSyncResponse for GlobalpayPaymentsResponse {
    fn get_connector_capture_id(&self) -> String {
        self.id.clone()
    }

    fn get_capture_attempt_status(&self) -> common_enums::AttemptStatus {
        common_enums::AttemptStatus::from(self.status)
    }

    fn is_capture_response(&self) -> bool {
        true
    }

    fn get_amount_captured(&self) -> Result<Option<MinorUnit>, error_stack::Report<ParsingError>> {
        match self.amount.clone() {
            Some(amount) => {
                let minor_amount = StringMinorUnitForConnector::convert_back(
                    &StringMinorUnitForConnector,
                    amount,
                    self.currency.unwrap_or_default(), //it is ignored in convert_back function
                )?;
                Ok(Some(minor_amount))
            }
            None => Ok(None),
        }
    }
    fn get_connector_reference_id(&self) -> Option<String> {
        self.reference.clone()
    }
}


pub struct AuthorizedotnetWebhookPayload {
    pub id: Option<String>,
}


pub fn collect(self) -> ActivePaymentsMetricsBucketValue {
        ActivePaymentsMetricsBucketValue {
            active_payments: self.active_payments.collect(),
        }
    }


- **core:** Fix failing token based MIT payments ([#4735](https://github.com/juspay/hyperswitch/pull/4735)) ([`1bd4061`](https://github.com/juspay/hyperswitch/commit/1bd406197b5baf1c041f0dffa5bc02dce10f1529))
- Added hget lookup for all updated_by existing cases ([#4716](https://github.com/juspay/hyperswitch/pull/4716)) ([`fabf80c`](https://github.com/juspay/hyperswitch/commit/fabf80c2b18ca690b7fb709c8c12d1ef7f24e5b6))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`ec50843`](https://github.com/juspay/hyperswitch/commit/ec508435a19c2942a5d66757a74dd06bed5b1a76))

**Full Changelog:** [`2024.05.22.0...2024.05.23.0`](https://github.com/juspay/hyperswitch/compare/2024.05.22.0...2024.05.23.0)

- - -

## 2024.05.22.0

### Features

- **core:** Add support for connectors having separate version call for pre authentication ([#4603](https://github.com/juspay/hyperswitch/pull/4603)) ([`528d692`](https://github.com/juspay/hyperswitch/commit/528d692a89f5cf9a82d1e5c28e4b3a1ef4bf6c6a))

### Refactors

- **graph:** Refactor the Knowledge Graph to include configs check, while eligibility analysis ([#4687](https://github.com/juspay/hyperswitch/pull/4687)) ([`a917776`](https://github.com/juspay/hyperswitch/commit/a917776bb8cd294f77c569e81ea4d665b6611c6d))

### Miscellaneous Tasks

- Move tracing to workspace deps and remove router_env as a dependency of redis_interface ([#4717](https://github.com/juspay/hyperswitch/pull/4717)) ([`fea2ea6`](https://github.com/juspay/hyperswitch/commit/fea2ea6d2cf4f3f68e4779e53b82120806748d7b))

**Full Changelog:** [`2024.05.21.1...2024.05.22.0`](https://github.com/juspay/hyperswitch/compare/2024.05.21.1...2024.05.22.0)

- - -

## 2024.05.21.1

### Features

- **Cypress:** Add response handler for Connector Testing ([#4624](https://github.com/juspay/hyperswitch/pull/4624)) ([`2e79ee0`](https://github.com/juspay/hyperswitch/commit/2e79ee0615292182111586fda7655dd9a796ef4f))
- **constraint_graph:** Add visualization functionality to the constraint graph ([#4701](https://github.com/juspay/hyperswitch/pull/4701)) ([`0f53f74`](https://github.com/juspay/hyperswitch/commit/0f53f74d26e829602519998c41a460dc9a4809af))

### Refactors

- **core:** Add support to enable pm_data and pm_id in payments response ([#4711](https://github.com/juspay/hyperswitch/pull/4711)) ([`2cd360e`](https://github.com/juspay/hyperswitch/commit/2cd360e6a9d6bbe4b91f7b501b6013db1f31d898))
- **router:** Added a new type minor unit to amount ([#4629](https://github.com/juspay/hyperswitch/pull/4629)) ([`443b7e6`](https://github.com/juspay/hyperswitch/commit/443b7e6ea2cf63f35a28a1cd24860399d96b15ba))

**Full Changelog:** [`2024.05.21.0...2024.05.21.1`](https://github.com/juspay/hyperswitch/compare/2024.05.21.0...2024.05.21.1)

- - -

## 2024.05.21.0

### Features

- **core:** Add a new endpoint for Complete Authorize flow ([#4686](https://github.com/juspay/hyperswitch/pull/4686)) ([`226c337`](https://github.com/juspay/hyperswitch/commit/226c337399a2e4c1fa50c4f3d0d4b237b5543426))

### Bug Fixes

- **router:** Handle connector authentication technical failures and skip confirm in authorize flow only when authentication_type is not challenge ([#4667](https://github.com/juspay/hyperswitch/pull/4667)) ([`842728e`](https://github.com/juspay/hyperswitch/commit/842728ef93241643d12170695ddf56cee4da45bd))

### Refactors

- **cache:** Remove `deref` impl on `Cache` type ([#4671](https://github.com/juspay/hyperswitch/pull/4671)) ([`36409bd`](https://github.com/juspay/hyperswitch/commit/36409bdc9185d4241971a30c55e1e331568abd2f))

### Documentation

- Update Docker Compose setup guide to checkout `latest` tag ([#4695](https://github.com/juspay/hyperswitch/pull/4695)) ([`40f6776`](https://github.com/juspay/hyperswitch/commit/40f6776c46abc4b9c89fb2aa195f4ce64b312cf6))

### Miscellaneous Tasks

- **docker-compose:** Specify `pull_policy` for hyperswitch services ([#4688](https://github.com/juspay/hyperswitch/pull/4688)) ([`909e75c`](https://github.com/juspay/hyperswitch/commit/909e75c71a6e3418b5d15396569d986eff852c06))

**Full Changelog:** [`2024.05.20.2...2024.05.21.0`](https://github.com/juspay/hyperswitch/compare/2024.05.20.2...2024.05.21.0)

- - -

## 2024.05.20.2

### Features

- Add an api for toggle KV for all merchants ([#4600](https://github.com/juspay/hyperswitch/pull/4600)) ([`7f53461`](https://github.com/juspay/hyperswitch/commit/7f5346169edc4266b7b08578aac7aef1ede630f3))

**Full Changelog:** [`2024.05.20.1...2024.05.20.2`](https://github.com/juspay/hyperswitch/compare/2024.05.20.1...2024.05.20.2)

- - -

## 2024.05.20.1

### Features

- Soft kill kv ([#4582](https://github.com/juspay/hyperswitch/pull/4582)) ([`3fa59d4`](https://github.com/juspay/hyperswitch/commit/3fa59d4bac01de8fa25e28340a57e578d9980032))

**Full Changelog:** [`2024.05.20.0...2024.05.20.1`](https://github.com/juspay/hyperswitch/compare/2024.05.20.0...2024.05.20.1)

- - -

## 2024.05.20.0

### Features

- Added client_source, client_version in payment_attempt from payments confirm request headers ([#4657](https://github.com/juspay/hyperswitch/pull/4657)) ([`7e44bbc`](https://github.com/juspay/hyperswitch/commit/7e44bbca63c1818c0fabdf2734d9b0ae5d639fe1))

### Bug Fixes

- **docker:** Fix stack overflow for docker images ([#4660](https://github.com/juspay/hyperswitch/pull/4660)) ([`a62f69d`](https://github.com/juspay/hyperswitch/commit/a62f69d447245273c73611309055d2341a47b783))
- Address non-digit character cases in card number validation ([#4649](https://github.com/juspay/hyperswitch/pull/4649)) ([`8c0d72e`](https://github.com/juspay/hyperswitch/commit/8c0d72e225c56b7bece733d9565fc8774deaa490))

### Refactors

- **FRM:** Refactor frm configs ([#4581](https://github.com/juspay/hyperswitch/pull/4581)) ([`853f3b4`](https://github.com/juspay/hyperswitch/commit/853f3b4854ff9ec1e169b7633f1e9bf8259e9ceb))

**Full Changelog:** [`2024.05.17.0...2024.05.20.0`](https://github.com/juspay/hyperswitch/compare/2024.05.17.0...2024.05.20.0)

- - -

## 2024.05.17.0

### Bug Fixes

- **core:** Use `realip_remote_addr` function to extract ip address ([#4653](https://github.com/juspay/hyperswitch/pull/4653)) ([`8427b60`](https://github.com/juspay/hyperswitch/commit/8427b60a1851f2d9d2f141f28eb122d42f680736))
- **recon:** Make recon status optional in merchant account ([#4654](https://github.com/juspay/hyperswitch/pull/4654)) ([`84cb2bc`](https://github.com/juspay/hyperswitch/commit/84cb2bcb6bbb82f54315c82c7421a222d2e37bc6))

### Refactors

- **access_token:** Handle network delays with expiry of access token ([#4617](https://github.com/juspay/hyperswitch/pull/4617)) ([`0d45f85`](https://github.com/juspay/hyperswitch/commit/0d45f854a2cc18cc421a3d449a6dc2c830ef9dd5))
- **cards,router:** Remove duplicated card number interface ([#4404](https://github.com/juspay/hyperswitch/pull/4404)) ([`27ae437`](https://github.com/juspay/hyperswitch/commit/27ae437a88492bf5b17ad2fbf4a083891602c07a))

### Miscellaneous Tasks

- Add deprecated flag to soon to be deprecated fields in payment request and response ([#4261](https://github.com/juspay/hyperswitch/pull/4261)) ([`9ac5d70`](https://github.com/juspay/hyperswitch/commit/9ac5d70e2ed0a036b5f2bfe7488f218b83fce7c3))

**Full Changelog:** [`2024.05.16.1...2024.05.17.0`](https://github.com/juspay/hyperswitch/compare/2024.05.16.1...2024.05.17.0)

- - -

## 2024.05.16.1

### Features

- **middleware:** Log content_length for 4xx ([#4655](https://github.com/juspay/hyperswitch/pull/4655)) ([`4b5b558`](https://github.com/juspay/hyperswitch/commit/4b5b558dae8d2fefb66b8b16c486f07e3e800758))

### Refactors

- **session_flow:** Remove the shipping and billing parameter fields if null for apple pay and google pay ([#4661](https://github.com/juspay/hyperswitch/pull/4661)) ([`0dee53e`](https://github.com/juspay/hyperswitch/commit/0dee53ecb2d5203285a819bc8e71111d2c133f03))

**Full Changelog:** [`2024.05.16.0...2024.05.16.1`](https://github.com/juspay/hyperswitch/compare/2024.05.16.0...2024.05.16.1)

- - -

## 2024.05.16.0

### Features

- **core:** Move RouterData to crate hyperswitch_domain_models ([#4524](https://github.com/juspay/hyperswitch/pull/4524)) ([`ff1c2dd`](https://github.com/juspay/hyperswitch/commit/ff1c2ddf8b9d8f35deee1ab41c2286cc5b349271))

### Bug Fixes

- **connector:** Accept state abbreviation in 2 letter ([#4646](https://github.com/juspay/hyperswitch/pull/4646)) ([`3cf840e`](https://github.com/juspay/hyperswitch/commit/3cf840e48678e56a443bc891c48589d4b53bc07a))
- **router:** Add `max_amount` validation in payment flows ([#4645](https://github.com/juspay/hyperswitch/pull/4645)) ([`df865d7`](https://github.com/juspay/hyperswitch/commit/df865d76be1c867b9ee4d9cbb92a98dca4ecf229))

### Refactors

- **bank-redirect:** Remove billing from bankredirect payment data ([#4362](https://github.com/juspay/hyperswitch/pull/4362)) ([`0958d94`](https://github.com/juspay/hyperswitch/commit/0958d948f98bc41df64d8ea18cb1a8d3a0eb80fe))
- **db:** Add TenantID field to KafkaEvent struct ([#4598](https://github.com/juspay/hyperswitch/pull/4598)) ([`24214bc`](https://github.com/juspay/hyperswitch/commit/24214bcfcd0a34acd39dba88f6c015ac6b1edbc4))
- **router:** Remove default case handling in bambora connector ([#4473](https://github.com/juspay/hyperswitch/pull/4473)) ([`1a27ba5`](https://github.com/juspay/hyperswitch/commit/1a27ba576427126cc6a3fe2be86489abc9af63d8))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`f2ff7a2`](https://github.com/juspay/hyperswitch/commit/f2ff7a211b9f7ca16352061768e9b7c0a38a3845))

**Full Changelog:** [`2024.05.15.0...2024.05.16.0`](https://github.com/juspay/hyperswitch/compare/2024.05.15.0...2024.05.16.0)

- - -

## 2024.05.15.0

### Features

- **payment_methods:** Pass required shipping details field for wallets session call based on `business_profile` config ([#4616](https://github.com/juspay/hyperswitch/pull/4616)) ([`650f3fa`](https://github.com/juspay/hyperswitch/commit/650f3fa25c4130a2148862863ff444d16b41d2f3))
- **router:** Send `openurl_if_required` post_message in external 3ds flow for return_url redirection from sdk ([#4642](https://github.com/juspay/hyperswitch/pull/4642)) ([`bf06a5b`](https://github.com/juspay/hyperswitch/commit/bf06a5b51161365af7a3570a986455fefdf2c61b))

### Bug Fixes

- **config:** Include gpayments base url in deployment config files ([#4637](https://github.com/juspay/hyperswitch/pull/4637)) ([`03ed6dc`](https://github.com/juspay/hyperswitch/commit/03ed6dc0d6abc06ecfbbffe3111581fb4a0754da))

### Refactors

- **connector:** [BOA/CYBS] refund error handling ([#4632](https://github.com/juspay/hyperswitch/pull/4632)) ([`99702ed`](https://github.com/juspay/hyperswitch/commit/99702ed8f99cb03fc4452c067131aebf368de054))
- **payment_methods:** Update api contract for update payment method endpoint ([#4641](https://github.com/juspay/hyperswitch/pull/4641)) ([`e43ae65`](https://github.com/juspay/hyperswitch/commit/e43ae653a02cf453f8492630819e505c1f529f47))
- Remove `Ctx` generic from payments core ([#4574](https://github.com/juspay/hyperswitch/pull/4574)) ([`6b509c7`](https://github.com/juspay/hyperswitch/commit/6b509c7bec43fdd4332848498ce31023a26486e6))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`45b8814`](https://github.com/juspay/hyperswitch/commit/45b88140a2e43dfccfb5875a14dca5cd8b74b3fc))

**Full Changelog:** [`2024.05.14.0...2024.05.15.0`](https://github.com/juspay/hyperswitch/compare/2024.05.14.0...2024.05.15.0)

- - -

## 2024.05.14.0

### Features

- **connector:** Generate connector template code for gpayments authenticaition connector ([#4584](https://github.com/juspay/hyperswitch/pull/4584)) ([`2a302eb`](https://github.com/juspay/hyperswitch/commit/2a302eb5973c64d8b77f8110fdbeb536ccbe1488))
- **payment_methods:** Pass `required_billing_contact_fields` field in `/session` call based on dynamic fields ([#4601](https://github.com/juspay/hyperswitch/pull/4601)) ([`348cd74`](https://github.com/juspay/hyperswitch/commit/348cd744dca20c54c6ed47c8036f43f16429c8f3))
- **payments_update:** Update payment_method_billing in payment update ([#4614](https://github.com/juspay/hyperswitch/pull/4614)) ([`2692995`](https://github.com/juspay/hyperswitch/commit/26929956172e9f0e1e3fb41f5e4dbb19d866abf2))
- **refunds:** Update refunds filters ([#4409](https://github.com/juspay/hyperswitch/pull/4409)) ([`cfab2af`](https://github.com/juspay/hyperswitch/commit/cfab2af7d4a2478d7609a1bd34dd0579dad194c2))

### Bug Fixes

- **connector_token:** Move config redis ([#4540](https://github.com/juspay/hyperswitch/pull/4540)) ([`1602eb5`](https://github.com/juspay/hyperswitch/commit/1602eb541d317d9b155cbcbffed3d54f7d0b5acd))

### Refactors

- **bank-transfer:** Remove billing from banktransfer payment data ([#4377](https://github.com/juspay/hyperswitch/pull/4377)) ([`0f5a370`](https://github.com/juspay/hyperswitch/commit/0f5a370b55140fd63aeab4ca8427bd371f5e5ec4))
- **card_details:** Added missing card data fields for connectors ([#4571](https://github.com/juspay/hyperswitch/pull/4571)) ([`41655ba`](https://github.com/juspay/hyperswitch/commit/41655ba300567455a5b28b85584d990981a24167))

### Miscellaneous Tasks

- **postman:** Update Postman collection files ([`22210b0`](https://github.com/juspay/hyperswitch/commit/22210b0ff44014b885842c804879267d9a83ab1b))

**Full Changelog:** [`2024.05.13.0...2024.05.14.0`](https://github.com/juspay/hyperswitch/compare/2024.05.13.0...2024.05.14.0)

- - -

## 2024.05.13.0

### Features

- **Connectors:** Add mandate validation for auth flow ([#4089](https://github.com/juspay/hyperswitch/pull/4089)) ([`fef28c3`](https://github.com/juspay/hyperswitch/commit/fef28c3345ae60046f46a2bdf9eca6b38278d75b))
- **analytics:** Authentication analytics ([#4429](https://github.com/juspay/hyperswitch/pull/4429)) ([`24d1542`](https://github.com/juspay/hyperswitch/commit/24d154248c8814e729206208f096aba68dcff8c0))

### Bug Fixes

- **connector:** [BOA/CYBS] add cancelled status to refund response ([#4620](https://github.com/juspay/hyperswitch/pull/4620)) ([`cf0e3da`](https://github.com/juspay/hyperswitch/commit/cf0e3daeaa1dfdfa00d4cccdff5b845ac368bcb9))
- **router:** Fix QR data into image conversion ([#4619](https://github.com/juspay/hyperswitch/pull/4619)) ([`28ab368`](https://github.com/juspay/hyperswitch/commit/28ab36873b2a475f1de95819b3d81aae954a2cfc))

### Refactors

- **payment_method_data:** Send optional billing details in response ([#4569](https://github.com/juspay/hyperswitch/pull/4569)) ([`86e0550`](https://github.com/juspay/hyperswitch/commit/86e05501cbea53fd85e2bc67a1c2be4cba47d0ff))

**Full Changelog:** [`2024.05.10.0...2024.05.13.0`](https://github.com/juspay/hyperswitch/compare/2024.05.10.0...2024.05.13.0)

- - -

## 2024.05.10.0

### Features

- **connector:** [Payone] add connector template code ([#4469](https://github.com/juspay/hyperswitch/pull/4469)) ([`f386f42`](https://github.com/juspay/hyperswitch/commit/f386f423c0e5fac55a24756d7ee7a3ce1c20fb13))
- **users:**
  - Create API to Verify TOTP ([#4597](https://github.com/juspay/hyperswitch/pull/4597)) ([`9135423`](https://github.com/juspay/hyperswitch/commit/91354232e03a8dbd9ad9eccc8620eac321765dd7))
  - New routes to accept invite and list merchants ([#4591](https://github.com/juspay/hyperswitch/pull/4591)) ([`e70d58a`](https://github.com/juspay/hyperswitch/commit/e70d58afc941d436aae0aaa683c2e8b5db2ade33))

### Bug Fixes

- **connector:**
  - [iatapay]handle empty error response in case of 401 ([#4291](https://github.com/juspay/hyperswitch/pull/4291)) ([`d1404d9`](https://github.com/juspay/hyperswitch/commit/d1404d9aff2aea513a2ffd422c7e10e760b7382c))
  - [BAMBORA] Audit Fixes for Bambora ([#4604](https://github.com/juspay/hyperswitch/pull/4604)) ([`366596f`](https://github.com/juspay/hyperswitch/commit/366596f14d6c874a8e2d418a99beb90046c5b040))
- **router:** [NETCETERA] skip sending browser_information in authentication request for app device_channel ([#4613](https://github.com/juspay/hyperswitch/pull/4613)) ([`d2a496c`](https://github.com/juspay/hyperswitch/commit/d2a496cf4ddab94efa5ad1127a94687d45bed027))
- **users:** Fix bugs caused by the new token only flows ([#4607](https://github.com/juspay/hyperswitch/pull/4607)) ([`a0f11d7`](https://github.com/juspay/hyperswitch/commit/a0f11d79add17e0bc19d8677c90f8a35d6c99c97))

### Refactors

- **billing:** Store `payment_method_data_billing` for recurring payments ([#4513](https://github.com/juspay/hyperswitch/pull/4513)) ([`55ae0fc`](https://github.com/juspay/hyperswitch/commit/55ae0fc5f704d8b35815fcd2170befb4a726ea8d))

**Full Changelog:** [`2024.05.09.0...2024.05.10.0`](https://github.com/juspay/hyperswitch/compare/2024.05.09.0...2024.05.10.0)

- - -

## 2024.05.09.0

### Features

- **business_profile:** Feature add a config to use `billing` as `payment_method_billing` ([#4557](https://github.com/juspay/hyperswitch/pull/4557)) ([`3e1c7eb`](https://github.com/juspay/hyperswitch/commit/3e1c7eba49de3110a2d71cea8e0540c7182d2058))
- **connector-configs:** [Cashtocode] add CNY currency for evoucher ([#4578](https://github.com/juspay/hyperswitch/pull/4578)) ([`c47cac8`](https://github.com/juspay/hyperswitch/commit/c47cac815792df865e416d5ffc6c46faf6662053))
- **users:** Create `user_key_store` table and `begin_totp` API ([#4577](https://github.com/juspay/hyperswitch/pull/4577)) ([`a97016f`](https://github.com/juspay/hyperswitch/commit/a97016fea41c3b74149d8eaa5c0271ec1347bc39))

### Bug Fixes

- **connector:** [BOA/CYBS] make rsync status optional ([#4570](https://github.com/juspay/hyperswitch/pull/4570)) ([`339da8b`](https://github.com/juspay/hyperswitch/commit/339da8b0c9a1e388b65ff5d82a162e758c85ec6b))
- **core:** Drop three_dsserver_trans_id from authentication table ([#4587](https://github.com/juspay/hyperswitch/pull/4587)) ([`ec3b60e`](https://github.com/juspay/hyperswitch/commit/ec3b60e37c0b178c3e5e3fe79db88f83fd195722))


OpenAPI Block Path: components.schemas.RetrievePaymentLinkRequest
{
  "type": "object",
  "properties": {
    "client_secret": {
      "type": "string",
      "description": "It's a token used for client side verification.",
      "nullable": true
    }
  }
}


pub struct ExternalVaultProxyPaymentsData {
    pub payment_method_data: ExternalVaultPaymentMethodData,
    /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount)
    /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately
    /// ```text
    /// get_original_amount()
    /// get_surcharge_amount()
    /// get_tax_on_surcharge_amount()
    /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount
    /// ```
    pub amount: i64,
    pub order_tax_amount: Option<MinorUnit>,
    pub email: Option<pii::Email>,
    pub customer_name: Option<Secret<String>>,
    pub currency: storage_enums::Currency,
    pub confirm: bool,
    pub statement_descriptor_suffix: Option<String>,
    pub statement_descriptor: Option<String>,
    pub capture_method: Option<storage_enums::CaptureMethod>,
    pub router_return_url: Option<String>,
    pub webhook_url: Option<String>,
    pub complete_authorize_url: Option<String>,
    // Mandates
    pub setup_future_usage: Option<storage_enums::FutureUsage>,
    pub mandate_id: Option<api_models::payments::MandateIds>,
    pub off_session: Option<bool>,
    pub customer_acceptance: Option<common_payments_types::CustomerAcceptance>,
    pub setup_mandate_details: Option<mandates::MandateData>,
    pub browser_info: Option<BrowserInformation>,
    pub order_details: Option<Vec<OrderDetailsWithAmount>>,
    pub order_category: Option<String>,
    pub session_token: Option<String>,
    pub enrolled_for_3ds: bool,
    pub related_transaction_id: Option<String>,
    pub payment_experience: Option<storage_enums::PaymentExperience>,
    pub payment_method_type: Option<storage_enums::PaymentMethodType>,
    pub surcharge_details: Option<SurchargeDetails>,
    pub customer_id: Option<id_type::CustomerId>,
    pub request_incremental_authorization: bool,
    pub metadata: Option<serde_json::Value>,
    pub authentication_data: Option<AuthenticationData>,
    pub request_extended_authorization:
        Option<common_types::primitive_wrappers::RequestExtendedAuthorizationBool>,
    pub split_payments: Option<common_types::payments::SplitPaymentsRequest>,

    // New amount for amount frame work
    pub minor_amount: MinorUnit,
    /// Merchant's identifier for the payment/invoice. This will be sent to the connector
    /// if the connector provides support to accept multiple reference ids.
    /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.
    pub merchant_order_reference_id: Option<String>,
    pub integrity_object: Option<AuthoriseIntegrityObject>,
    pub shipping_cost: Option<MinorUnit>,
    pub additional_payment_method_data: Option<AdditionalPaymentData>,
    pub merchant_account_id: Option<Secret<String>>,
    pub merchant_config_currency: Option<storage_enums::Currency>,
    pub connector_testing_data: Option<pii::SecretSerdeValue>,
    pub order_id: Option<String>,
}


pub struct PaymentMethodCollectLinkStatusDetails {
    pub pm_collect_link_id: String,
    pub customer_id: id_type::CustomerId,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub session_expiry: time::PrimitiveDateTime,
    pub return_url: Option<url::Url>,
    pub status: link_utils::PaymentMethodCollectStatus,
    #[serde(flatten)]
    pub ui_config: link_utils::GenericLinkUiConfigFormData,
}


pub struct BluesnapThreeDSecureInfo {
    three_d_secure_reference_id: String,
}


pub(crate) async fn payments_create_and_confirm_intent(
    state: SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    request: payments_api::PaymentsRequest,
    mut header_payload: HeaderPayload,
) -> RouterResponse<payments_api::PaymentsResponse> {
    use hyperswitch_domain_models::{
        payments::PaymentIntentData, router_flow_types::PaymentCreateIntent,
    };

    let payment_id = id_type::GlobalPaymentId::generate(&state.conf.cell_information.id);

    let payload = payments_api::PaymentsCreateIntentRequest::from(&request);

    let create_intent_response = Box::pin(payments_intent_core::<
        PaymentCreateIntent,
        payments_api::PaymentsIntentResponse,
        _,
        _,
        PaymentIntentData<PaymentCreateIntent>,
    >(
        state.clone(),
        req_state.clone(),
        merchant_context.clone(),
        profile.clone(),
        operations::PaymentIntentCreate,
        payload,
        payment_id.clone(),
        header_payload.clone(),
    ))
    .await?;

    logger::info!(?create_intent_response);

    let create_intent_response = create_intent_response
        .get_json_body()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unexpected response from payments core")?;

    let payload = payments_api::PaymentsConfirmIntentRequest::from(&request);

    let confirm_intent_response = decide_authorize_or_setup_intent_flow(
        state,
        req_state,
        merchant_context,
        profile,
        &create_intent_response,
        payload,
        payment_id,
        header_payload,
    )
    .await?;

    logger::info!(?confirm_intent_response);

    Ok(confirm_intent_response)
}


impl api::MandateSetup for Volt {}


pub fn server(state: AppState) -> Scope {
        web::scope("/payment_link")
            .app_data(web::Data::new(state))
            .service(web::resource("/list").route(web::post().to(payment_link::payments_link_list)))
            .service(
                web::resource("/{payment_link_id}")
                    .route(web::get().to(payment_link::payment_link_retrieve)),
            )
            .service(
                web::resource("{merchant_id}/{payment_id}")
                    .route(web::get().to(payment_link::initiate_payment_link)),
            )
            .service(
                web::resource("s/{merchant_id}/{payment_id}")
                    .route(web::get().to(payment_link::initiate_secure_payment_link)),
            )
            .service(
                web::resource("status/{merchant_id}/{payment_id}")
                    .route(web::get().to(payment_link::payment_link_status)),
            )
    }


pub struct BankOfAmericaConsumerAuthInformation {
    ucaf_collection_indicator: Option<String>,
    cavv: Option<String>,
    ucaf_authentication_data: Option<Secret<String>>,
    xid: Option<String>,
    directory_server_transaction_id: Option<Secret<String>>,
    specification_version: Option<String>,
}


pub struct RecoveryCodes(pub Vec<Secret<String>>);


pub async fn customers_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::GlobalCustomerId>,
    json_payload: web::Json<customers::CustomerUpdateRequest>,
) -> HttpResponse {
    let flow = Flow::CustomersUpdate;
    let id = path.into_inner();
    let request = json_payload.into_inner();
    let request_internal = customers::CustomerUpdateRequestInternal { id, request };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        request_internal,
        |state, auth: auth::AuthenticationData, request_internal, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            update_customer(state, merchant_context, request_internal)
        },
        auth::auth_type(
            &auth::V2ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            },
            &auth::JWTAuth {
                permission: Permission::MerchantCustomerWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


    #[strum(serialize = "AN")]
    Andalusia,
    #[strum(serialize = "VI")]
    ArabaAlava,
    #[strum(serialize = "AR")]
    Aragon,
    #[strum(serialize = "BA")]
    BadajozProvince,
    #[strum(serialize = "PM")]
    BalearicIslands,
    #[strum(serialize = "B")]
    BarcelonaProvince,
    #[strum(serialize = "PV")]
    BasqueCountry,
    #[strum(serialize = "BI")]
    Biscay,
    #[strum(serialize = "BU")]
    BurgosProvince,
    #[strum(serialize = "CN")]
    CanaryIslands,
    #[strum(serialize = "S")]
    Cantabria,
    #[strum(serialize = "CS")]
    CastellonProvince,
    #[strum(serialize = "CL")]
    CastileAndLeon,
    #[strum(serialize = "CM")]
    CastileLaMancha,
    #[strum(serialize = "CT")]
    Catalonia,
    #[strum(serialize = "CE")]
    Ceuta,
    #[strum(serialize = "CR")]
    CiudadRealProvince,
    #[strum(serialize = "MD")]
    CommunityOfMadrid,
    #[strum(serialize = "CU")]
    CuencaProvince,
    #[strum(serialize = "CC")]
    CaceresProvince,
    #[strum(serialize = "CA")]
    CadizProvince,
    #[strum(serialize = "CO")]
    CordobaProvince,
    #[strum(serialize = "EX")]
    Extremadura,
    #[strum(serialize = "GA")]
    Galicia,
    #[strum(serialize = "SS")]
    Gipuzkoa,
    #[strum(serialize = "GI")]
    GironaProvince,
    #[strum(serialize = "GR")]
    GranadaProvince,
    #[strum(serialize = "GU")]
    GuadalajaraProvince,
    #[strum(serialize = "H")]
    HuelvaProvince,
    #[strum(serialize = "HU")]
    HuescaProvince,
    #[strum(serialize = "J")]
    JaenProvince,
    #[strum(serialize = "RI")]
    LaRioja,
    #[strum(serialize = "GC")]
    LasPalmasProvince,
    #[strum(serialize = "LE")]
    LeonProvince,
    #[strum(serialize = "L")]
    LleidaProvince,
    #[strum(serialize = "LU")]
    LugoProvince,
    #[strum(serialize = "M")]
    MadridProvince,
    #[strum(serialize = "ML")]
    Melilla,
    #[strum(serialize = "MU")]
    MurciaProvince,
    #[strum(serialize = "MA")]
    MalagaProvince,
    #[strum(serialize = "NC")]
    Navarre,
    #[strum(serialize = "OR")]
    OurenseProvince,
    #[strum(serialize = "P")]
    PalenciaProvince,
    #[strum(serialize = "PO")]
    PontevedraProvince,
    #[strum(serialize = "O")]
    ProvinceOfAsturias,
    #[strum(serialize = "AV")]
    ProvinceOfAvila,
    #[strum(serialize = "MC")]
    RegionOfMurcia,
    #[strum(serialize = "SA")]
    SalamancaProvince,
    #[strum(serialize = "TF")]
    SantaCruzDeTenerifeProvince,
    #[strum(serialize = "SG")]
    SegoviaProvince,
    #[strum(serialize = "SE")]
    SevilleProvince,
    #[strum(serialize = "SO")]
    SoriaProvince,
    #[strum(serialize = "T")]
    TarragonaProvince,
    #[strum(serialize = "TE")]
    TeruelProvince,
    #[strum(serialize = "TO")]
    ToledoProvince,
    #[strum(serialize = "V")]
    ValenciaProvince,
    #[strum(serialize = "VC")]
    ValencianCommunity,
    #[strum(serialize = "VA")]
    ValladolidProvince,
    #[strum(serialize = "ZA")]
    ZamoraProvince,
    #[strum(serialize = "Z")]
    ZaragozaProvince,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum SwitzerlandStatesAbbreviation {
    #[strum(serialize = "AG")]
    Aargau,
    #[strum(serialize = "AR")]
    AppenzellAusserrhoden,
    #[strum(serialize = "AI")]
    AppenzellInnerrhoden,
    #[strum(serialize = "BL")]
    BaselLandschaft,
    #[strum(serialize = "FR")]
    CantonOfFribourg,
    #[strum(serialize = "GE")]
    CantonOfGeneva,
    #[strum(serialize = "JU")]
    CantonOfJura,
    #[strum(serialize = "LU")]
    CantonOfLucerne,
    #[strum(serialize = "NE")]
    CantonOfNeuchatel,
    #[strum(serialize = "SH")]
    CantonOfSchaffhausen,
    #[strum(serialize = "SO")]
    CantonOfSolothurn,
    #[strum(serialize = "SG")]
    CantonOfStGallen,
    #[strum(serialize = "VS")]
    CantonOfValais,
    #[strum(serialize = "VD")]
    CantonOfVaud,
    #[strum(serialize = "ZG")]
    CantonOfZug,
    #[strum(serialize = "GL")]
    Glarus,
    #[strum(serialize = "GR")]
    Graubunden,
    #[strum(serialize = "NW")]
    Nidwalden,
    #[strum(serialize = "OW")]
    Obwalden,
    #[strum(serialize = "SZ")]
    Schwyz,
    #[strum(serialize = "TG")]
    Thurgau,
    #[strum(serialize = "TI")]
    Ticino,
    #[strum(serialize = "UR")]
    Uri,
    #[strum(serialize = "BE")]
    CantonOfBern,
    #[strum(serialize = "ZH")]
    CantonOfZurich,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum UnitedKingdomStatesAbbreviation {
    #[strum(serialize = "ABE")]
    Aberdeen,
    #[strum(serialize = "ABD")]
    Aberdeenshire,
    #[strum(serialize = "ANS")]
    Angus,
    #[strum(serialize = "ANT")]
    Antrim,
    #[strum(serialize = "ANN")]
    AntrimAndNewtownabbey,
    #[strum(serialize = "ARD")]
    Ards,
    #[strum(serialize = "AND")]
    ArdsAndNorthDown,
    #[strum(serialize = "AGB")]
    ArgyllAndBute,
    #[strum(serialize = "ARM")]
    ArmaghCityAndDistrictCouncil,
    #[strum(serialize = "ABC")]
    ArmaghBanbridgeAndCraigavon,
    #[strum(serialize = "SH-AC")]
    AscensionIsland,
    #[strum(serialize = "BLA")]
    BallymenaBorough,
    #[strum(serialize = "BLY")]
    Ballymoney,
    #[strum(serialize = "BNB")]
    Banbridge,
    #[strum(serialize = "BNS")]
    Barnsley,
    #[strum(serialize = "BAS")]
    BathAndNorthEastSomerset,
    #[strum(serialize = "BDF")]
    Bedford,
    #[strum(serialize = "BFS")]
    BelfastDistrict,
    #[strum(serialize = "BIR")]
    Birmingham,
    #[strum(serialize = "BBD")]
    BlackburnWithDarwen,
    #[strum(serialize = "BPL")]
    Blackpool,
    #[strum(serialize = "BGW")]
    BlaenauGwentCountyBorough,
    #[strum(serialize = "BOL")]
    Bolton,
    #[strum(serialize = "BMH")]
    Bournemouth,
    #[strum(serialize = "BRC")]
    BracknellForest,
    #[strum(serialize = "BRD")]
    Bradford,
    #[strum(serialize = "BGE")]
    BridgendCountyBorough,
    #[strum(serialize = "BNH")]
    BrightonAndHove,
    #[strum(serialize = "BKM")]
    Buckinghamshire,
    #[strum(serialize = "BUR")]
    Bury,
    #[strum(serialize = "CAY")]
    CaerphillyCountyBorough,
    #[strum(serialize = "CLD")]
    Calderdale,
    #[strum(serialize = "CAM")]
    Cambridgeshire,
    #[strum(serialize = "CMN")]
    Carmarthenshire,
    #[strum(serialize = "CKF")]
    CarrickfergusBoroughCouncil,
    #[strum(serialize = "CSR")]
    Castlereagh,
    #[strum(serialize = "CCG")]
    CausewayCoastAndGlens,
    #[strum(serialize = "CBF")]
    CentralBedfordshire,
    #[strum(serialize = "CGN")]
    Ceredigion,
    #[strum(serialize = "CHE")]
    CheshireEast,
    #[strum(serialize = "CHW")]
    CheshireWestAndChester,
    #[strum(serialize = "CRF")]
    CityAndCountyOfCardiff,
    #[strum(serialize = "SWA")]
    CityAndCountyOfSwansea,
    #[strum(serialize = "BST")]
    CityOfBristol,
    #[strum(serialize = "DER")]
    CityOfDerby,
    #[strum(serialize = "KHL")]
    CityOfKingstonUponHull,
    #[strum(serialize = "LCE")]
    CityOfLeicester,
    #[strum(serialize = "LND")]
    CityOfLondon,
    #[strum(serialize = "NGM")]
    CityOfNottingham,
    #[strum(serialize = "PTE")]
    CityOfPeterborough,
    #[strum(serialize = "PLY")]
    CityOfPlymouth,
    #[strum(serialize = "POR")]
    CityOfPortsmouth,
    #[strum(serialize = "STH")]
    CityOfSouthampton,
    #[strum(serialize = "STE")]
    CityOfStokeOnTrent,
    #[strum(serialize = "SND")]
    CityOfSunderland,
    #[strum(serialize = "WSM")]
    CityOfWestminster,
    #[strum(serialize = "WLV")]
    CityOfWolverhampton,
    #[strum(serialize = "YOR")]
    CityOfYork,
    #[strum(serialize = "CLK")]
    Clackmannanshire,
    #[strum(serialize = "CLR")]
    ColeraineBoroughCouncil,
    #[strum(serialize = "CWY")]
    ConwyCountyBorough,
    #[strum(serialize = "CKT")]
    CookstownDistrictCouncil,
    #[strum(serialize = "CON")]
    Cornwall,
    #[strum(serialize = "DUR")]
    CountyDurham,
    #[strum(serialize = "COV")]
    Coventry,
    #[strum(serialize = "CGV")]
    CraigavonBoroughCouncil,
    #[strum(serialize = "CMA")]
    Cumbria,
    #[strum(serialize = "DAL")]
    Darlington,
    #[strum(serialize = "DEN")]
    Denbighshire,
    #[strum(serialize = "DBY")]
    Derbyshire,
    #[strum(serialize = "DRS")]
    DerryCityAndStrabane,
    #[strum(serialize = "DRY")]
    DerryCityCouncil,
    #[strum(serialize = "DEV")]
    Devon,
    #[strum(serialize = "DNC")]
    Doncaster,
    #[strum(serialize = "DOR")]
    Dorset,
    #[strum(serialize = "DOW")]
    DownDistrictCouncil,
    #[strum(serialize = "DUD")]
    Dudley,
    #[strum(serialize = "DGY")]
    DumfriesAndGalloway,
    #[strum(serialize = "DND")]
    Dundee,
    #[strum(serialize = "DGN")]
    DungannonAndSouthTyroneBoroughCouncil,
    #[strum(serialize = "EAY")]
    EastAyrshire,
    #[strum(serialize = "EDU")]
    EastDunbartonshire,
    #[strum(serialize = "ELN")]
    EastLothian,
    #[strum(serialize = "ERW")]
    EastRenfrewshire,
    #[strum(serialize = "ERY")]
    EastRidingOfYorkshire,
    #[strum(serialize = "ESX")]
    EastSussex,
    #[strum(serialize = "EDH")]
    Edinburgh,
    #[strum(serialize = "ENG")]
    England,
    #[strum(serialize = "ESS")]
    Essex,
    #[strum(serialize = "FAL")]
    Falkirk,
    #[strum(serialize = "FMO")]
    FermanaghAndOmagh,
    #[strum(serialize = "FER")]
    FermanaghDistrictCouncil,
    #[strum(serialize = "FIF")]
    Fife,
    #[strum(serialize = "FLN")]
    Flintshire,
    #[strum(serialize = "GAT")]
    Gateshead,
    #[strum(serialize = "GLG")]
    Glasgow,
    #[strum(serialize = "GLS")]
    Gloucestershire,
    #[strum(serialize = "GWN")]
    Gwynedd,
    #[strum(serialize = "HAL")]
    Halton,
    #[strum(serialize = "HAM")]
    Hampshire,
    #[strum(serialize = "HPL")]
    Hartlepool,
    #[strum(serialize = "HEF")]
    Herefordshire,
    #[strum(serialize = "HRT")]
    Hertfordshire,
    #[strum(serialize = "HLD")]
    Highland,
    #[strum(serialize = "IVC")]
    Inverclyde,
    #[strum(serialize = "IOW")]
    IsleOfWight,
    #[strum(serialize = "IOS")]
    IslesOfScilly,
    #[strum(serialize = "KEN")]
    Kent,
    #[strum(serialize = "KIR")]
    Kirklees,
    #[strum(serialize = "KWL")]
    Knowsley,
    #[strum(serialize = "LAN")]
    Lancashire,
    #[strum(serialize = "LRN")]
    LarneBoroughCouncil,
    #[strum(serialize = "LDS")]
    Leeds,
    #[strum(serialize = "LEC")]
    Leicestershire,
    #[strum(serialize = "LMV")]
    LimavadyBoroughCouncil,
    #[strum(serialize = "LIN")]
    Lincolnshire,
    #[strum(serialize = "LBC")]
    LisburnAndCastlereagh,
    #[strum(serialize = "LSB")]
    LisburnCityCouncil,
    #[strum(serialize = "LIV")]
    Liverpool,
    #[strum(serialize = "BDG")]
    LondonBoroughOfBarkingAndDagenham,
    #[strum(serialize = "BNE")]
    LondonBoroughOfBarnet,
    #[strum(serialize = "BEX")]
    LondonBoroughOfBexley,
    #[strum(serialize = "BEN")]
    LondonBoroughOfBrent,
    #[strum(serialize = "BRY")]
    LondonBoroughOfBromley,
    #[strum(serialize = "CMD")]
    LondonBoroughOfCamden,
    #[strum(serialize = "CRY")]
    LondonBoroughOfCroydon,
    #[strum(serialize = "EAL")]
    LondonBoroughOfEaling,
    #[strum(serialize = "ENF")]
    LondonBoroughOfEnfield,
    #[strum(serialize = "HCK")]
    LondonBoroughOfHackney,
    #[strum(serialize = "HMF")]
    LondonBoroughOfHammersmithAndFulham,
    #[strum(serialize = "HRY")]
    LondonBoroughOfHaringey,
    #[strum(serialize = "HRW")]
    LondonBoroughOfHarrow,
    #[strum(serialize = "HAV")]
    LondonBoroughOfHavering,
    #[strum(serialize = "HIL")]
    LondonBoroughOfHillingdon,
    #[strum(serialize = "HNS")]
    LondonBoroughOfHounslow,
    #[strum(serialize = "ISL")]
    LondonBoroughOfIslington,
    #[strum(serialize = "LBH")]
    LondonBoroughOfLambeth,
    #[strum(serialize = "LEW")]
    LondonBoroughOfLewisham,
    #[strum(serialize = "MRT")]
    LondonBoroughOfMerton,
    #[strum(serialize = "NWM")]
    LondonBoroughOfNewham,
    #[strum(serialize = "RDB")]
    LondonBoroughOfRedbridge,
    #[strum(serialize = "RIC")]
    LondonBoroughOfRichmondUponThames,
    #[strum(serialize = "SWK")]
    LondonBoroughOfSouthwark,
    #[strum(serialize = "STN")]
    LondonBoroughOfSutton,
    #[strum(serialize = "TWH")]
    LondonBoroughOfTowerHamlets,
    #[strum(serialize = "WFT")]
    LondonBoroughOfWalthamForest,
    #[strum(serialize = "WND")]
    LondonBoroughOfWandsworth,
    #[strum(serialize = "MFT")]
    MagherafeltDistrictCouncil,
    #[strum(serialize = "MAN")]
    Manchester,
    #[strum(serialize = "MDW")]
    Medway,
    #[strum(serialize = "MTY")]
    MerthyrTydfilCountyBorough,
    #[strum(serialize = "WGN")]
    MetropolitanBoroughOfWigan,
    #[strum(serialize = "MEA")]
    MidAndEastAntrim,
    #[strum(serialize = "MUL")]
    MidUlster,
    #[strum(serialize = "MDB")]
    Middlesbrough,
    #[strum(serialize = "MLN")]
    Midlothian,
    #[strum(serialize = "MIK")]
    MiltonKeynes,
    #[strum(serialize = "MON")]
    Monmouthshire,
    #[strum(serialize = "MRY")]
    Moray,
    #[strum(serialize = "MYL")]
    MoyleDistrictCouncil,
    #[strum(serialize = "NTL")]
    NeathPortTalbotCountyBorough,
    #[strum(serialize = "NET")]
    NewcastleUponTyne,
    #[strum(serialize = "NWP")]
    Newport,
    #[strum(serialize = "NYM")]
    NewryAndMourneDistrictCouncil,
    #[strum(serialize = "NMD")]
    NewryMourneAndDown,
    #[strum(serialize = "NTA")]
    NewtownabbeyBoroughCouncil,
    #[strum(serialize = "NFK")]
    Norfolk,
    #[strum(serialize = "NAY")]
    NorthAyrshire,
    #[strum(serialize = "NDN")]
    NorthDownBoroughCouncil,
    #[strum(serialize = "NEL")]
    NorthEastLincolnshire,
    #[strum(serialize = "NLK")]
    NorthLanarkshire,
    #[strum(serialize = "NLN")]
    NorthLincolnshire,
    #[strum(serialize = "NSM")]
    NorthSomerset,
    #[strum(serialize = "NTY")]
    NorthTyneside,
    #[strum(serialize = "NYK")]
    NorthYorkshire,
    #[strum(serialize = "NTH")]
    Northamptonshire,
    #[strum(serialize = "NIR")]
    NorthernIreland,
    #[strum(serialize = "NBL")]
    Northumberland,
    #[strum(serialize = "NTT")]
    Nottinghamshire,
    #[strum(serialize = "OLD")]
    Oldham,
    #[strum(serialize = "OMH")]
    OmaghDistrictCouncil,
    #[strum(serialize = "ORK")]
    OrkneyIslands,
    #[strum(serialize = "ELS")]
    OuterHebrides,
    #[strum(serialize = "OXF")]
    Oxfordshire,
    #[strum(serialize = "PEM")]
    Pembrokeshire,
    #[strum(serialize = "PKN")]
    PerthAndKinross,
    #[strum(serialize = "POL")]
    Poole,
    #[strum(serialize = "POW")]
    Powys,
    #[strum(serialize = "RDG")]
    Reading,
    #[strum(serialize = "RCC")]
    RedcarAndCleveland,
    #[strum(serialize = "RFW")]
    Renfrewshire,
    #[strum(serialize = "RCT")]
    RhonddaCynonTaf,
    #[strum(serialize = "RCH")]
    Rochdale,
    #[strum(serialize = "ROT")]
    Rotherham,
    #[strum(serialize = "GRE")]
    RoyalBoroughOfGreenwich,
    #[strum(serialize = "KEC")]
    RoyalBoroughOfKensingtonAndChelsea,
    #[strum(serialize = "KTT")]
    RoyalBoroughOfKingstonUponThames,
    #[strum(serialize = "RUT")]
    Rutland,
    #[strum(serialize = "SH-HL")]
    SaintHelena,
    #[strum(serialize = "SLF")]
    Salford,
    #[strum(serialize = "SAW")]
    Sandwell,
    #[strum(serialize = "SCT")]
    Scotland,
    #[strum(serialize = "SCB")]
    ScottishBorders,
    #[strum(serialize = "SFT")]
    Sefton,
    #[strum(serialize = "SHF")]
    Sheffield,
    #[strum(serialize = "ZET")]
    ShetlandIslands,
    #[strum(serialize = "SHR")]
    Shropshire,
    #[strum(serialize = "SLG")]
    Slough,
    #[strum(serialize = "SOL")]
    Solihull,
    #[strum(serialize = "SOM")]
    Somerset,
    #[strum(serialize = "SAY")]
    SouthAyrshire,
    #[strum(serialize = "SGC")]
    SouthGloucestershire,
    #[strum(serialize = "SLK")]
    SouthLanarkshire,
    #[strum(serialize = "STY")]
    SouthTyneside,
    #[strum(serialize = "SOS")]
    SouthendOnSea,
    #[strum(serialize = "SHN")]
    StHelens,
    #[strum(serialize = "STS")]
    Staffordshire,
    #[strum(serialize = "STG")]
    Stirling,
    #[strum(serialize = "SKP")]
    Stockport,
    #[strum(serialize = "STT")]
    StocktonOnTees,
    #[strum(serialize = "STB")]
    StrabaneDistrictCouncil,
    #[strum(serialize = "SFK")]
    Suffolk,
    #[strum(serialize = "SRY")]
    Surrey,
    #[strum(serialize = "SWD")]
    Swindon,
    #[strum(serialize = "TAM")]
    Tameside,
    #[strum(serialize = "TFW")]
    TelfordAndWrekin,
    #[strum(serialize = "THR")]
    Thurrock,
    #[strum(serialize = "TOB")]
    Torbay,
    #[strum(serialize = "TOF")]
    Torfaen,
    #[strum(serialize = "TRF")]
    Trafford,
    #[strum(serialize = "UKM")]
    UnitedKingdom,
    #[strum(serialize = "VGL")]
    ValeOfGlamorgan,
    #[strum(serialize = "WKF")]
    Wakefield,
    #[strum(serialize = "WLS")]
    Wales,
    #[strum(serialize = "WLL")]
    Walsall,
    #[strum(serialize = "WRT")]
    Warrington,
    #[strum(serialize = "WAR")]
    Warwickshire,
    #[strum(serialize = "WBK")]
    WestBerkshire,
    #[strum(serialize = "WDU")]
    WestDunbartonshire,
    #[strum(serialize = "WLN")]
    WestLothian,
    #[strum(serialize = "WSX")]
    WestSussex,
    #[strum(serialize = "WIL")]
    Wiltshire,
    #[strum(serialize = "WNM")]
    WindsorAndMaidenhead,
    #[strum(serialize = "WRL")]
    Wirral,
    #[strum(serialize = "WOK")]
    Wokingham,
    #[strum(serialize = "WOR")]
    Worcestershire,
    #[strum(serialize = "WRX")]
    WrexhamCountyBorough,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum BelgiumStatesAbbreviation {
    #[strum(serialize = "VAN")]
    Antwerp,
    #[strum(serialize = "BRU")]
    BrusselsCapitalRegion,
    #[strum(serialize = "VOV")]
    EastFlanders,
    #[strum(serialize = "VLG")]
    Flanders,
    #[strum(serialize = "VBR")]
    FlemishBrabant,
    #[strum(serialize = "WHT")]
    Hainaut,
    #[strum(serialize = "VLI")]
    Limburg,
    #[strum(serialize = "WLG")]
    Liege,
    #[strum(serialize = "WLX")]
    Luxembourg,
    #[strum(serialize = "WNA")]
    Namur,
    #[strum(serialize = "WAL")]
    Wallonia,
    #[strum(serialize = "WBR")]
    WalloonBrabant,
    #[strum(serialize = "VWV")]
    WestFlanders,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum LuxembourgStatesAbbreviation {
    #[strum(serialize = "CA")]
    CantonOfCapellen,
    #[strum(serialize = "CL")]
    CantonOfClervaux,
    #[strum(serialize = "DI")]
    CantonOfDiekirch,
    #[strum(serialize = "EC")]
    CantonOfEchternach,
    #[strum(serialize = "ES")]
    CantonOfEschSurAlzette,
    #[strum(serialize = "GR")]
    CantonOfGrevenmacher,
    #[strum(serialize = "LU")]
    CantonOfLuxembourg,
    #[strum(serialize = "ME")]
    CantonOfMersch,
    #[strum(serialize = "RD")]
    CantonOfRedange,
    #[strum(serialize = "RM")]
    CantonOfRemich,
    #[strum(serialize = "VD")]
    CantonOfVianden,
    #[strum(serialize = "WI")]
    CantonOfWiltz,
    #[strum(serialize = "D")]
    DiekirchDistrict,
    #[strum(serialize = "G")]
    GrevenmacherDistrict,
    #[strum(serialize = "L")]
    LuxembourgDistrict,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum RussiaStatesAbbreviation {
    #[strum(serialize = "ALT")]
    AltaiKrai,
    #[strum(serialize = "AL")]
    AltaiRepublic,
    #[strum(serialize = "AMU")]
    AmurOblast,
    #[strum(serialize = "ARK")]
    Arkhangelsk,
    #[strum(serialize = "AST")]
    AstrakhanOblast,
    #[strum(serialize = "BEL")]
    BelgorodOblast,
    #[strum(serialize = "BRY")]
    BryanskOblast,
    #[strum(serialize = "CE")]
    ChechenRepublic,
    #[strum(serialize = "CHE")]
    ChelyabinskOblast,
    #[strum(serialize = "CHU")]
    ChukotkaAutonomousOkrug,
    #[strum(serialize = "CU")]
    ChuvashRepublic,
    #[strum(serialize = "IRK")]
    Irkutsk,
    #[strum(serialize = "IVA")]
    IvanovoOblast,
    #[strum(serialize = "YEV")]
    JewishAutonomousOblast,
    #[strum(serialize = "KB")]
    KabardinoBalkarRepublic,
    #[strum(serialize = "KGD")]
    Kaliningrad,
    #[strum(serialize = "KLU")]
    KalugaOblast,
    #[strum(serialize = "KAM")]
    KamchatkaKrai,
    #[strum(serialize = "KC")]
    KarachayCherkessRepublic,
    #[strum(serialize = "KEM")]
    KemerovoOblast,
    #[strum(serialize = "KHA")]
    KhabarovskKrai,
    #[strum(serialize = "KHM")]
    KhantyMansiAutonomousOkrug,
    #[strum(serialize = "KIR")]
    KirovOblast,
    #[strum(serialize = "KO")]
    KomiRepublic,
    #[strum(serialize = "KOS")]
    KostromaOblast,
    #[strum(serialize = "KDA")]
    KrasnodarKrai,
    #[strum(serialize = "KYA")]
    KrasnoyarskKrai,
    #[strum(serialize = "KGN")]
    KurganOblast,
    #[strum(serialize = "KRS")]
    KurskOblast,
    #[strum(serialize = "LEN")]
    LeningradOblast,
    #[strum(serialize = "LIP")]
    LipetskOblast,
    #[strum(serialize = "MAG")]
    MagadanOblast,
    #[strum(serialize = "ME")]
    MariElRepublic,
    #[strum(serialize = "MOW")]
    Moscow,
    #[strum(serialize = "MOS")]
    MoscowOblast,
    #[strum(serialize = "MUR")]
    MurmanskOblast,
    #[strum(serialize = "NEN")]
    NenetsAutonomousOkrug,
    #[strum(serialize = "NIZ")]
    NizhnyNovgorodOblast,
    #[strum(serialize = "NGR")]
    NovgorodOblast,
    #[strum(serialize = "NVS")]
    Novosibirsk,
    #[strum(serialize = "OMS")]
    OmskOblast,
    #[strum(serialize = "ORE")]
    OrenburgOblast,
    #[strum(serialize = "ORL")]
    OryolOblast,
    #[strum(serialize = "PNZ")]
    PenzaOblast,
    #[strum(serialize = "PER")]
    PermKrai,
    #[strum(serialize = "PRI")]
    PrimorskyKrai,
    #[strum(serialize = "PSK")]
    PskovOblast,
    #[strum(serialize = "AD")]
    RepublicOfAdygea,
    #[strum(serialize = "BA")]
    RepublicOfBashkortostan,
    #[strum(serialize = "BU")]
    RepublicOfBuryatia,
    #[strum(serialize = "DA")]
    RepublicOfDagestan,
    #[strum(serialize = "IN")]
    RepublicOfIngushetia,
    #[strum(serialize = "KL")]
    RepublicOfKalmykia,
    #[strum(serialize = "KR")]
    RepublicOfKarelia,
    #[strum(serialize = "KK")]
    RepublicOfKhakassia,
    #[strum(serialize = "MO")]
    RepublicOfMordovia,
    #[strum(serialize = "SE")]
    RepublicOfNorthOssetiaAlania,
    #[strum(serialize = "TA")]
    RepublicOfTatarstan,
    #[strum(serialize = "ROS")]
    RostovOblast,
    #[strum(serialize = "RYA")]
    RyazanOblast,
    #[strum(serialize = "SPE")]
    SaintPetersburg,
    #[strum(serialize = "SA")]
    SakhaRepublic,
    #[strum(serialize = "SAK")]
    Sakhalin,
    #[strum(serialize = "SAM")]
    SamaraOblast,
    #[strum(serialize = "SAR")]
    SaratovOblast,
    #[strum(serialize = "UA-40")]
    Sevastopol,
    #[strum(serialize = "SMO")]
    SmolenskOblast,
    #[strum(serialize = "STA")]
    StavropolKrai,
    #[strum(serialize = "SVE")]
    Sverdlovsk,
    #[strum(serialize = "TAM")]
    TambovOblast,
    #[strum(serialize = "TOM")]
    TomskOblast,
    #[strum(serialize = "TUL")]
    TulaOblast,
    #[strum(serialize = "TY")]
    TuvaRepublic,
    #[strum(serialize = "TVE")]
    TverOblast,
    #[strum(serialize = "TYU")]
    TyumenOblast,
    #[strum(serialize = "UD")]
    UdmurtRepublic,
    #[strum(serialize = "ULY")]
    UlyanovskOblast,
    #[strum(serialize = "VLA")]
    VladimirOblast,
    #[strum(serialize = "VLG")]
    VologdaOblast,
    #[strum(serialize = "VOR")]
    VoronezhOblast,
    #[strum(serialize = "YAN")]
    YamaloNenetsAutonomousOkrug,
    #[strum(serialize = "YAR")]
    YaroslavlOblast,
    #[strum(serialize = "ZAB")]
    ZabaykalskyKrai,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum SanMarinoStatesAbbreviation {
    #[strum(serialize = "01")]
    Acquaviva,
    #[strum(serialize = "06")]
    BorgoMaggiore,
    #[strum(serialize = "02")]
    Chiesanuova,
    #[strum(serialize = "03")]
    Domagnano,
    #[strum(serialize = "04")]
    Faetano,
    #[strum(serialize = "05")]
    Fiorentino,
    #[strum(serialize = "08")]
    Montegiardino,
    #[strum(serialize = "07")]
    SanMarino,
    #[strum(serialize = "09")]
    Serravalle,
}

#[derive(
    Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString,
)]
pub enum SerbiaStatesAbbreviation {
    #[strum(serialize = "00")]
    Belgrade,

    #[strum(serialize = "01")]
    BorDistrict,

    #[strum(serialize = "02")]
    BraničevoDistrict,

    #[strum(serialize = "03")]
    CentralBanatDistrict,

    #[strum(serialize = "04")]
    JablanicaDistrict,

    #[strum(serialize = "05")]
    KolubaraDistrict,

    #[strum(serialize = "06")]
    MačvaDistrict,

    #[strum(serialize = "07")]
    MoravicaDistrict,

    #[strum(serialize = "08")]
    NišavaDistrict,

    #[strum(serialize = "09")]
    NorthBanatDistrict,

    #[strum(serialize = "10")]
    NorthBačkaDistrict,

    #[strum(serialize = "11")]
    PirotDistrict,

    #[strum(serialize = "12")]
    PodunavljeDistrict,

    #[strum(serialize = "13")]
    PomoravljeDistrict,

    #[strum(serialize = "14")]
    PčinjaDistrict,

    #[strum(serialize = "15")]
    RasinaDistrict,

    #[strum(serialize = "16")]
    RaškaDistrict,

    #[strum(serialize = "17")]
    SouthBanatDistrict,

    #[strum(serialize = "18")]
    SouthBačkaDistrict,

    #[strum(serialize = "19")]
    SremDistrict,


pub struct CybersourcePaymentsIncrementalAuthorizationRequest {
    processing_information: ProcessingInformation,
    order_information: OrderInformationIncrementalAuthorization,
}


pub struct ThreeDSRequestorPriorTransactionAuthenticationInformation {
    /// This data element provides additional information to the ACS to determine the best
    /// approach for handling a request. The field is limited to 36 characters containing
    /// ACS Transaction ID for a prior authenticated transaction (for example, the first
    /// recurring transaction that was authenticated with the cardholder).
    pub three_ds_req_prior_ref: String,

    /// Mechanism used by the Cardholder to previously authenticate to the 3DS Requestor.
    /// Accepted values for this field are:
    ///    - 01 -> Frictionless authentication occurred by ACS
    ///    - 02 -> Cardholder challenge occurred by ACS
    ///    - 03 -> AVS verified
    ///    - 04 -> Other issuer methods
    ///    - 80-99 -> PS-specific value (dependent on the payment scheme type).
    pub three_ds_req_prior_auth_method: String,

    /// Date and time converted into UTC of the prior authentication. Accepted date
    /// format is YYYYMMDDHHMM.
    pub three_ds_req_prior_auth_timestamp: String,

    /// Data that documents and supports a specific authentication process. In the current
    /// version of the specification this data element is not defined in detail, however
    /// the intention is that for each 3DS Requestor Authentication Method, this field carry
    /// data that the ACS can use to verify the authentication process. In future versions
    /// of the application, these details are expected to be included. Field is limited to
    /// maximum 2048 characters.
    pub three_ds_req_prior_auth_data: String,
}


impl MandateSetup for Netcetera {}


pub struct Rule<O> {
    pub name: String,
    #[serde(alias = "routingOutput")]
    pub connector_selection: O,
    pub statements: Vec<IfStatement>,
}


pub struct ZenPaymentData {
    browser_details: ZenBrowserDetails,
    #[serde(rename = "type")]
    payment_type: ZenPaymentTypes,
    #[serde(skip_serializing_if = "Option::is_none")]
    token: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    card: Option<ZenCardDetails>,
    descriptor: String,
    return_verify_url: Option<String>,
}


impl api::Refund for Xendit {}


OpenAPI Block Path: components.schemas.CardRedirectResponse
{
  "allOf": [
    {
      "allOf": [
        {
          "$ref": "#/components/schemas/CardRedirectData"
        }
      ],
      "nullable": true
    },
    {
      "type": "object"
    }
  ]
}


pub struct WorldpayAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) entity_id: Secret<String>,
}


impl api::ConnectorAccessToken for Digitalvirgo {}

impl api::ConnectorAccessToken for for Digitalvirgo


pub struct SantanderCalendar {
    #[serde(rename = "calendario")]
    pub creation: String,
    #[serde(rename = "expiracao")]
    pub expiration: i32,
}


pub async fn update_or_add_connector_customer_payment_processor_tokens(
        state: &SessionState,
        connector_customer_id: &str,
        payment_processor_token_info_map: HashMap<String, PaymentProcessorTokenStatus>,
    ) -> CustomResult<(), errors::StorageError> {
        let redis_conn =
            state
                .store
                .get_redis_conn()
                .change_context(errors::StorageError::RedisError(
                    errors::RedisError::RedisConnectionError.into(),
                ))?;
        let tokens_key = format!("customer:{connector_customer_id}:tokens");

        // allocate capacity up-front to avoid rehashing
        let mut serialized_payment_processor_tokens: HashMap<String, String> =
            HashMap::with_capacity(payment_processor_token_info_map.len());

        // serialize all tokens, preserving explicit error handling and attachable diagnostics
        for (payment_processor_token_id, payment_processor_token_status) in
            payment_processor_token_info_map
        {
            let serialized = serde_json::to_string(&payment_processor_token_status)
                .change_context(errors::StorageError::SerializationFailed)
                .attach_printable("Failed to serialize token status")?;

            serialized_payment_processor_tokens.insert(payment_processor_token_id, serialized);
        }
        let seconds = &state.conf.revenue_recovery.redis_ttl_in_seconds;

        // Update or add tokens
        redis_conn
            .set_hash_fields(
                &tokens_key.into(),
                serialized_payment_processor_tokens,
                Some(*seconds),
            )
            .await
            .change_context(errors::StorageError::RedisError(
                errors::RedisError::SetHashFieldFailed.into(),
            ))?;

        tracing::info!(
            connector_customer_id = %connector_customer_id,
            "Successfully updated or added customer tokens",
        );

        Ok(())
    }


Documentation: api-reference/v2/profile/profile--create.mdx
# Type: Doc File

---
openapi: post /v2/profiles
---


pub struct AuthenticationDetails {
    /// The status of authentication for the payment method
    #[schema(value_type = IntentStatus)]
    pub status: common_enums::IntentStatus,

    /// Error details of the authentication
    #[schema(value_type = Option<ErrorDetails>)]
    pub error: Option<payments::ErrorDetails>,
}


impl ConnectorRedirectResponse for Braintree {
    fn get_flow_type(
        &self,
        _query_params: &str,
        json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, errors::ConnectorError> {
        match action {
            PaymentAction::PSync => match json_payload {
                Some(payload) => {
                    let redirection_response: transformers::BraintreeRedirectionResponse =
                        serde_json::from_value(payload).change_context(
                            errors::ConnectorError::MissingConnectorRedirectionPayload {
                                field_name: "redirection_response",
                            },
                        )?;
                    let braintree_payload =
                        serde_json::from_str::<transformers::BraintreeThreeDsErrorResponse>(
                            &redirection_response.authentication_response,
                        );
                    let (error_code, error_message) = match braintree_payload {
                        Ok(braintree_response_payload) => (
                            braintree_response_payload.code,
                            braintree_response_payload.message,
                        ),
                        Err(_) => (
                            NO_ERROR_CODE.to_string(),
                            redirection_response.authentication_response,
                        ),
                    };
                    Ok(CallConnectorAction::StatusUpdate {
                        status: enums::AttemptStatus::AuthenticationFailed,
                        error_code: Some(error_code),
                        error_message: Some(error_message),
                    })
                }
                None => Ok(CallConnectorAction::Avoid),
            },
            PaymentAction::CompleteAuthorize
            | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
        }
    }
}

impl ConnectorRedirectResponse for for Braintree


pub struct AchPaymentInformation {
    bank: AchBankAccount,
}


impl ForeignIDRef for PaymentAttempt {
    fn foreign_id(&self) -> String {
        todo!()
    }
}

impl ForeignIDRef for for PaymentAttempt


pub fn generate_payment_method_response(
    payment_method: &domain::PaymentMethod,
    single_use_token: &Option<payment_method_data::SingleUsePaymentMethodToken>,
) -> errors::RouterResult<api::PaymentMethodResponse> {
    let pmd = payment_method
        .payment_method_data
        .clone()
        .map(|data| data.into_inner())
        .and_then(|data| match data {
            api::PaymentMethodsData::Card(card) => {
                Some(api::PaymentMethodResponseData::Card(card.into()))
            }
            _ => None,
        });
    let mut connector_tokens = payment_method
        .connector_mandate_details
        .as_ref()
        .and_then(|connector_token_details| connector_token_details.payments.clone())
        .map(|payment_token_details| payment_token_details.0)
        .map(|payment_token_details| {
            payment_token_details
                .into_iter()
                .map(transformers::ForeignFrom::foreign_from)
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();

    if let Some(token) = single_use_token {
        let connector_token_single_use = transformers::ForeignFrom::foreign_from(token);
        connector_tokens.push(connector_token_single_use);
    }
    let connector_tokens = if connector_tokens.is_empty() {
        None
    } else {
        Some(connector_tokens)
    };

    let network_token_pmd = payment_method
        .network_token_payment_method_data
        .clone()
        .map(|data| data.into_inner())
        .and_then(|data| match data {
            domain::PaymentMethodsData::NetworkToken(token) => {
                Some(api::NetworkTokenDetailsPaymentMethod::from(token))
            }
            _ => None,
        });

    let network_token = network_token_pmd.map(|pmd| api::NetworkTokenResponse {
        payment_method_data: pmd,
    });

    let resp = api::PaymentMethodResponse {
        merchant_id: payment_method.merchant_id.to_owned(),
        customer_id: payment_method.customer_id.to_owned(),
        id: payment_method.id.to_owned(),
        payment_method_type: payment_method.get_payment_method_type(),
        payment_method_subtype: payment_method.get_payment_method_subtype(),
        created: Some(payment_method.created_at),
        recurring_enabled: Some(false),
        last_used_at: Some(payment_method.last_used_at),
        payment_method_data: pmd,
        connector_tokens,
        network_token,
    };

    Ok(resp)
}


pub struct GlobalPaymentMethodId(GlobalId);


pub struct StripebillingWebhookData {
    pub object: StripebillingWebhookObject,
}


                format!(
                    "{field_name} contains invalid data. Expected format is {expected_format}"
                ), None
            )),
            Self::InvalidRequestData { message } => {
                AER::Unprocessable(ApiError::new("IR", 6, message.to_string(), None))
            }
            Self::InvalidDataValue { field_name } => AER::BadRequest(ApiError::new(
                "IR",
                7,
                format!("Invalid value provided: {field_name}"), None
            )),
            Self::ClientSecretNotGiven => AER::BadRequest(ApiError::new(
                "IR",
                8,
                "client_secret was not provided", None
            )),
            Self::ClientSecretExpired => AER::BadRequest(ApiError::new(
                "IR",
                8,
                "The provided client_secret has expired", None
            )),
            Self::ClientSecretInvalid => {
                AER::BadRequest(ApiError::new("IR", 9, "The client_secret provided does not match the client_secret associated with the Payment", None))
            }
            Self::MandateActive => {
                AER::BadRequest(ApiError::new("IR", 10, "Customer has active mandate/subsciption", None))
            }
            Self::CustomerRedacted => {
                AER::BadRequest(ApiError::new("IR", 11, "Customer has already been redacted", None))
            }
            Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)),
            Self::RefundAmountExceedsPaymentAmount => {
                AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None))
            }
            Self::PaymentUnexpectedState {
                current_flow,
                field_name,
                current_value,
                states,
            } => AER::BadRequest(ApiError::new("IR", 14, format!("This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}"), None)),
            Self::InvalidEphemeralKey => AER::Unauthorized(ApiError::new("IR", 15, "Invalid Ephemeral Key for the customer", None)),
            Self::PreconditionFailed { message } => {
                AER::BadRequest(ApiError::new("IR", 16, message.to_string(), None))
            }
            Self::InvalidJwtToken => AER::Unauthorized(ApiError::new("IR", 17, "Access forbidden, invalid JWT token was used", None)),
            Self::GenericUnauthorized { message } => {
                AER::Unauthorized(ApiError::new("IR", 18, message.to_string(), None))
            },
            Self::NotSupported { message } => {
                AER::BadRequest(ApiError::new("IR", 19, "Payment method type not supported", Some(Extra {reason: Some(message.to_owned()), ..Default::default()})))
            },
            Self::FlowNotSupported { flow, connector } => {
                AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message
            }
            Self::MissingRequiredFields { field_names } => AER::BadRequest(
                ApiError::new("IR", 21, "Missing required params".to_string(), Some(Extra {data: Some(serde_json::json!(field_names)), ..Default::default() })),
            ),
            Self::AccessForbidden {resource} => {
                AER::ForbiddenCommonResource(ApiError::new("IR", 22, format!("Access forbidden. Not authorized to access this resource {resource}"), None))
            },
            Self::FileProviderNotSupported { message } => {
                AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None))
            },
            Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new(
                "IR",
                24,
                format!("Invalid {wallet_name} wallet token"), None
            )),
            Self::PaymentMethodDeleteFailed => {
                AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None))
            }
            Self::InvalidCookie => {
                AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None))
            }
            Self::ExtendedCardInfoNotFound => {
                AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None))
            }
            Self::CurrencyNotSupported { message } => {
                AER::BadRequest(ApiError::new("IR", 28, message, None))
            }
            Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 29, message.to_string(), None)),
            Self::InvalidConnectorConfiguration {config} => {
                AER::BadRequest(ApiError::new("IR", 30, format!("Merchant connector account is configured with invalid {config}"), None))
            }
            Self::InvalidCardIin => AER::BadRequest(ApiError::new("IR", 31, "The provided card IIN does not exist", None)),
            Self::InvalidCardIinLength  => AER::BadRequest(ApiError::new("IR", 32, "The provided card IIN length is invalid, please provide an IIN with 6 digits", None)),
            Self::MissingFile => {
                AER::BadRequest(ApiError::new("IR", 33, "File not found in the request", None))
            }
            Self::MissingDisputeId => {
                AER::BadRequest(ApiError::new("IR", 34, "Dispute id not found in the request", None))
            }
            Self::MissingFilePurpose => {
                AER::BadRequest(ApiError::new("IR", 35, "File purpose not found in the request or is invalid", None))
            }
            Self::MissingFileContentType => {
                AER::BadRequest(ApiError::new("IR", 36, "File content type not found", None))
            }
            Self::GenericNotFoundError { message } => {
                AER::NotFound(ApiError::new("IR", 37, message, None))
            },
            Self::GenericDuplicateError { message } => {
                AER::BadRequest(ApiError::new("IR", 38, message, None))
            }
            Self::IncorrectPaymentMethodConfiguration => {
                AER::BadRequest(ApiError::new("IR", 39, "No eligible connector was found for the current payment method configuration", None))
            }
            Self::LinkConfigurationError { message } => {
                AER::BadRequest(ApiError::new("IR", 40, message, None))
            },
            Self::PayoutFailed { data } => {
                AER::BadRequest(ApiError::new("IR", 41, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()})))
            },
            Self::CookieNotFound => {
                AER::Unauthorized(ApiError::new("IR", 42, "Cookies are not found in the request", None))
            },
            Self::ExternalVaultFailed => {
                AER::BadRequest(ApiError::new("IR", 45, "External Vault failed while processing with connector.", None))
            },
            Self::MandatePaymentDataMismatch { fields} => {
                AER::BadRequest(ApiError::new("IR", 46, format!("Field {fields} doesn't match with the ones used during mandate creation"), Some(Extra {fields: Some(fields.to_owned()), ..Default::default()}))) //FIXME: error message
            }
            Self::MaxFieldLengthViolated { connector, field_name,  max_length, received_length} => {
                AER::BadRequest(ApiError::new("IR", 47, format!("Connector '{connector}' rejected field '{field_name}': length {received_length} exceeds maximum of {max_length}"), Some(Extra {connector: Some(connector.to_string()), ..Default::default()})))
            }
            Self::WebhookAuthenticationFailed => {
                AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None))
            }
            Self::WebhookBadRequest => {
                AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None))
            }
            Self::WebhookProcessingFailure => {
                AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None))
            },
            Self::WebhookResourceNotFound => {
                AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None))
            }
            Self::WebhookUnprocessableEntity => {
                AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None))
            },
            Self::WebhookInvalidMerchantSecret => {
                AER::BadRequest(ApiError::new("WE", 6, "Merchant Secret set for webhook source verification is invalid", None))
            }
            Self::IntegrityCheckFailed {
                reason,
                field_names,
                connector_transaction_id
            } => AER::InternalServerError(ApiError::new(
                "IE",
                0,
                format!("{reason} as data mismatched for {field_names}"),
                Some(Extra {
                    connector_transaction_id: connector_transaction_id.to_owned(),
                    ..Default::default()
                })
            )),
            Self::PlatformAccountAuthNotSupported => {
                AER::BadRequest(ApiError::new("IR", 43, "API does not support platform operation", None))
            }
            Self::InvalidPlatformOperation => {
                AER::Unauthorized(ApiError::new("IR", 44, "Invalid platform account operation", None))
            }
            Self::TokenizationRecordNotFound{ id } => {
                AER::NotFound(ApiError::new("HE", 2, format!("Tokenization record not found for the given token_id '{id}' "), None))
            }
        }
    }
}

impl actix_web::ResponseError for ApiErrorResponse {
    fn status_code(&self) -> StatusCode {
        ErrorSwitch::<api_models::errors::types::ApiErrorResponse>::switch(self).status_code()
    }

    fn error_response(&self) -> actix_web::HttpResponse {
        ErrorSwitch::<api_models::errors::types::ApiErrorResponse>::switch(self).error_response()
    }
}

impl From<ApiErrorResponse> for router_data::ErrorResponse {
    fn from(error: ApiErrorResponse) -> Self {
        Self {
            code: error.error_code(),
            message: error.error_message(),
            reason: None,
            status_code: match error {
                ApiErrorResponse::ExternalConnectorError { status_code, .. } => status_code,
                _ => 500,
            },
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        }
    }
}