// File: crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 51

use api_models::payments::{
    ApplePayCombinedMetadata, ApplepayCombinedSessionTokenData, ApplepaySessionTokenData,
    ApplepaySessionTokenMetadata,
};
use base64::Engine;
use common_enums::{enums, Currency};
use common_utils::{
    consts::BASE64_ENGINE, ext_traits::ValueExt, pii, request::Method, types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, Secret};
use serde::{ser::Serializer, Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, CardData as _, PaymentsCancelRequestData, PaymentsSyncRequestData, RouterData as _,
    },
};

#[derive(Debug, Serialize)]
pub struct FiservRouterData<T> {
    pub amount: FloatMajorUnit,
    pub router_data: T,
}

impl<T> TryFrom<(FloatMajorUnit, T)> for FiservRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from((amount, router_data): (FloatMajorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data,
        })
    }
}

impl Serialize for FiservCheckoutChargesRequest {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::Checkout(inner) => inner.serialize(serializer),
            Self::Charges(inner) => inner.serialize(serializer),
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservPaymentsRequest {
    amount: Amount,
    merchant_details: MerchantDetails,
    #[serde(flatten)]
    checkout_charges_request: FiservCheckoutChargesRequest,
}

#[derive(Debug)]
pub enum FiservCheckoutChargesRequest {
    Checkout(CheckoutPaymentsRequest),
    Charges(ChargesPaymentRequest),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckoutPaymentsRequest {
    order: FiservOrder,
    payment_method: FiservPaymentMethod,
    interactions: FiservInteractions,
    transaction_details: TransactionDetails,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservChannel {
    Web,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservPaymentInitiator {
    Merchant,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FiservCustomerConfirmation {
    ReviewAndPay,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservInteractions {
    channel: FiservChannel,
    customer_confirmation: FiservCustomerConfirmation,
    payment_initiator: FiservPaymentInitiator,
    return_urls: FiservReturnUrls,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservReturnUrls {
    success_url: String,
    cancel_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservPaymentMethod {
    provider: FiservWallet,
    #[serde(rename = "type")]
    wallet_type: FiservWalletType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservOrder {
    intent: FiservIntent,
}

#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservIntent {
    Authorize,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ChargesPaymentRequest {
    source: Source,
    transaction_interaction: Option<TransactionInteraction>,
    transaction_details: TransactionDetails,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservWallet {
    ApplePay,
    GooglePay,
    PayPal,
}

#[derive(Debug, Serialize)]
#[serde(tag = "sourceType")]
pub enum Source {
    #[serde(rename = "GooglePay")]
    GooglePay(GooglePayData),
    #[serde(rename = "PaymentCard")]
    PaymentCard { card: CardData },
    #[serde(rename = "ApplePay")]
    ApplePay(ApplePayWalletDetails),
    #[serde(rename = "DecryptedWallet")]
    DecryptedWallet(DecryptedWalletDetails),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayWalletDetails {
    pub data: Secret<String>,
    pub header: ApplePayHeader,
    pub signature: Secret<String>,
    pub version: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub application_data: Option<Secret<String>>,
    pub apple_pay_merchant_id: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayHeader {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub application_data_hash: Option<Secret<String>>,
    pub ephemeral_public_key: Secret<String>,
    pub public_key_hash: Secret<String>,
    pub transaction_id: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DecryptedWalletDetails {
    pub card: WalletCardData,
    #[serde(rename = "cavv")]
    pub cryptogram: Secret<String>,
    #[serde(rename = "xid")]
    pub transaction_id: Secret<String>,
    pub wallet_type: FiservWalletType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservWalletType {
    ApplePay,
    GooglePay,
    PaypalWallet,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayData {
    data: Secret<String>,
    signature: Secret<String>,
    version: String,
    intermediate_signing_key: IntermediateSigningKey,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardData {
    card_data: cards::CardNumber,
    expiration_month: Secret<String>,
    expiration_year: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    security_code: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WalletCardData {
    card_data: cards::CardNumber,
    expiration_month: Secret<String>,
    expiration_year: Secret<String>,
}

#[derive(Default, Debug, Serialize)]
pub struct Amount {
    total: FloatMajorUnit,
    currency: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionDetails {
    #[serde(skip_serializing_if = "Option::is_none")]
    capture_flag: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    reversal_reason_code: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    merchant_transaction_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    operation_type: Option<OperationType>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum OperationType {
    Create,
    Capture,
    Authorize,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerchantDetails {
    merchant_id: Secret<String>,
    terminal_id: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionInteraction {
    origin: TransactionInteractionOrigin,
    eci_indicator: TransactionInteractionEciIndicator,
    pos_condition_code: TransactionInteractionPosConditionCode,
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TransactionInteractionOrigin {
    #[default]
    Ecom,
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TransactionInteractionEciIndicator {
    #[default]
    ChannelEncrypted,
}
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TransactionInteractionPosConditionCode {
    #[default]
    CardNotPresentEcom,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IntermediateSigningKey {
    signed_key: Secret<String>,
    signatures: Vec<Secret<String>>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedKey {
    key_value: String,
    key_expiration: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedMessage {
    encrypted_message: String,
    ephemeral_public_key: String,
    tag: String,
}

#[derive(Debug, Default)]
pub struct FullyParsedGooglePayToken {
    pub signature: Secret<String>,
    pub protocol_version: String,
    pub encrypted_message: String,
    pub ephemeral_public_key: String,
    pub tag: String,
    pub key_value: String,
    pub key_expiration: String,
    pub signatures: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RawGooglePayToken {
    pub signature: Secret<String>,
    pub protocol_version: String,
    pub signed_message: Secret<String>,
    pub intermediate_signing_key: IntermediateSigningKey,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ApplePayDecryptedData {
    pub data: Secret<String>,
    pub signature: Secret<String>,
    pub version: Secret<String>,
    pub header: ApplePayHeader,
}

pub fn parse_googlepay_token_safely(token_json_str: &str) -> FullyParsedGooglePayToken {
    let mut result = FullyParsedGooglePayToken::default();

    if let Ok(raw_token) = serde_json::from_str::<RawGooglePayToken>(token_json_str) {
        result.signature = raw_token.signature;
        result.protocol_version = raw_token.protocol_version;
        result.signatures = raw_token
            .intermediate_signing_key
            .signatures
            .into_iter()
            .map(|s| s.expose().to_owned())
            .collect();

        if let Ok(key) = serde_json::from_str::<SignedKey>(
            &raw_token.intermediate_signing_key.signed_key.expose(),
        ) {
            result.key_value = key.key_value;
            result.key_expiration = key.key_expiration;
        }

        if let Ok(message) =
            serde_json::from_str::<SignedMessage>(&raw_token.signed_message.expose())
        {
            result.encrypted_message = message.encrypted_message;
            result.ephemeral_public_key = message.ephemeral_public_key;
            result.tag = message.tag;
        }
    }

    result
}

impl TryFrom<&FiservRouterData<&types::PaymentsAuthorizeRouterData>> for FiservPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &FiservRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let auth: FiservAuthType = FiservAuthType::try_from(&item.router_data.connector_auth_type)?;
        let amount = Amount {
            total: item.amount,
            currency: item.router_data.request.currency.to_string(),
        };
        let metadata = item.router_data.get_connector_meta()?.clone();
        let session: FiservSessionObject = metadata
            .expose()
            .parse_value("FiservSessionObject")
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "Merchant connector account metadata",
            })?;

        let merchant_details = MerchantDetails {
            merchant_id: auth.merchant_account,
            terminal_id: Some(session.terminal_id),
        };

        let checkout_charges_request = match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(ref ccard) => {
                Ok(FiservCheckoutChargesRequest::Charges(
                    ChargesPaymentRequest {
                        source: Source::PaymentCard {
                            card: CardData {
                                card_data: ccard.card_number.clone(),
                                expiration_month: ccard.card_exp_month.clone(),
                                expiration_year: ccard.get_expiry_year_4_digit(),
                                security_code: Some(ccard.card_cvc.clone()),
                            },
                        },
                        transaction_details: TransactionDetails {
                            capture_flag: Some(matches!(
                                item.router_data.request.capture_method,
                                Some(enums::CaptureMethod::Automatic)
                                    | Some(enums::CaptureMethod::SequentialAutomatic)
                                    | None
                            )),
                            reversal_reason_code: None,
                            merchant_transaction_id: Some(
                                item.router_data.connector_request_reference_id.clone(),
                            ),
                            operation_type: None,
                        },
                        transaction_interaction: Some(TransactionInteraction {
                            //Payment is being made in online mode, card not present
                            origin: TransactionInteractionOrigin::Ecom,
                            // transaction encryption such as SSL/TLS, but authentication was not performed
                            eci_indicator: TransactionInteractionEciIndicator::ChannelEncrypted,
                            //card not present in online transaction
                            pos_condition_code:
                                TransactionInteractionPosConditionCode::CardNotPresentEcom,
                        }),
                    },
                ))
            }
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                WalletData::GooglePay(data) => {
                    let token_string = data
                        .tokenization_data
                        .get_encrypted_google_pay_token()
                        .change_context(errors::ConnectorError::MissingRequiredField {
                            field_name: "gpay wallet_token",
                        })?
                        .to_owned();

                    let parsed = parse_googlepay_token_safely(&token_string);

                    Ok(FiservCheckoutChargesRequest::Charges(
                        ChargesPaymentRequest {
                            source: Source::GooglePay(GooglePayData {
                                data: Secret::new(parsed.encrypted_message),
                                signature: Secret::new(parsed.signature.expose().to_owned()),
                                version: parsed.protocol_version,
                                intermediate_signing_key: IntermediateSigningKey {
                                    signed_key: Secret::new(
                                        serde_json::json!({
                                            "keyValue": parsed.key_value,
                                            "keyExpiration": parsed.key_expiration
                                        })
                                        .to_string(),
                                    ),
                                    signatures: parsed
                                        .signatures
                                        .into_iter()
                                        .map(|s| Secret::new(s.to_owned()))
                                        .collect(),
                                },
                            }),
                            transaction_details: TransactionDetails {
                                capture_flag: Some(matches!(
                                    item.router_data.request.capture_method,
                                    Some(enums::CaptureMethod::Automatic)
                                        | Some(enums::CaptureMethod::SequentialAutomatic)
                                        | None
                                )),
                                reversal_reason_code: None,
                                merchant_transaction_id: Some(
                                    item.router_data.connector_request_reference_id.clone(),
                                ),
                                operation_type: None,
                            },
                            transaction_interaction: None,
                        },
                    ))
                }
                WalletData::PaypalRedirect(_) => {
                    let return_url = item
                        .router_data
                        .request
                        .complete_authorize_url
                        .clone()
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "return_url",
                        })?;
                    Ok(FiservCheckoutChargesRequest::Checkout(
                        CheckoutPaymentsRequest {
                            payment_method: FiservPaymentMethod {
                                provider: FiservWallet::PayPal,
                                wallet_type: FiservWalletType::PaypalWallet,
                            },
                            order: FiservOrder {
                                intent: FiservIntent::Authorize,
                            },
                            interactions: FiservInteractions {
                                channel: FiservChannel::Web,
                                customer_confirmation: FiservCustomerConfirmation::ReviewAndPay,
                                payment_initiator: FiservPaymentInitiator::Merchant,
                                return_urls: FiservReturnUrls {
                                    success_url: return_url.clone(),
                                    cancel_url: return_url,
                                },
                            },
                            transaction_details: TransactionDetails {
                                operation_type: Some(OperationType::Create),
                                capture_flag: Some(matches!(
                                    item.router_data.request.capture_method,
                                    Some(enums::CaptureMethod::Automatic)
                                        | Some(enums::CaptureMethod::SequentialAutomatic)
                                        | None
                                )),
                                reversal_reason_code: None,
                                merchant_transaction_id: Some(
                                    item.router_data.connector_request_reference_id.clone(),
                                ),
                            },
                        },
                    ))
                }
                WalletData::ApplePay(apple_pay_data) => match item
                    .router_data
                    .payment_method_token
                    .clone()
                {
                    Some(PaymentMethodToken::ApplePayDecrypt(pre_decrypt_data)) => Ok(
                        FiservCheckoutChargesRequest::Charges(ChargesPaymentRequest {
                            source: Source::DecryptedWallet(DecryptedWalletDetails {
                                wallet_type: FiservWalletType::ApplePay,
                                cryptogram: pre_decrypt_data
                                    .payment_data
                                    .online_payment_cryptogram
                                    .clone(),
                                transaction_id: Secret::new(apple_pay_data.transaction_identifier),
                                card: WalletCardData {
                                    card_data: pre_decrypt_data
                                        .application_primary_account_number
                                        .clone(),
                                    expiration_month: pre_decrypt_data
                                        .get_expiry_month()
                                        .change_context(
                                            errors::ConnectorError::MissingRequiredField {
                                                field_name: "apple_pay_expiry_month",
                                            },
                                        )?,
                                    expiration_year: pre_decrypt_data.get_four_digit_expiry_year(),
                                },
                            }),
                            transaction_details: TransactionDetails {
                                capture_flag: Some(matches!(
                                    item.router_data.request.capture_method,
                                    Some(enums::CaptureMethod::Automatic)
                                        | Some(enums::CaptureMethod::SequentialAutomatic)
                                        | None
                                )),
                                reversal_reason_code: None,
                                merchant_transaction_id: Some(
                                    item.router_data.connector_request_reference_id.clone(),
                                ),
                                operation_type: None,
                            },
                            transaction_interaction: None,
                        }),
                    ),
                    _ => {
                        let decoded_bytes = match apple_pay_data.payment_data {
                            common_types::payments::ApplePayPaymentData::Encrypted(
                                ref encrypted_str,
                            ) => BASE64_ENGINE
                                .decode(encrypted_str)
                                .change_context(errors::ConnectorError::ParsingFailed)?,
                            _ => {
                                return Err(errors::ConnectorError::ParsingFailed.into());
                            }
                        };

                        let payment_data_decoded: ApplePayDecryptedData =
                            serde_json::from_slice(&decoded_bytes)
                                .change_context(errors::ConnectorError::ParsingFailed)?;

                        let data = payment_data_decoded.data;
                        let signature = payment_data_decoded.signature;
                        let version = payment_data_decoded.version;

                        let header = ApplePayHeader {
                            ephemeral_public_key: payment_data_decoded.header.ephemeral_public_key,
                            public_key_hash: payment_data_decoded.header.public_key_hash,
                            transaction_id: payment_data_decoded.header.transaction_id,
                            application_data_hash: None,
                        };

                        let apple_pay_metadata = item.router_data.get_connector_meta()?.expose();
                        let applepay_metadata = apple_pay_metadata
                            .clone()
                            .parse_value::<ApplepayCombinedSessionTokenData>(
                                "ApplepayCombinedSessionTokenData",
                            )
                            .map(|combined_metadata| {
                                ApplepaySessionTokenMetadata::ApplePayCombined(
                                    combined_metadata.apple_pay_combined,
                                )
                            })
                            .or_else(|_| {
                                apple_pay_metadata
                                    .parse_value::<ApplepaySessionTokenData>(
                                        "ApplepaySessionTokenData",
                                    )
                                    .map(|old_metadata| {
                                        ApplepaySessionTokenMetadata::ApplePay(
                                            old_metadata.apple_pay,
                                        )
                                    })
                            })
                            .change_context(errors::ConnectorError::ParsingFailed)?;

                        let merchant_identifier = match applepay_metadata {
                            ApplepaySessionTokenMetadata::ApplePayCombined(ref combined) => {
                                match combined {
                                    ApplePayCombinedMetadata::Simplified { .. } => {
                                        return Err(
                                            errors::ConnectorError::MissingApplePayTokenData.into(),
                                        )
                                    }
                                    ApplePayCombinedMetadata::Manual {
                                        session_token_data, ..
                                    } => &session_token_data.merchant_identifier,
                                }
                            }
                            ApplepaySessionTokenMetadata::ApplePay(ref data) => {
                                &data.session_token_data.merchant_identifier
                            }
                        };

                        Ok(FiservCheckoutChargesRequest::Charges(
                            ChargesPaymentRequest {
                                source: Source::ApplePay(ApplePayWalletDetails {
                                    data,
                                    header,
                                    signature,
                                    version,
                                    application_data: None,
                                    apple_pay_merchant_id: Secret::new(
                                        merchant_identifier.to_owned(),
                                    ),
                                }),
                                transaction_details: TransactionDetails {
                                    capture_flag: Some(matches!(
                                        item.router_data.request.capture_method,
                                        Some(enums::CaptureMethod::Automatic)
                                            | Some(enums::CaptureMethod::SequentialAutomatic)
                                            | None
                                    )),
                                    reversal_reason_code: None,
                                    merchant_transaction_id: Some(
                                        item.router_data.connector_request_reference_id.clone(),
                                    ),
                                    operation_type: None,
                                },
                                transaction_interaction: None,
                            },
                        ))
                    }
                },
                _ => Err(error_stack::report!(
                    errors::ConnectorError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("fiserv"),
                    )
                )),
            },
            PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Err(
                error_stack::report!(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("fiserv"),
                )),
            ),
        }?;

        Ok(Self {
            amount,
            checkout_charges_request,
            merchant_details,
        })
    }
}

pub struct FiservAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) merchant_account: Secret<String>,
    pub(super) api_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for FiservAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::SignatureKey {
            api_key,
            key1,
            api_secret,
        } = auth_type
        {
            Ok(Self {
                api_key: api_key.to_owned(),
                merchant_account: key1.to_owned(),
                api_secret: api_secret.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType)?
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservCancelRequest {
    transaction_details: TransactionDetails,
    merchant_details: MerchantDetails,
    reference_transaction_details: ReferenceTransactionDetails,
}

impl TryFrom<&types::PaymentsCancelRouterData> for FiservCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let auth: FiservAuthType = FiservAuthType::try_from(&item.connector_auth_type)?;
        let metadata = item.get_connector_meta()?.clone();
        let session: FiservSessionObject = metadata
            .expose()
            .parse_value("FiservSessionObject")
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "Merchant connector account metadata",
            })?;
        Ok(Self {
            merchant_details: MerchantDetails {
                merchant_id: auth.merchant_account,
                terminal_id: Some(session.terminal_id),
            },
            reference_transaction_details: ReferenceTransactionDetails {
                reference_transaction_id: item.request.connector_transaction_id.to_string(),
            },
            transaction_details: TransactionDetails {
                capture_flag: None,
                reversal_reason_code: Some(item.request.get_cancellation_reason()?),
                merchant_transaction_id: Some(item.connector_request_reference_id.clone()),
                operation_type: None,
            },
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
    pub error: Option<Vec<ErrorDetails>>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorDetails {
    #[serde(rename = "type")]
    pub error_type: Option<String>,
    pub code: Option<String>,
    pub field: Option<String>,
    pub message: Option<String>,
    pub additional_info: Option<String>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservPaymentStatus {
    Succeeded,
    Failed,
    Captured,
    Declined,
    Voided,
    Authorized,
    #[default]
    Processing,
    Created,
}

impl From<FiservPaymentStatus> for enums::AttemptStatus {
    fn from(item: FiservPaymentStatus) -> Self {
        match item {
            FiservPaymentStatus::Captured | FiservPaymentStatus::Succeeded => Self::Charged,
            FiservPaymentStatus::Declined | FiservPaymentStatus::Failed => Self::Failure,
            FiservPaymentStatus::Processing => Self::Authorizing,
            FiservPaymentStatus::Voided => Self::Voided,
            FiservPaymentStatus::Authorized => Self::Authorized,
            FiservPaymentStatus::Created => Self::AuthenticationPending,
        }
    }
}

impl From<FiservPaymentStatus> for enums::RefundStatus {
    fn from(item: FiservPaymentStatus) -> Self {
        match item {
            FiservPaymentStatus::Succeeded
            | FiservPaymentStatus::Authorized
            | FiservPaymentStatus::Captured => Self::Success,
            FiservPaymentStatus::Declined | FiservPaymentStatus::Failed => Self::Failure,
            FiservPaymentStatus::Voided
            | FiservPaymentStatus::Processing
            | FiservPaymentStatus::Created => Self::Pending,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessorResponseDetails {
    pub approval_status: Option<String>,
    pub approval_code: Option<String>,
    pub reference_number: Option<String>,
    pub processor: Option<String>,
    pub host: Option<String>,
    pub network_routed: Option<String>,
    pub network_international_id: Option<String>,
    pub response_code: Option<String>,
    pub response_message: Option<String>,
    pub host_response_code: Option<String>,
    pub host_response_message: Option<String>,
    pub additional_info: Option<Vec<AdditionalInfo>>,
    pub bank_association_details: Option<BankAssociationDetails>,
    pub response_indicators: Option<ResponseIndicators>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdditionalInfo {
    pub name: Option<String>,
    pub value: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BankAssociationDetails {
    pub association_response_code: Option<String>,
    pub avs_security_code_response: Option<AvsSecurityCodeResponse>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AvsSecurityCodeResponse {
    pub street_match: Option<String>,
    pub postal_code_match: Option<String>,
    pub security_code_match: Option<String>,
    pub association: Option<Association>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Association {
    pub avs_code: Option<String>,
    pub security_code_response: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResponseIndicators {
    pub alternate_route_debit_indicator: Option<bool>,
    pub signature_line_indicator: Option<bool>,
    pub signature_debit_route_indicator: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservChargesResponse {
    pub gateway_response: GatewayResponse,
    pub payment_receipt: PaymentReceipt,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservCheckoutResponse {
    pub gateway_response: GatewayResponse,
    pub payment_receipt: PaymentReceipt,
    pub interactions: FiservResponseInteractions,
    pub order: Option<FiservResponseOrders>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservResponseInteractions {
    actions: FiservResponseActions,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservResponseActions {
    #[serde(rename = "type")]
    action_type: String,
    url: url::Url,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservResponseOrders {
    intent: FiservIntent,
    order_id: String,
    order_status: FiservOrderStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum FiservOrderStatus {
    PayerActionRequired,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FiservPaymentsResponse {
    Charges(FiservChargesResponse),
    Checkout(FiservCheckoutResponse),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentReceipt {
    pub approved_amount: ApprovedAmount,
    pub processor_response_details: Option<ProcessorResponseDetails>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApprovedAmount {
    pub total: FloatMajorUnit,
    pub currency: Currency,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(transparent)]
pub struct FiservSyncResponse {
    pub sync_responses: Vec<FiservPaymentsResponse>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GatewayResponse {
    gateway_transaction_id: Option<String>,
    transaction_state: FiservPaymentStatus,
    transaction_processing_details: TransactionProcessingDetails,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionProcessingDetails {
    order_id: String,
    transaction_id: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, FiservPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, FiservPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (gateway_resp, redirect_url, order_id) = match &item.response {
            FiservPaymentsResponse::Charges(res) => (res.gateway_response.clone(), None, None),
            FiservPaymentsResponse::Checkout(res) => (
                res.gateway_response.clone(),
                Some(res.interactions.actions.url.clone()),
                res.order.as_ref().map(|o| o.order_id.clone()),
            ),
        };

        let redirection_data = redirect_url.map(|url| RedirectForm::from((url, Method::Get)));

        let connector_metadata: Option<serde_json::Value> = Some(serde_json::json!({
            "order_id": order_id,
        }));

        Ok(Self {
            status: enums::AttemptStatus::from(gateway_resp.transaction_state),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(
                    gateway_resp.transaction_processing_details.transaction_id,
                ),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata,
                network_txn_id: None,
                connector_response_reference_id: Some(
                    gateway_resp.transaction_processing_details.order_id,
                ),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, FiservSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, FiservSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let gateway_resp = match item.response.sync_responses.first() {
            Some(gateway_response) => gateway_response,
            None => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };

        let connector_response_reference_id = match gateway_resp {
            FiservPaymentsResponse::Charges(res) => {
                &res.gateway_response.transaction_processing_details.order_id
            }
            FiservPaymentsResponse::Checkout(res) => {
                &res.gateway_response.transaction_processing_details.order_id
            }
        };

        let transaction_id = match gateway_resp {
            FiservPaymentsResponse::Charges(res) => {
                &res.gateway_response
                    .transaction_processing_details
                    .transaction_id
            }
            FiservPaymentsResponse::Checkout(res) => {
                &res.gateway_response
                    .transaction_processing_details
                    .transaction_id
            }
        };

        let transaction_state = match gateway_resp {
            FiservPaymentsResponse::Charges(res) => &res.gateway_response.transaction_state,
            FiservPaymentsResponse::Checkout(res) => &res.gateway_response.transaction_state,
        };

        Ok(Self {
            status: enums::AttemptStatus::from(transaction_state.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(transaction_id.to_string()),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(connector_response_reference_id.to_string()),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservCaptureRequest {
    amount: Amount,
    transaction_details: TransactionDetails,
    merchant_details: MerchantDetails,
    reference_transaction_details: ReferenceTransactionDetails,
    #[serde(skip_serializing_if = "Option::is_none")]
    order: Option<FiservOrderRequest>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FiservOrderRequest {
    order_id: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReferenceTransactionDetails {
    reference_transaction_id: String,
}

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

impl TryFrom<&Option<pii::SecretSerdeValue>> for FiservSessionObject {
    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<&FiservRouterData<&types::PaymentsCaptureRouterData>> for FiservCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &FiservRouterData<&types::PaymentsCaptureRouterData>,
    ) -> Result<Self, Self::Error> {
        let auth: FiservAuthType = FiservAuthType::try_from(&item.router_data.connector_auth_type)?;
        let metadata = item
            .router_data
            .connector_meta_data
            .clone()
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        let session: FiservSessionObject = metadata
            .expose()
            .parse_value("FiservSessionObject")
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "Merchant connector account metadata",
            })?;

        let order_id = item
            .router_data
            .request
            .connector_meta
            .as_ref()
            .and_then(|v| v.get("order_id"))
            .and_then(|v| v.as_str())
            .map(|s| s.to_string());

        Ok(Self {
            amount: Amount {
                total: item.amount,
                currency: item.router_data.request.currency.to_string(),
            },
            order: Some(FiservOrderRequest { order_id }),
            transaction_details: TransactionDetails {
                capture_flag: Some(true),
                reversal_reason_code: None,
                merchant_transaction_id: Some(
                    item.router_data.connector_request_reference_id.clone(),
                ),
                operation_type: Some(OperationType::Capture),
            },
            merchant_details: MerchantDetails {
                merchant_id: auth.merchant_account,
                terminal_id: Some(session.terminal_id),
            },


// Module Structure
// File: crates/router/src/core/files.rs
// Module: router

// Public submodules:
pub mod helpers;


// Struct: ChargebeeItem
// File: crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ChargebeeItem


// File: crates/hyperswitch_connectors/src/connectors/novalnet.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 1

pub mod transformers;
use core::str;
use std::{collections::HashSet, sync::LazyLock};

use base64::Engine;
use common_enums::enums;
use common_utils::{
    crypto,
    errors::CustomResult,
    ext_traits::{ByteSliceExt, BytesExt},
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation,
    },
    configs::Connectors,
    disputes, errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, Response},
    webhooks,
};
use masking::{ExposeInterface, Mask};
use transformers as novalnet;

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

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

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

impl api::Payment for Novalnet {}
impl api::PaymentSession for Novalnet {}
impl api::ConnectorAccessToken for Novalnet {}
impl api::MandateSetup for Novalnet {}
impl api::PaymentAuthorize for Novalnet {}
impl api::PaymentSync for Novalnet {}
impl api::PaymentCapture for Novalnet {}
impl api::PaymentVoid for Novalnet {}
impl api::Refund for Novalnet {}
impl api::RefundExecute for Novalnet {}
impl api::RefundSync for Novalnet {}
impl api::PaymentToken for Novalnet {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Novalnet
{
    // Not Implemented (R)
}

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

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

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

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

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

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = novalnet::NovalnetAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let api_key: String = auth.payment_access_key.expose();
        let encoded_api_key = common_utils::consts::BASE64_ENGINE.encode(api_key);
        Ok(vec![(
            headers::X_NN_ACCESS_KEY.to_string(),
            encoded_api_key.into_masked(),
        )])
    }

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

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

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.code,
            message: response.message,
            reason: response.reason,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Novalnet {
    fn validate_psync_reference_id(
        &self,
        data: &PaymentsSyncData,
        _is_three_ds: bool,
        _status: enums::AttemptStatus,
        _connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        if data.encoded_data.is_some()
            || data
                .connector_transaction_id
                .get_connector_transaction_id()
                .is_ok()
        {
            return Ok(());
        }

        Err(errors::ConnectorError::MissingConnectorTransactionID.into())
    }
    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd: HashSet<PaymentMethodDataType> = HashSet::from([
            PaymentMethodDataType::Card,
            PaymentMethodDataType::GooglePay,
            PaymentMethodDataType::PaypalRedirect,
            PaymentMethodDataType::ApplePay,
        ]);
        utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}

impl ConnectorRedirectResponse for Novalnet {
    fn get_flow_type(
        &self,
        _query_params: &str,
        _json_payload: Option<serde_json::Value>,
        action: enums::PaymentAction,
    ) -> CustomResult<enums::CallConnectorAction, errors::ConnectorError> {
        match action {
            enums::PaymentAction::PSync => Ok(enums::CallConnectorAction::Trigger),
            _ => Ok(enums::CallConnectorAction::Avoid),
        }
    }
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Novalnet {
    //TODO: implement sessions flow
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Novalnet {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
    for Novalnet
{
    fn get_headers(
        &self,
        req: &SetupMandateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &SetupMandateRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/payment"))
    }

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

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

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

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

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

    fn get_url(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        match req.request.is_auto_capture()? {
            true => Ok(format!("{endpoint}/payment")),
            false => Ok(format!("{endpoint}/authorize")),
        }
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;

        let connector_router_data = novalnet::NovalnetRouterData::from((amount, req));
        let connector_req = novalnet::NovalnetPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

    fn get_url(
        &self,
        _req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/transaction/details"))
    }

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

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

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

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

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

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

    fn get_url(
        &self,
        _req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/transaction/capture"))
    }

    fn get_request_body(
        &self,
        req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount_to_capture,
            req.request.currency,
        )?;

        let connector_router_data = novalnet::NovalnetRouterData::from((amount, req));
        let connector_req = novalnet::NovalnetCaptureRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/transaction/refund"))
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let refund_amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_refund_amount,
            req.request.currency,
        )?;

        let connector_router_data = novalnet::NovalnetRouterData::from((refund_amount, req));
        let connector_req = novalnet::NovalnetRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::RefundExecuteType::get_headers(
                self, req, connectors,
            )?)
            .set_body(types::RefundExecuteType::get_request_body(
                self, req, connectors,
            )?)
            .build();
        Ok(Some(request))
    }

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

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

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

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

    fn get_url(
        &self,
        _req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/transaction/details"))
    }

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

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

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

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

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

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

    fn get_url(
        &self,
        _req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let endpoint = self.base_url(connectors);
        Ok(format!("{endpoint}/transaction/cancel"))
    }

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

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

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

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

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

fn get_webhook_object_from_body(
    body: &[u8],
) -> CustomResult<novalnet::NovalnetWebhookNotificationResponse, errors::ConnectorError> {
    let novalnet_webhook_notification_response = body
        .parse_struct("NovalnetWebhookNotificationResponse")
        .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

    Ok(novalnet_webhook_notification_response)
}

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

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

        hex::decode(notif_item.event.checksum)
            .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid)
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let notif = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
        let (amount, currency) = match notif.transaction {
            novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => {
                (data.amount, data.currency)
            }
            novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => {
                (data.amount, data.currency)
            }

            novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => {
                (data.amount, data.currency)
            }

            novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => {
                (data.amount, data.currency)
            }
        };
        let amount = amount
            .map(|amount| amount.to_string())
            .unwrap_or("".to_string());
        let currency = currency
            .map(|amount| amount.to_string())
            .unwrap_or("".to_string());

        let secret_auth = String::from_utf8(connector_webhook_secrets.secret.to_vec())
            .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid)
            .attach_printable("Could not convert webhook secret auth to UTF-8")?;
        let reversed_secret_auth = novalnet::reverse_string(&secret_auth);

        let message = format!(
            "{}{}{}{}{}{}",
            notif.event.tid,
            notif.event.event_type,
            notif.result.status,
            amount,
            currency,
            reversed_secret_auth
        );

        Ok(message.into_bytes())
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let notif = get_webhook_object_from_body(request.body)
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        let transaction_order_no = match notif.transaction {
            novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => data.order_no,
            novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => data.order_no,
            novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => data.order_no,
            novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => data.order_no,
        };

        if novalnet::is_refund_event(&notif.event.event_type) {
            Ok(api_models::webhooks::ObjectReferenceId::RefundId(
                api_models::webhooks::RefundIdType::ConnectorRefundId(notif.event.tid.to_string()),
            ))
        } else {
            match transaction_order_no {
                Some(order_no) => Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::PaymentAttemptId(order_no),
                )),
                None => Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::ConnectorTransactionId(
                        notif.event.tid.to_string(),
                    ),
                )),
            }
        }
    }

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

        let optional_transaction_status = match notif.transaction {
            novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => {
                Some(data.status)
            }
            novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => data.status,
            novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => {
                Some(data.status)
            }
            novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => {
                Some(data.status)
            }
        };

        let transaction_status =
            optional_transaction_status.ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "transaction_status",
            })?;
        // NOTE: transaction_status will always be present for Webhooks
        // But we are handling optional type here, since we are reusing TransactionData Struct from NovalnetPaymentsResponseTransactionData for Webhooks response too
        // In NovalnetPaymentsResponseTransactionData, transaction_status is optional

        let incoming_webhook_event =
            novalnet::get_incoming_webhook_event(notif.event.event_type, transaction_status);
        Ok(incoming_webhook_event)
    }

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

    fn get_dispute_details(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<disputes::DisputePayload, errors::ConnectorError> {
        let notif: transformers::NovalnetWebhookNotificationResponse =
            get_webhook_object_from_body(request.body)
                .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let (amount, currency, reason, reason_code) = match notif.transaction {
            novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => {
                (data.amount, data.currency, None, None)
            }
            novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => {
                (data.amount, data.currency, None, None)
            }

            novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => {
                (data.amount, data.currency, None, None)
            }

            novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => {
                (data.amount, data.currency, data.reason, data.reason_code)
            }
        };

        let dispute_status =
            novalnet::get_novalnet_dispute_status(notif.event.event_type).to_string();
        Ok(disputes::DisputePayload {
            amount: utils::convert_amount(
                self.amount_converter,
                amount.ok_or(errors::ConnectorError::AmountConversionFailed)?,
                novalnet::option_to_result(currency)?,
            )?,
            currency: novalnet::option_to_result(currency)?,
            dispute_stage: api_models::enums::DisputeStage::Dispute,
            connector_dispute_id: notif.event.tid.to_string(),
            connector_reason: reason,
            connector_reason_code: reason_code,
            challenge_required_by: None,
            connector_status: dispute_status,
            created_at: None,
            updated_at: None,
        })
    }
}

static NOVALNET_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> =
    LazyLock::new(|| {
        let supported_capture_methods = vec![
            enums::CaptureMethod::Automatic,
            enums::CaptureMethod::Manual,
            enums::CaptureMethod::SequentialAutomatic,
        ];

        let supported_card_network = vec![
            common_enums::CardNetwork::Visa,
            common_enums::CardNetwork::Mastercard,
            common_enums::CardNetwork::AmericanExpress,
            common_enums::CardNetwork::JCB,
            common_enums::CardNetwork::DinersClub,
            common_enums::CardNetwork::UnionPay,
            common_enums::CardNetwork::Discover,
            common_enums::CardNetwork::CartesBancaires,
            common_enums::CardNetwork::Interac,
        ];

        let mut novalnet_supported_payment_methods = SupportedPaymentMethods::new();

        novalnet_supported_payment_methods.add(
            enums::PaymentMethod::Card,
            enums::PaymentMethodType::Credit,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::Supported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods.clone(),
                specific_features: Some(
                    api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({


// Function: construct_relay_refund_router_data
// File: crates/router/src/core/relay/utils.rs
// Module: router

pub fn construct_relay_refund_router_data<F>(
    state: &SessionState,
    merchant_id: &id_type::MerchantId,
    connector_account: &domain::MerchantConnectorAccount,
    relay_record: &hyperswitch_domain_models::relay::Relay,
) -> RouterResult<types::RefundsRouterData<F>>


// Implementation: impl KafkaMessage for for KafkaConsolidatedEvent
// File: crates/router/src/services/kafka.rs
// Module: router
// Methods: 3 total (0 public)

impl KafkaMessage for for KafkaConsolidatedEvent


// Implementation: impl ConnectorAccessToken for for Payone
// File: crates/hyperswitch_connectors/src/connectors/payone.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorAccessToken for for Payone


// Function: set_card_details
// File: crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs
// Module: router

pub fn set_card_details(
        self,
        card_from_locker: &'a api_models::payment_methods::Card,
        optional_card_info: Option<diesel_models::CardInfo>,
        card_cvc: Option<Secret<String>>,
    ) -> NetworkTokenizationBuilder<'a, PmAssigned>


// Struct: AdminApiAuthWithMerchantIdFromRoute
// File: crates/router/src/services/authentication.rs
// Module: router
// Implementations: 0

pub struct AdminApiAuthWithMerchantIdFromRoute


// Function: get_pm_filters_cgraph_key
// File: crates/common_utils/src/id_type/merchant.rs
// Module: common_utils

// Documentation: get_pm_filters_cgraph_key

pub fn get_pm_filters_cgraph_key(&self) -> String


// Function: open_router_config_default
// File: crates/api_models/src/routing.rs
// Module: api_models

pub fn open_router_config_default() -> Self


// Implementation: impl api::Refund for for Nexixpay
// File: crates/hyperswitch_connectors/src/connectors/nexixpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Refund for for Nexixpay


// Struct: HelcimPaymentsErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct HelcimPaymentsErrorResponse


// Struct: ClientReferenceInformation
// File: crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ClientReferenceInformation


// Function: contains_key
// File: crates/hyperswitch_constraint_graph/src/dense_map.rs
// Module: hyperswitch_constraint_graph

pub fn contains_key(&self, key: K) -> bool


// Struct: ZenAuthType
// File: crates/hyperswitch_connectors/src/connectors/zen/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ZenAuthType


// File: crates/diesel_models/src/query/user_role.rs
// Module: diesel_models
// Public functions: 7

use async_bb8_diesel::AsyncRunQueryDsl;
use common_utils::id_type;
use diesel::{
    associations::HasTable,
    debug_query,
    pg::Pg,
    result::Error as DieselError,
    sql_types::{Bool, Nullable},
    BoolExpressionMethods, ExpressionMethods, QueryDsl,
};
use error_stack::{report, ResultExt};

use crate::{
    enums::{UserRoleVersion, UserStatus},
    errors,
    query::generics,
    schema::user_roles::dsl,
    user_role::*,
    PgPooledConn, StorageResult,
};

impl UserRoleNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UserRole> {
        generics::generic_insert(conn, self).await
    }
}

impl UserRole {
    fn check_user_in_lineage(
        tenant_id: id_type::TenantId,
        org_id: Option<id_type::OrganizationId>,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
    ) -> Box<
        dyn diesel::BoxableExpression<<Self as HasTable>::Table, Pg, SqlType = Nullable<Bool>>
            + 'static,
    > {
        // Checking in user roles, for a user in token hierarchy, only one of the relations will be true:
        // either tenant level, org level, merchant level, or profile level
        // Tenant-level: (tenant_id = ? && org_id = null && merchant_id = null && profile_id = null)
        // Org-level: (org_id = ? && merchant_id = null && profile_id = null)
        // Merchant-level: (org_id = ? && merchant_id = ? && profile_id = null)
        // Profile-level: (org_id = ? && merchant_id = ? && profile_id = ?)
        Box::new(
            // Tenant-level condition
            dsl::tenant_id
                .eq(tenant_id.clone())
                .and(dsl::org_id.is_null())
                .and(dsl::merchant_id.is_null())
                .and(dsl::profile_id.is_null())
                .or(
                    // Org-level condition
                    dsl::tenant_id
                        .eq(tenant_id.clone())
                        .and(dsl::org_id.eq(org_id.clone()))
                        .and(dsl::merchant_id.is_null())
                        .and(dsl::profile_id.is_null()),
                )
                .or(
                    // Merchant-level condition
                    dsl::tenant_id
                        .eq(tenant_id.clone())
                        .and(dsl::org_id.eq(org_id.clone()))
                        .and(dsl::merchant_id.eq(merchant_id.clone()))
                        .and(dsl::profile_id.is_null()),
                )
                .or(
                    // Profile-level condition
                    dsl::tenant_id
                        .eq(tenant_id)
                        .and(dsl::org_id.eq(org_id))
                        .and(dsl::merchant_id.eq(merchant_id))
                        .and(dsl::profile_id.eq(profile_id)),
                ),
        )
    }

    pub async fn find_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = Self::check_user_in_lineage(
            tenant_id,
            Some(org_id),
            Some(merchant_id),
            Some(profile_id),
        );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, predicate).await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn update_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        update: UserRoleUpdate,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = dsl::tenant_id
            .eq(tenant_id.clone())
            .and(dsl::org_id.is_null())
            .and(dsl::merchant_id.is_null())
            .and(dsl::profile_id.is_null())
            .or(
                // Org-level condition
                dsl::tenant_id
                    .eq(tenant_id.clone())
                    .and(dsl::org_id.eq(org_id.clone()))
                    .and(dsl::merchant_id.is_null())
                    .and(dsl::profile_id.is_null()),
            )
            .or(
                // Merchant-level condition
                dsl::tenant_id
                    .eq(tenant_id.clone())
                    .and(dsl::org_id.eq(org_id.clone()))
                    .and(dsl::merchant_id.eq(merchant_id.clone()))
                    .and(dsl::profile_id.is_null()),
            )
            .or(
                // Profile-level condition
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .and(dsl::merchant_id.eq(merchant_id))
                    .and(dsl::profile_id.eq(profile_id)),
            );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            UserRoleUpdateInternal,
            _,
            _,
        >(conn, predicate, update.into())
        .await
    }

    pub async fn delete_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = Self::check_user_in_lineage(
            tenant_id,
            Some(org_id),
            Some(merchant_id),
            Some(profile_id),
        );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(conn, predicate)
            .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn generic_user_roles_list_for_user(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: Option<id_type::OrganizationId>,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        entity_id: Option<String>,
        status: Option<UserStatus>,
        version: Option<UserRoleVersion>,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::user_id.eq(user_id).and(dsl::tenant_id.eq(tenant_id)))
            .into_boxed();

        if let Some(org_id) = org_id {
            query = query.filter(dsl::org_id.eq(org_id));
        }

        if let Some(merchant_id) = merchant_id {
            query = query.filter(dsl::merchant_id.eq(merchant_id));
        }

        if let Some(profile_id) = profile_id {
            query = query.filter(dsl::profile_id.eq(profile_id));
        }

        if let Some(entity_id) = entity_id {
            query = query.filter(dsl::entity_id.eq(entity_id));
        }

        if let Some(version) = version {
            query = query.filter(dsl::version.eq(version));
        }

        if let Some(status) = status {
            query = query.filter(dsl::status.eq(status));
        }

        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn generic_user_roles_list_for_org_and_extra(
        conn: &PgPooledConn,
        user_id: Option<String>,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        version: Option<UserRoleVersion>,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::org_id.eq(org_id).and(dsl::tenant_id.eq(tenant_id)))
            .into_boxed();

        if let Some(user_id) = user_id {
            query = query.filter(dsl::user_id.eq(user_id));
        }

        if let Some(merchant_id) = merchant_id {
            query = query.filter(dsl::merchant_id.eq(merchant_id));
        }

        if let Some(profile_id) = profile_id {
            query = query.filter(dsl::profile_id.eq(profile_id));
        }

        if let Some(version) = version {
            query = query.filter(dsl::version.eq(version));
        }

        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }

    pub async fn list_user_roles_by_user_id_across_tenants(
        conn: &PgPooledConn,
        user_id: String,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::user_id.eq(user_id))
            .into_boxed();
        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }
}


// Struct: CompleteAuthorizeRedirectResponse
// File: crates/hyperswitch_domain_models/src/router_request_types.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct CompleteAuthorizeRedirectResponse


// Function: check_node_inner
// File: crates/hyperswitch_constraint_graph/src/graph.rs
// Module: hyperswitch_constraint_graph

pub fn check_node_inner<C>(
        &self,
        ctx: &C,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        memo: &mut Memoization<V>,
        cycle_map: &mut CycleCheck,
        domains: Option<&[DomainId]>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,


// Struct: PayeezyPaymentsRequest
// File: crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayeezyPaymentsRequest


// Function: make_any_aggregator
// File: crates/hyperswitch_constraint_graph/src/builder.rs
// Module: hyperswitch_constraint_graph

pub fn make_any_aggregator<M: Metadata>(
        &mut self,
        nodes: &[(NodeId, Relation, Strength)


// Function: payment_method_intent_create
// File: crates/router/src/core/payment_methods.rs
// Module: router

pub fn payment_method_intent_create(
    state: &SessionState,
    req: api::PaymentMethodIntentCreate,
    merchant_context: &domain::MerchantContext,
) -> RouterResponse<api::PaymentMethodResponse>


// Struct: Extra
// File: crates/api_models/src/errors/types.rs
// Module: api_models
// Implementations: 0

pub struct Extra


// Implementation: impl api::PaymentSession for for Elavon
// File: crates/hyperswitch_connectors/src/connectors/elavon.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSession for for Elavon


// Struct: AcquirerData
// File: crates/hyperswitch_connectors/src/connectors/netcetera/netcetera_types.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AcquirerData


// Implementation: impl api::PaymentSync for for Worldpay
// File: crates/hyperswitch_connectors/src/connectors/worldpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSync for for Worldpay


// Implementation: impl Responder
// File: crates/router/src/routes/payments.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Function: perform_surcharge_decision_management_for_payment_method_list
// File: crates/router/src/core/payment_methods/surcharge_decision_configs.rs
// Module: router

pub fn perform_surcharge_decision_management_for_payment_method_list(
    _state: &SessionState,
    _algorithm_ref: routing::RoutingAlgorithmRef,
    _payment_attempt: &storage::PaymentAttempt,
    _payment_intent: &storage::PaymentIntent,
    _billing_address: Option<payments::Address>,
    _response_payment_method_types: &mut [api_models::payment_methods::ResponsePaymentMethodsEnabled],
) -> ConditionalConfigResult<(
    types::SurchargeMetadata,
    surcharge_decision_configs::MerchantSurchargeConfigs,
)>


// Struct: PayuOrderResponseProperty
// File: crates/hyperswitch_connectors/src/connectors/payu/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayuOrderResponseProperty


// Struct: PaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PaymentsResponse


// Struct: FacilitapaySubject
// File: crates/hyperswitch_connectors/src/connectors/facilitapay/responses.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct FacilitapaySubject


// Function: get_captures_count
// File: crates/router/src/core/payments/types.rs
// Module: router

pub fn get_captures_count(&self) -> RouterResult<i16>