File: crates/router_derive/src/macros/api_error.rs
 Public functions: 3

mod helpers;

use std::collections::HashMap;

use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    punctuated::Punctuated, token::Comma, Data, DeriveInput, Fields, Ident, ImplGenerics,
    TypeGenerics, Variant, WhereClause,
};

use crate::macros::{
    api_error::helpers::{
        check_missing_attributes, get_unused_fields, ErrorTypeProperties, ErrorVariantProperties,
        HasErrorTypeProperties, HasErrorVariantProperties,
    },
    helpers::non_enum_error,
};

pub(crate) fn api_error_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    let variants = match &ast.data {
        Data::Enum(e) => &e.variants,
        _ => return Err(non_enum_error()),
    };

    let type_properties = ast.get_type_properties()?;

    let mut variants_properties_map = HashMap::new();
    for variant in variants {
        let variant_properties = variant.get_variant_properties()?;
        check_missing_attributes(variant, &variant_properties)?;

        variants_properties_map.insert(variant, variant_properties);
    }

    let error_type_fn = implement_error_type(name, &type_properties, &variants_properties_map);
    let error_code_fn = implement_error_code(name, &variants_properties_map);
    let error_message_fn = implement_error_message(name, &variants_properties_map);
    let serialize_impl = implement_serialize(
        name,
        (&impl_generics, &ty_generics, where_clause),
        &type_properties,
        &variants_properties_map,
    );

    Ok(quote! {
        #[automatically_derived]
        impl #impl_generics std::error::Error for #name #ty_generics #where_clause {}

        #[automatically_derived]
        impl #impl_generics #name #ty_generics #where_clause {
            #error_type_fn
            #error_code_fn
            #error_message_fn
        }

        #serialize_impl
    })
}

fn implement_error_type(
    enum_name: &Ident,
    type_properties: &ErrorTypeProperties,
    variants_properties_map: &HashMap<&Variant, ErrorVariantProperties>,
) -> TokenStream {
    let mut arms = Vec::new();
    for (&variant, properties) in variants_properties_map.iter() {
        let ident = &variant.ident;
        let params = match variant.fields {
            Fields::Unit => quote! {},
            Fields::Unnamed(..) => quote! { (..) },
            Fields::Named(..) => quote! { {..} },
        };
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_type = properties.error_type.as_ref().unwrap();

        arms.push(quote! { #enum_name::#ident #params => #error_type });
    }

    // Safety: Missing attributes are already checked before this function is called.
    #[allow(clippy::unwrap_used)]
    let error_type_enum = type_properties.error_type_enum.as_ref().unwrap();
    quote! {
        pub fn error_type(&self) -> #error_type_enum {
            match self {
                #(#arms),*
            }
        }
    }
}

fn implement_error_code(
    enum_name: &Ident,
    variants_properties_map: &HashMap<&Variant, ErrorVariantProperties>,
) -> TokenStream {
    let mut arms = Vec::new();
    for (&variant, properties) in variants_properties_map.iter() {
        let ident = &variant.ident;
        let params = match variant.fields {
            Fields::Unit => quote! {},
            Fields::Unnamed(..) => quote! { (..) },
            Fields::Named(..) => quote! { {..} },
        };
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_code = properties.code.as_ref().unwrap();

        arms.push(quote! { #enum_name::#ident #params => #error_code.to_string() });
    }

    quote! {
        pub fn error_code(&self) -> String {
            match self {
                #(#arms),*
            }
        }
    }
}

fn implement_error_message(
    enum_name: &Ident,
    variants_properties_map: &HashMap<&Variant, ErrorVariantProperties>,
) -> TokenStream {
    let mut arms = Vec::new();
    for (&variant, properties) in variants_properties_map.iter() {
        let ident = &variant.ident;
        let params = match variant.fields {
            Fields::Unit => quote! {},
            Fields::Unnamed(..) => quote! { (..) },
            Fields::Named(ref fields) => {
                let fields = fields
                    .named
                    .iter()
                    .map(|f| {
                        // Safety: Named fields are guaranteed to have an identifier.
                        #[allow(clippy::unwrap_used)]
                        f.ident.as_ref().unwrap()
                    })
                    .collect::<Punctuated<&Ident, Comma>>();
                quote! { {#fields} }
            }
        };
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_message = properties.message.as_ref().unwrap();

        arms.push(quote! { #enum_name::#ident #params => format!(#error_message) });
    }

    quote! {
        pub fn error_message(&self) -> String {
            match self {
                #(#arms),*
            }
        }
    }
}

fn implement_serialize(
    enum_name: &Ident,
    generics: (&ImplGenerics<'_>, &TypeGenerics<'_>, Option<&WhereClause>),
    type_properties: &ErrorTypeProperties,
    variants_properties_map: &HashMap<&Variant, ErrorVariantProperties>,
) -> TokenStream {
    let (impl_generics, ty_generics, where_clause) = generics;
    let mut arms = Vec::new();
    for (&variant, properties) in variants_properties_map.iter() {
        let ident = &variant.ident;
        let params = match variant.fields {
            Fields::Unit => quote! {},
            Fields::Unnamed(..) => quote! { (..) },
            Fields::Named(ref fields) => {
                let fields = fields
                    .named
                    .iter()
                    .map(|f| {
                        // Safety: Named fields are guaranteed to have an identifier.
                        #[allow(clippy::unwrap_used)]
                        f.ident.as_ref().unwrap()
                    })
                    .collect::<Punctuated<&Ident, Comma>>();
                quote! { {#fields} }
            }
        };
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_message = properties.message.as_ref().unwrap();
        let msg_unused_fields =
            get_unused_fields(&variant.fields, &error_message.value(), &properties.ignore);

        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_type_enum = type_properties.error_type_enum.as_ref().unwrap();
        let response_definition = if msg_unused_fields.is_empty() {
            quote! {
                #[derive(Clone, Debug, serde::Serialize)]
                struct ErrorResponse {
                    #[serde(rename = "type")]
                    error_type: #error_type_enum,
                    code: String,
                    message: String,
                }
            }
        } else {
            let mut extra_fields = Vec::new();
            for field in &msg_unused_fields {
                let vis = &field.vis;
                // Safety: `msq_unused_fields` is expected to contain named fields only.
                #[allow(clippy::unwrap_used)]
                let ident = &field.ident.as_ref().unwrap();
                let ty = &field.ty;
                extra_fields.push(quote! { #vis #ident: #ty });
            }
            quote! {
                #[derive(Clone, Debug, serde::Serialize)]
                struct ErrorResponse #ty_generics #where_clause {
                    #[serde(rename = "type")]
                    error_type: #error_type_enum,
                    code: String,
                    message: String,
                    #(#extra_fields),*
                }
            }
        };

        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let error_type = properties.error_type.as_ref().unwrap();
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let code = properties.code.as_ref().unwrap();
        // Safety: Missing attributes are already checked before this function is called.
        #[allow(clippy::unwrap_used)]
        let message = properties.message.as_ref().unwrap();
        let extra_fields = msg_unused_fields
            .iter()
            .map(|field| {
                // Safety: `extra_fields` is expected to contain named fields only.
                #[allow(clippy::unwrap_used)]
                let field_name = field.ident.as_ref().unwrap();
                quote! { #field_name: #field_name.to_owned() }
            })
            .collect::<Vec<TokenStream>>();
        arms.push(quote! {
            #enum_name::#ident #params => {
                #response_definition
                let response = ErrorResponse {
                    error_type: #error_type,
                    code: #code.to_string(),
                    message: format!(#message),
                    #(#extra_fields),*
                };
                response.serialize(serializer)
            }
        });
    }
    quote! {
        #[automatically_derived]
        impl #impl_generics serde::Serialize for #enum_name #ty_generics #where_clause {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                match self {
                    #(#arms),*
                }
            }
        }
    }
}


pub struct Address {
    line1: Option<Secret<String>>,
    line2: Option<Secret<String>>,
    zip: Option<Secret<String>>,
    state: Option<Secret<String>>,
    city: Option<String>,
    country: Option<api_models::enums::CountryAlpha2>,
}


pub struct ChargebeeInvoiceBillingAddress {
    pub line1: Option<Secret<String>>,
    pub line2: Option<Secret<String>>,
    pub line3: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
    pub country: Option<enums::CountryAlpha2>,
    pub zip: Option<Secret<String>>,
    pub city: Option<String>,
}


File: crates/hyperswitch_domain_models/src/router_data.rs
 Public functions: 7
 Public structs: 20

use std::{collections::HashMap, marker::PhantomData};

use common_types::{payments as common_payment_types, primitive_wrappers};
use common_utils::{
    errors::IntegrityCheckError,
    ext_traits::{OptionExt, ValueExt},
    id_type,
    types::MinorUnit,
};
use error_stack::ResultExt;
use masking::{ExposeInterface, Secret};

use crate::{
    network_tokenization::NetworkTokenNumber, payment_address::PaymentAddress, payment_method_data,
    payments,
};
#[cfg(feature = "v2")]
use crate::{
    payments::{
        payment_attempt::{ErrorDetails, PaymentAttemptUpdate},
        payment_intent::PaymentIntentUpdate,
    },
    router_flow_types, router_request_types, router_response_types,
};

#[derive(Debug, Clone)]
pub struct RouterData<Flow, Request, Response> {
    pub flow: PhantomData<Flow>,
    pub merchant_id: id_type::MerchantId,
    pub customer_id: Option<id_type::CustomerId>,
    pub connector_customer: Option<String>,
    pub connector: String,
    // TODO: This should be a PaymentId type.
    // Make this change after all the connector dependency has been removed from connectors
    pub payment_id: String,
    pub attempt_id: String,
    pub tenant_id: id_type::TenantId,
    pub status: common_enums::enums::AttemptStatus,
    pub payment_method: common_enums::enums::PaymentMethod,
    pub connector_auth_type: ConnectorAuthType,
    pub description: Option<String>,
    pub address: PaymentAddress,
    pub auth_type: common_enums::enums::AuthenticationType,
    pub connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    pub connector_wallets_details: Option<common_utils::pii::SecretSerdeValue>,
    pub amount_captured: Option<i64>,
    pub access_token: Option<AccessToken>,
    pub session_token: Option<String>,
    pub reference_id: Option<String>,
    pub payment_method_token: Option<PaymentMethodToken>,
    pub recurring_mandate_payment_data: Option<RecurringMandatePaymentData>,
    pub preprocessing_id: Option<String>,
    /// This is the balance amount for gift cards or voucher
    pub payment_method_balance: Option<PaymentMethodBalance>,

    ///for switching between two different versions of the same connector
    pub connector_api_version: Option<String>,

    /// Contains flow-specific data required to construct a request and send it to the connector.
    pub request: Request,

    /// Contains flow-specific data that the connector responds with.
    pub response: Result<Response, ErrorResponse>,

    /// Contains a reference ID that should be sent in the connector request
    pub connector_request_reference_id: String,

    #[cfg(feature = "payouts")]
    /// Contains payout method data
    pub payout_method_data: Option<api_models::payouts::PayoutMethodData>,

    #[cfg(feature = "payouts")]
    /// Contains payout's quote ID
    pub quote_id: Option<String>,

    pub test_mode: Option<bool>,
    pub connector_http_status_code: Option<u16>,
    pub external_latency: Option<u128>,
    /// Contains apple pay flow type simplified or manual
    pub apple_pay_flow: Option<payment_method_data::ApplePayFlow>,

    pub frm_metadata: Option<common_utils::pii::SecretSerdeValue>,

    pub dispute_id: Option<String>,
    pub refund_id: Option<String>,

    /// This field is used to store various data regarding the response from connector
    pub connector_response: Option<ConnectorResponseData>,
    pub payment_method_status: Option<common_enums::PaymentMethodStatus>,

    // minor amount for amount framework
    pub minor_amount_captured: Option<MinorUnit>,
    pub minor_amount_capturable: Option<MinorUnit>,

    pub integrity_check: Result<(), IntegrityCheckError>,

    pub additional_merchant_data: Option<api_models::admin::AdditionalMerchantData>,

    pub header_payload: Option<payments::HeaderPayload>,

    pub connector_mandate_request_reference_id: Option<String>,

    pub l2_l3_data: Option<L2L3Data>,

    pub authentication_id: Option<id_type::AuthenticationId>,
    /// Contains the type of sca exemption required for the transaction
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,

    /// Contains stringified connector raw response body
    pub raw_connector_response: Option<Secret<String>>,

    /// Indicates whether the payment ID was provided by the merchant (true),
    /// or generated internally by Hyperswitch (false)
    pub is_payment_id_from_merchant: Option<bool>,
}

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct L2L3Data {
    pub order_date: Option<time::PrimitiveDateTime>,
    pub tax_status: Option<common_enums::TaxStatus>,
    pub customer_tax_registration_id: Option<Secret<String>>,
    pub order_details: Option<Vec<api_models::payments::OrderDetailsWithAmount>>,
    pub discount_amount: Option<MinorUnit>,
    pub shipping_cost: Option<MinorUnit>,
    pub shipping_amount_tax: Option<MinorUnit>,
    pub duty_amount: Option<MinorUnit>,
    pub order_tax_amount: Option<MinorUnit>,
    pub merchant_order_reference_id: Option<String>,
    pub customer_id: Option<id_type::CustomerId>,
    pub shipping_origin_zip: Option<Secret<String>>,
    pub shipping_state: Option<Secret<String>>,
    pub shipping_country: Option<common_enums::CountryAlpha2>,
    pub shipping_destination_zip: Option<Secret<String>>,
    pub billing_address_city: Option<String>,
    pub merchant_tax_registration_id: Option<Secret<String>>,
}

// Different patterns of authentication.
#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(tag = "auth_type")]
pub enum ConnectorAuthType {
    TemporaryAuth,
    HeaderKey {
        api_key: Secret<String>,
    },
    BodyKey {
        api_key: Secret<String>,
        key1: Secret<String>,
    },
    SignatureKey {
        api_key: Secret<String>,
        key1: Secret<String>,
        api_secret: Secret<String>,
    },
    MultiAuthKey {
        api_key: Secret<String>,
        key1: Secret<String>,
        api_secret: Secret<String>,
        key2: Secret<String>,
    },
    CurrencyAuthKey {
        auth_key_map: HashMap<common_enums::enums::Currency, common_utils::pii::SecretSerdeValue>,
    },
    CertificateAuth {
        certificate: Secret<String>,
        private_key: Secret<String>,
    },
    #[default]
    NoKey,
}

impl ConnectorAuthType {
    pub fn from_option_secret_value(
        value: Option<common_utils::pii::SecretSerdeValue>,
    ) -> common_utils::errors::CustomResult<Self, common_utils::errors::ParsingError> {
        value
            .parse_value::<Self>("ConnectorAuthType")
            .change_context(common_utils::errors::ParsingError::StructParseFailure(
                "ConnectorAuthType",
            ))
    }

    pub fn from_secret_value(
        value: common_utils::pii::SecretSerdeValue,
    ) -> common_utils::errors::CustomResult<Self, common_utils::errors::ParsingError> {
        value
            .parse_value::<Self>("ConnectorAuthType")
            .change_context(common_utils::errors::ParsingError::StructParseFailure(
                "ConnectorAuthType",
            ))
    }

    // show only first and last two digits of the key and mask others with *
    // mask the entire key if it's length is less than or equal to 4
    fn mask_key(&self, key: String) -> Secret<String> {
        let key_len = key.len();
        let masked_key = if key_len <= 4 {
            "*".repeat(key_len)
        } else {
            // Show the first two and last two characters, mask the rest with '*'
            let mut masked_key = String::new();
            let key_len = key.len();
            // Iterate through characters by their index
            for (index, character) in key.chars().enumerate() {
                if index < 2 || index >= key_len - 2 {
                    masked_key.push(character); // Keep the first two and last two characters
                } else {
                    masked_key.push('*'); // Mask the middle characters
                }
            }
            masked_key
        };
        Secret::new(masked_key)
    }

    // Mask the keys in the auth_type
    pub fn get_masked_keys(&self) -> Self {
        match self {
            Self::TemporaryAuth => Self::TemporaryAuth,
            Self::NoKey => Self::NoKey,
            Self::HeaderKey { api_key } => Self::HeaderKey {
                api_key: self.mask_key(api_key.clone().expose()),
            },
            Self::BodyKey { api_key, key1 } => Self::BodyKey {
                api_key: self.mask_key(api_key.clone().expose()),
                key1: self.mask_key(key1.clone().expose()),
            },
            Self::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Self::SignatureKey {
                api_key: self.mask_key(api_key.clone().expose()),
                key1: self.mask_key(key1.clone().expose()),
                api_secret: self.mask_key(api_secret.clone().expose()),
            },
            Self::MultiAuthKey {
                api_key,
                key1,
                api_secret,
                key2,
            } => Self::MultiAuthKey {
                api_key: self.mask_key(api_key.clone().expose()),
                key1: self.mask_key(key1.clone().expose()),
                api_secret: self.mask_key(api_secret.clone().expose()),
                key2: self.mask_key(key2.clone().expose()),
            },
            Self::CurrencyAuthKey { auth_key_map } => Self::CurrencyAuthKey {
                auth_key_map: auth_key_map.clone(),
            },
            Self::CertificateAuth {
                certificate,
                private_key,
            } => Self::CertificateAuth {
                certificate: self.mask_key(certificate.clone().expose()),
                private_key: self.mask_key(private_key.clone().expose()),
            },
        }
    }
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
pub struct AccessTokenAuthenticationResponse {
    pub code: Secret<String>,
    pub expires: i64,
}

#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
pub struct AccessToken {
    pub token: Secret<String>,
    pub expires: i64,
}

#[derive(Debug, Clone, serde::Deserialize)]
pub enum PaymentMethodToken {
    Token(Secret<String>),
    ApplePayDecrypt(Box<common_payment_types::ApplePayPredecryptData>),
    GooglePayDecrypt(Box<common_payment_types::GPayPredecryptData>),
    PazeDecrypt(Box<PazeDecryptedData>),
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayPredecryptDataInternal {
    pub application_primary_account_number: cards::CardNumber,
    pub application_expiration_date: String,
    pub currency_code: String,
    pub transaction_amount: i64,
    pub device_manufacturer_identifier: Secret<String>,
    pub payment_data_type: Secret<String>,
    pub payment_data: ApplePayCryptogramDataInternal,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayCryptogramDataInternal {
    pub online_payment_cryptogram: Secret<String>,
    pub eci_indicator: Option<String>,
}

impl TryFrom<ApplePayPredecryptDataInternal> for common_payment_types::ApplePayPredecryptData {
    type Error = common_utils::errors::ValidationError;
    fn try_from(data: ApplePayPredecryptDataInternal) -> Result<Self, Self::Error> {
        let application_expiration_month = data.clone().get_expiry_month()?;
        let application_expiration_year = data.clone().get_four_digit_expiry_year()?;

        Ok(Self {
            application_primary_account_number: data.application_primary_account_number.clone(),
            application_expiration_month,
            application_expiration_year,
            payment_data: data.payment_data.into(),
        })
    }
}

impl From<GooglePayPredecryptDataInternal> for common_payment_types::GPayPredecryptData {
    fn from(data: GooglePayPredecryptDataInternal) -> Self {
        Self {
            card_exp_month: Secret::new(data.payment_method_details.expiration_month.two_digits()),
            card_exp_year: Secret::new(data.payment_method_details.expiration_year.four_digits()),
            application_primary_account_number: data.payment_method_details.pan.clone(),
            cryptogram: data.payment_method_details.cryptogram.clone(),
            eci_indicator: data.payment_method_details.eci_indicator.clone(),
        }
    }
}

impl From<ApplePayCryptogramDataInternal> for common_payment_types::ApplePayCryptogramData {
    fn from(payment_data: ApplePayCryptogramDataInternal) -> Self {
        Self {
            online_payment_cryptogram: payment_data.online_payment_cryptogram,
            eci_indicator: payment_data.eci_indicator,
        }
    }
}

impl ApplePayPredecryptDataInternal {
    /// This logic being applied as apple pay provides application_expiration_date in the YYMMDD format
    fn get_four_digit_expiry_year(
        &self,
    ) -> Result<Secret<String>, common_utils::errors::ValidationError> {
        Ok(Secret::new(format!(
            "20{}",
            self.application_expiration_date.get(0..2).ok_or(
                common_utils::errors::ValidationError::InvalidValue {
                    message: "Invalid two-digit year".to_string(),
                }
            )?
        )))
    }

    fn get_expiry_month(&self) -> Result<Secret<String>, common_utils::errors::ValidationError> {
        Ok(Secret::new(
            self.application_expiration_date
                .get(2..4)
                .ok_or(common_utils::errors::ValidationError::InvalidValue {
                    message: "Invalid two-digit month".to_string(),
                })?
                .to_owned(),
        ))
    }
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayPredecryptDataInternal {
    pub message_expiration: String,
    pub message_id: String,
    #[serde(rename = "paymentMethod")]
    pub payment_method_type: String,
    pub payment_method_details: GooglePayPaymentMethodDetails,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayPaymentMethodDetails {
    pub auth_method: common_enums::enums::GooglePayAuthMethod,
    pub expiration_month: cards::CardExpirationMonth,
    pub expiration_year: cards::CardExpirationYear,
    pub pan: cards::CardNumber,
    pub cryptogram: Option<Secret<String>>,
    pub eci_indicator: Option<String>,
    pub card_network: Option<String>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeDecryptedData {
    pub client_id: Secret<String>,
    pub profile_id: String,
    pub token: PazeToken,
    pub payment_card_network: common_enums::enums::CardNetwork,
    pub dynamic_data: Vec<PazeDynamicData>,
    pub billing_address: PazeAddress,
    pub consumer: PazeConsumer,
    pub eci: Option<String>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeToken {
    pub payment_token: NetworkTokenNumber,
    pub token_expiration_month: Secret<String>,
    pub token_expiration_year: Secret<String>,
    pub payment_account_reference: Secret<String>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeDynamicData {
    pub dynamic_data_value: Option<Secret<String>>,
    pub dynamic_data_type: Option<String>,
    pub dynamic_data_expiration: Option<String>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeAddress {
    pub name: Option<Secret<String>>,
    pub line1: Option<Secret<String>>,
    pub line2: Option<Secret<String>>,
    pub line3: Option<Secret<String>>,
    pub city: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
    pub zip: Option<Secret<String>>,
    pub country_code: Option<common_enums::enums::CountryAlpha2>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazeConsumer {
    // This is consumer data not customer data.
    pub first_name: Option<Secret<String>>,
    pub last_name: Option<Secret<String>>,
    pub full_name: Secret<String>,
    pub email_address: common_utils::pii::Email,
    pub mobile_number: Option<PazePhoneNumber>,
    pub country_code: Option<common_enums::enums::CountryAlpha2>,
    pub language_code: Option<String>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PazePhoneNumber {
    pub country_code: Secret<String>,
    pub phone_number: Secret<String>,
}

#[derive(Debug, Default, Clone)]
pub struct RecurringMandatePaymentData {
    pub payment_method_type: Option<common_enums::enums::PaymentMethodType>, //required for making recurring payment using saved payment method through stripe
    pub original_payment_authorized_amount: Option<i64>,
    pub original_payment_authorized_currency: Option<common_enums::enums::Currency>,
    pub mandate_metadata: Option<common_utils::pii::SecretSerdeValue>,
}

#[derive(Debug, Clone)]
pub struct PaymentMethodBalance {
    pub amount: MinorUnit,
    pub currency: common_enums::enums::Currency,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ConnectorResponseData {
    pub additional_payment_method_data: Option<AdditionalPaymentMethodConnectorResponse>,
    extended_authorization_response_data: Option<ExtendedAuthorizationResponseData>,
}

impl ConnectorResponseData {
    pub fn with_additional_payment_method_data(
        additional_payment_method_data: AdditionalPaymentMethodConnectorResponse,
    ) -> Self {
        Self {
            additional_payment_method_data: Some(additional_payment_method_data),
            extended_authorization_response_data: None,
        }
    }
    pub fn new(
        additional_payment_method_data: Option<AdditionalPaymentMethodConnectorResponse>,
        extended_authorization_response_data: Option<ExtendedAuthorizationResponseData>,
    ) -> Self {
        Self {
            additional_payment_method_data,
            extended_authorization_response_data,
        }
    }

    pub fn get_extended_authorization_response_data(
        &self,
    ) -> Option<&ExtendedAuthorizationResponseData> {
        self.extended_authorization_response_data.as_ref()
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum AdditionalPaymentMethodConnectorResponse {
    Card {
        /// Details regarding the authentication details of the connector, if this is a 3ds payment.
        authentication_data: Option<serde_json::Value>,
        /// Various payment checks that are done for a payment
        payment_checks: Option<serde_json::Value>,
        /// Card Network returned by the processor
        card_network: Option<String>,
        /// Domestic(Co-Branded) Card network returned by the processor
        domestic_network: Option<String>,
    },
    PayLater {
        klarna_sdk: Option<KlarnaSdkResponse>,
    },
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ExtendedAuthorizationResponseData {
    pub extended_authentication_applied:
        Option<primitive_wrappers::ExtendedAuthorizationAppliedBool>,
    pub capture_before: Option<time::PrimitiveDateTime>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KlarnaSdkResponse {
    pub payment_type: Option<String>,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct ErrorResponse {
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
    pub status_code: u16,
    pub attempt_status: Option<common_enums::enums::AttemptStatus>,
    pub connector_transaction_id: Option<String>,
    pub network_decline_code: Option<String>,
    pub network_advice_code: Option<String>,
    pub network_error_message: Option<String>,
    pub connector_metadata: Option<Secret<serde_json::Value>>,
}

impl Default for ErrorResponse {
    fn default() -> Self {
        Self {
            code: "HE_00".to_string(),
            message: "Something went wrong".to_string(),
            reason: None,
            status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            attempt_status: None,
            connector_transaction_id: None,
            network_decline_code: None,
            network_advice_code: None,
            network_error_message: None,
            connector_metadata: None,
        }
    }
}

impl ErrorResponse {
    pub fn get_not_implemented() -> Self {
        Self {
            code: "IR_00".to_string(),
            message: "This API is under development and will be made available soon.".to_string(),
            reason: None,
            status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            attempt_status: None,
            connector_transaction_id: None,
            network_decline_code: None,
            network_advice_code: None,
            network_error_message: None,
            connector_metadata: None,
        }
    }
}

/// Get updatable trakcer objects of payment intent and payment attempt
#[cfg(feature = "v2")]
pub trait TrackerPostUpdateObjects<Flow, FlowRequest, D> {
    fn get_payment_intent_update(
        &self,
        payment_data: &D,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentIntentUpdate;

    fn get_payment_attempt_update(
        &self,
        payment_data: &D,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentAttemptUpdate;

    /// Get the amount that can be captured for the payment
    fn get_amount_capturable(&self, payment_data: &D) -> Option<MinorUnit>;

    /// Get the amount that has been captured for the payment
    fn get_captured_amount(&self, payment_data: &D) -> Option<MinorUnit>;

    /// Get the attempt status based on parameters like captured amount and amount capturable
    fn get_attempt_status_for_db_update(
        &self,
        payment_data: &D,
    ) -> common_enums::enums::AttemptStatus;
}

#[cfg(feature = "v2")]
impl
    TrackerPostUpdateObjects<
        router_flow_types::Authorize,
        router_request_types::PaymentsAuthorizeData,
        payments::PaymentConfirmData<router_flow_types::Authorize>,
    >
    for RouterData<
        router_flow_types::Authorize,
        router_request_types::PaymentsAuthorizeData,
        router_response_types::PaymentsResponseData,
    >
{
    fn get_payment_intent_update(
        &self,
        payment_data: &payments::PaymentConfirmData<router_flow_types::Authorize>,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentIntentUpdate {
        let amount_captured = self.get_captured_amount(payment_data);
        let updated_feature_metadata =
            payment_data
                .payment_intent
                .feature_metadata
                .clone()
                .map(|mut feature_metadata| {
                    if let Some(ref mut payment_revenue_recovery_metadata) =
                        feature_metadata.payment_revenue_recovery_metadata
                    {
                        payment_revenue_recovery_metadata.payment_connector_transmission = if self
                            .response
                            .is_ok()
                        {
                            common_enums::PaymentConnectorTransmission::ConnectorCallSucceeded
                        } else {
                            common_enums::PaymentConnectorTransmission::ConnectorCallUnsuccessful
                        };
                    }
                    Box::new(feature_metadata)
                });

        match self.response {
            Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate {
                status: common_enums::IntentStatus::from(
                    self.get_attempt_status_for_db_update(payment_data),
                ),
                amount_captured,
                updated_by: storage_scheme.to_string(),
                feature_metadata: updated_feature_metadata,
            },
            Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate {
                status: {
                    let attempt_status = match error.attempt_status {
                        // Use the status sent by connector in error_response if it's present
                        Some(status) => status,
                        None => match error.status_code {
                            500..=511 => common_enums::enums::AttemptStatus::Pending,
                            _ => common_enums::enums::AttemptStatus::Failure,
                        },
                    };
                    common_enums::IntentStatus::from(attempt_status)
                },
                amount_captured,
                updated_by: storage_scheme.to_string(),
                feature_metadata: updated_feature_metadata,
            },
        }
    }

    fn get_payment_attempt_update(
        &self,
        payment_data: &payments::PaymentConfirmData<router_flow_types::Authorize>,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentAttemptUpdate {
        let amount_capturable = self.get_amount_capturable(payment_data);

        match self.response {
            Ok(ref response_router_data) => match response_router_data {
                router_response_types::PaymentsResponseData::TransactionResponse {
                    resource_id,
                    redirection_data,
                    connector_metadata,
                    connector_response_reference_id,
                    ..
                } => {
                    let attempt_status = self.get_attempt_status_for_db_update(payment_data);

                    let connector_payment_id = match resource_id {
                        router_request_types::ResponseId::NoResponseId => None,
                        router_request_types::ResponseId::ConnectorTransactionId(id)
                        | router_request_types::ResponseId::EncodedData(id) => Some(id.to_owned()),
                    };

                    PaymentAttemptUpdate::ConfirmIntentResponse(Box::new(
                        payments::payment_attempt::ConfirmIntentResponseUpdate {
                            status: attempt_status,
                            connector_payment_id,
                            updated_by: storage_scheme.to_string(),
                            redirection_data: *redirection_data.clone(),
                            amount_capturable,
                            connector_metadata: connector_metadata.clone().map(Secret::new),
                            connector_token_details: response_router_data
                                .get_updated_connector_token_details(
                                    payment_data
                                        .payment_attempt
                                        .connector_token_details
                                        .as_ref()
                                        .and_then(|token_details| {
                                            token_details.get_connector_token_request_reference_id()
                                        }),
                                ),
                            connector_response_reference_id: connector_response_reference_id
                                .clone(),
                        },
                    ))
                }
                router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::TransactionUnresolvedResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::ConnectorCustomerResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse {
                    ..
                } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse {
                    ..
                } => todo!(),
            },
            Err(ref error_response) => {
                let ErrorResponse {
                    code,
                    message,
                    reason,
                    status_code: _,
                    attempt_status: _,
                    connector_transaction_id,
                    network_decline_code,
                    network_advice_code,
                    network_error_message,
                    connector_metadata,
                } = error_response.clone();

                let attempt_status = match error_response.attempt_status {
                    // Use the status sent by connector in error_response if it's present
                    Some(status) => status,
                    None => match error_response.status_code {
                        500..=511 => common_enums::enums::AttemptStatus::Pending,
                        _ => common_enums::enums::AttemptStatus::Failure,
                    },
                };
                let error_details = ErrorDetails {
                    code,
                    message,
                    reason,
                    unified_code: None,
                    unified_message: None,
                    network_advice_code,
                    network_decline_code,
                    network_error_message,
                };

                PaymentAttemptUpdate::ErrorUpdate {
                    status: attempt_status,
                    error: error_details,
                    amount_capturable,
                    connector_payment_id: connector_transaction_id,
                    updated_by: storage_scheme.to_string(),
                }
            }
        }
    }

    fn get_attempt_status_for_db_update(
        &self,
        _payment_data: &payments::PaymentConfirmData<router_flow_types::Authorize>,
    ) -> common_enums::AttemptStatus {
        // For this step, consider whatever status was given by the connector module
        // We do not need to check for amount captured or amount capturable here because we are authorizing the whole amount
        self.status
    }

    fn get_amount_capturable(
        &self,
        payment_data: &payments::PaymentConfirmData<router_flow_types::Authorize>,
    ) -> Option<MinorUnit> {
        // Based on the status of the response, we can determine the amount capturable
        let intent_status = common_enums::IntentStatus::from(self.status);
        match intent_status {
            // If the status is already succeeded / failed we cannot capture any more amount
            // So set the amount capturable to zero
            common_enums::IntentStatus::Succeeded
            | common_enums::IntentStatus::Failed
            | common_enums::IntentStatus::Cancelled
            | common_enums::IntentStatus::CancelledPostCapture
            | common_enums::IntentStatus::Conflicted
            | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()),
            // For these statuses, update the capturable amount when it reaches terminal / capturable state
            common_enums::IntentStatus::RequiresCustomerAction
            | common_enums::IntentStatus::RequiresMerchantAction
            | common_enums::IntentStatus::Processing => None,
            // Invalid states for this flow
            common_enums::IntentStatus::RequiresPaymentMethod
            | common_enums::IntentStatus::RequiresConfirmation
            | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture => None,
            // If status is requires capture, get the total amount that can be captured
            // This is in cases where the capture method will be `manual` or `manual_multiple`
            // We do not need to handle the case where amount_to_capture is provided here as it cannot be passed in authroize flow
            common_enums::IntentStatus::RequiresCapture => {
                let total_amount = payment_data.payment_attempt.amount_details.get_net_amount();
                Some(total_amount)
            }
            // Invalid statues for this flow, after doing authorization this state is invalid
            common_enums::IntentStatus::PartiallyCaptured
            | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None,
        }
    }

    fn get_captured_amount(
        &self,
        payment_data: &payments::PaymentConfirmData<router_flow_types::Authorize>,
    ) -> Option<MinorUnit> {
        // Based on the status of the response, we can determine the amount that was captured
        let intent_status = common_enums::IntentStatus::from(self.status);
        match intent_status {
            // If the status is succeeded then we have captured the whole amount
            // we need not check for `amount_to_capture` here because passing `amount_to_capture` when authorizing is not supported
            common_enums::IntentStatus::Succeeded | common_enums::IntentStatus::Conflicted => {
                let total_amount = payment_data.payment_attempt.amount_details.get_net_amount();
                Some(total_amount)
            }
            // No amount is captured
            common_enums::IntentStatus::Cancelled
            | common_enums::IntentStatus::CancelledPostCapture
            | common_enums::IntentStatus::Failed
            | common_enums::IntentStatus::Expired => Some(MinorUnit::zero()),
            // For these statuses, update the amount captured when it reaches terminal state
            common_enums::IntentStatus::RequiresCustomerAction
            | common_enums::IntentStatus::RequiresMerchantAction
            | common_enums::IntentStatus::Processing => None,
            // Invalid states for this flow
            common_enums::IntentStatus::RequiresPaymentMethod
            | common_enums::IntentStatus::RequiresConfirmation => None,
            // No amount has been captured yet
            common_enums::IntentStatus::RequiresCapture
            | common_enums::IntentStatus::PartiallyAuthorizedAndRequiresCapture => {
                Some(MinorUnit::zero())
            }
            // Invalid statues for this flow
            common_enums::IntentStatus::PartiallyCaptured
            | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None,
        }
    }
}

#[cfg(feature = "v2")]
impl
    TrackerPostUpdateObjects<
        router_flow_types::Capture,
        router_request_types::PaymentsCaptureData,
        payments::PaymentCaptureData<router_flow_types::Capture>,
    >
    for RouterData<
        router_flow_types::Capture,
        router_request_types::PaymentsCaptureData,
        router_response_types::PaymentsResponseData,
    >
{
    fn get_payment_intent_update(
        &self,
        payment_data: &payments::PaymentCaptureData<router_flow_types::Capture>,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentIntentUpdate {
        let amount_captured = self.get_captured_amount(payment_data);
        match self.response {
            Ok(ref _response) => PaymentIntentUpdate::CaptureUpdate {
                status: common_enums::IntentStatus::from(
                    self.get_attempt_status_for_db_update(payment_data),
                ),
                amount_captured,
                updated_by: storage_scheme.to_string(),
            },
            Err(ref error) => PaymentIntentUpdate::CaptureUpdate {
                status: error
                    .attempt_status
                    .map(common_enums::IntentStatus::from)
                    .unwrap_or(common_enums::IntentStatus::Failed),
                amount_captured,
                updated_by: storage_scheme.to_string(),
            },
        }
    }

    fn get_payment_attempt_update(
        &self,
        payment_data: &payments::PaymentCaptureData<router_flow_types::Capture>,
        storage_scheme: common_enums::MerchantStorageScheme,
    ) -> PaymentAttemptUpdate {
        let amount_capturable = self.get_amount_capturable(payment_data);

        match self.response {
            Ok(ref response_router_data) => match response_router_data {
                router_response_types::PaymentsResponseData::TransactionResponse { .. } => {
                    let attempt_status = self.status;

                    PaymentAttemptUpdate::CaptureUpdate {
                        status: attempt_status,
                        amount_capturable,
                        updated_by: storage_scheme.to_string(),
                    }
                }
                router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::TransactionUnresolvedResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(),
                router_response_types::PaymentsResponseData::ConnectorCustomerResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse {
                    ..
                } => todo!(),
                router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::PaymentResourceUpdateResponse {
                    ..
                } => {
                    todo!()
                }
                router_response_types::PaymentsResponseData::PaymentsCreateOrderResponse {
                    ..
                } => todo!(),
            },
            Err(ref error_response) => {
                let ErrorResponse {
                    code,
                    message,
                    reason,
                    status_code: _,
                    attempt_status,
                    connector_transaction_id,
                    network_advice_code,
                    network_decline_code,
                    network_error_message,
                    connector_metadata: _,
                } = error_response.clone();
                let attempt_status = attempt_status.unwrap_or(self.status);

                let error_details = ErrorDetails {
                    code,
                    message,
                    reason,
                    unified_code: None,
                    unified_message: None,
                    network_advice_code,
                    network_decline_code,
                    network_error_message,
                };

                PaymentAttemptUpdate::ErrorUpdate {
                    status: attempt_status,
                    error: error_details,
                    amount_capturable,
                    connector_payment_id: connector_transaction_id,
                    updated_by: storage_scheme.to_string(),
                }
            }
        }
    }

    fn get_attempt_status_for_db_update(
        &self,
        payment_data: &payments::PaymentCaptureData<router_flow_types::Capture>,
    ) -> common_enums::AttemptStatus {
        match self.status {
            common_enums::AttemptStatus::Charged => {
                let amount_captured = self
                    .get_captured_amount(payment_data)
                    .unwrap_or(MinorUnit::zero());
                let total_amount = payment_data.payment_attempt.amount_details.get_net_amount();

                if amount_captured == total_amount {
                    common_enums::AttemptStatus::Charged
                } else {
                    common_enums::AttemptStatus::PartialCharged
                }
            }
            _ => self.status,
        }
    }

    fn get_amount_capturable(
        &self,
        payment_data: &payments::PaymentCaptureData<router_flow_types::Capture>,
    ) -> Option<MinorUnit> {
        // Based on the status of the response, we can determine the amount capturable
        let intent_status = common_enums::IntentStatus::from(self.status);
        match intent_status {


pub fn server(state: AppState) -> Scope {
        let mut route = web::scope("/payments").app_data(web::Data::new(state));

        #[cfg(feature = "olap")]
        {
            route = route
                .service(
                    web::resource("/list")
                        .route(web::get().to(payments::payments_list))
                        .route(web::post().to(payments::payments_list_by_filter)),
                )
                .service(
                    web::resource("/profile/list")
                        .route(web::get().to(payments::profile_payments_list))
                        .route(web::post().to(payments::profile_payments_list_by_filter)),
                )
                .service(
                    web::resource("/filter")
                        .route(web::post().to(payments::get_filters_for_payments)),
                )
                .service(
                    web::resource("/v2/filter").route(web::get().to(payments::get_payment_filters)),
                )
                .service(
                    web::resource("/aggregate")
                        .route(web::get().to(payments::get_payments_aggregates)),
                )
                .service(
                    web::resource("/profile/aggregate")
                        .route(web::get().to(payments::get_payments_aggregates_profile)),
                )
                .service(
                    web::resource("/v2/profile/filter")
                        .route(web::get().to(payments::get_payment_filters_profile)),
                )
                .service(
                    web::resource("/{payment_id}/manual-update")
                        .route(web::put().to(payments::payments_manual_update)),
                )
        }
        #[cfg(feature = "oltp")]
        {
            route = route
                .service(web::resource("").route(web::post().to(payments::payments_create)))
                .service(
                    web::resource("/session_tokens")
                        .route(web::post().to(payments::payments_connector_session)),
                )
                .service(
                    web::resource("/sync")
                        .route(web::post().to(payments::payments_retrieve_with_gateway_creds)),
                )
                .service(
                    web::resource("/{payment_id}")
                        .route(web::get().to(payments::payments_retrieve))
                        .route(web::post().to(payments::payments_update)),
                )
                .service(
                    web::resource("/{payment_id}/post_session_tokens").route(web::post().to(payments::payments_post_session_tokens)),
                )
                .service(
                    web::resource("/{payment_id}/confirm").route(web::post().to(payments::payments_confirm)),
                )
                .service(
                    web::resource("/{payment_id}/cancel").route(web::post().to(payments::payments_cancel)),
                )
                .service(
                    web::resource("/{payment_id}/cancel_post_capture").route(web::post().to(payments::payments_cancel_post_capture)),
                )
                .service(
                    web::resource("/{payment_id}/capture").route(web::post().to(payments::payments_capture)),
                )
                .service(
                    web::resource("/{payment_id}/approve")
                        .route(web::post().to(payments::payments_approve)),
                )
                .service(
                    web::resource("/{payment_id}/reject")
                        .route(web::post().to(payments::payments_reject)),
                )
                .service(
                    web::resource("/redirect/{payment_id}/{merchant_id}/{attempt_id}")
                        .route(web::get().to(payments::payments_start)),
                )
                .service(
                    web::resource(
                        "/{payment_id}/{merchant_id}/redirect/response/{connector}/{creds_identifier}",
                    )
                    .route(web::get().to(payments::payments_redirect_response_with_creds_identifier)),
                )
                .service(
                    web::resource("/{payment_id}/{merchant_id}/redirect/response/{connector}")
                        .route(web::get().to(payments::payments_redirect_response))
                        .route(web::post().to(payments::payments_redirect_response))
                )
                .service(
                    web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}/{creds_identifier}")
                        .route(web::get().to(payments::payments_complete_authorize_redirect_with_creds_identifier))
                        .route(web::post().to(payments::payments_complete_authorize_redirect_with_creds_identifier))
                )
                .service(
                    web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}")
                        .route(web::get().to(payments::payments_complete_authorize_redirect))
                        .route(web::post().to(payments::payments_complete_authorize_redirect)),
                )
                .service(
                    web::resource("/{payment_id}/complete_authorize")
                        .route(web::post().to(payments::payments_complete_authorize)),
                )
                .service(
                    web::resource("/{payment_id}/incremental_authorization").route(web::post().to(payments::payments_incremental_authorization)),
                )
                .service(
                    web::resource("/{payment_id}/{merchant_id}/authorize/{connector}").route(web::post().to(payments::post_3ds_payments_authorize)),
                )
                .service(
                    web::resource("/{payment_id}/3ds/authentication").route(web::post().to(payments::payments_external_authentication)),
                )
                .service(
                    web::resource("/{payment_id}/extended_card_info").route(web::get().to(payments::retrieve_extended_card_info)),
                )
                .service(
                web::resource("{payment_id}/calculate_tax")
                    .route(web::post().to(payments::payments_dynamic_tax_calculation)),
                )
                .service(
                    web::resource("{payment_id}/update_metadata")
                        .route(web::post().to(payments::payments_update_metadata)),
                );
        }
        route
    }


pub fn get_payment_method_create_from_payment_method_migrate(
        card_number: CardNumber,
        payment_method_migrate: &PaymentMethodMigrate,
    ) -> Self {
        let card_details =
            payment_method_migrate
                .card
                .as_ref()
                .map(|payment_method_migrate_card| CardDetail {
                    card_number,
                    card_exp_month: payment_method_migrate_card.card_exp_month.clone(),
                    card_exp_year: payment_method_migrate_card.card_exp_year.clone(),
                    card_holder_name: payment_method_migrate_card.card_holder_name.clone(),
                    nick_name: payment_method_migrate_card.nick_name.clone(),
                    card_issuing_country: payment_method_migrate_card.card_issuing_country.clone(),
                    card_network: payment_method_migrate_card.card_network.clone(),
                    card_issuer: payment_method_migrate_card.card_issuer.clone(),
                    card_type: payment_method_migrate_card.card_type.clone(),
                });

        Self {
            customer_id: payment_method_migrate.customer_id.clone(),
            payment_method: payment_method_migrate.payment_method,
            payment_method_type: payment_method_migrate.payment_method_type,
            payment_method_issuer: payment_method_migrate.payment_method_issuer.clone(),
            payment_method_issuer_code: payment_method_migrate.payment_method_issuer_code,
            metadata: payment_method_migrate.metadata.clone(),
            payment_method_data: payment_method_migrate.payment_method_data.clone(),
            connector_mandate_details: payment_method_migrate
                .connector_mandate_details
                .clone()
                .map(|common_mandate_reference| {
                    PaymentsMandateReference::from(common_mandate_reference)
                }),
            client_secret: None,
            billing: payment_method_migrate.billing.clone(),
            card: card_details,
            card_network: payment_method_migrate.card_network.clone(),
            #[cfg(feature = "payouts")]
            bank_transfer: payment_method_migrate.bank_transfer.clone(),
            #[cfg(feature = "payouts")]
            wallet: payment_method_migrate.wallet.clone(),
            network_transaction_id: payment_method_migrate.network_transaction_id.clone(),
        }
    }


pub struct PaymentMethodSessionResponse {
    #[schema(value_type = String, example = "12345_pms_01926c58bc6e77c09e809964e72af8c8")]
    pub id: id_type::GlobalPaymentMethodSessionId,

    /// The customer id for which the payment methods session is to be created
    #[schema(value_type = String, example = "12345_cus_01926c58bc6e77c09e809964e72af8c8")]
    pub customer_id: id_type::GlobalCustomerId,

    /// The billing address details of the customer. This will also be used for any new payment methods added during the session
    #[schema(value_type = Option<Address>)]
    pub billing: Option<payments::Address>,

    /// The tokenization type to be applied
    #[schema(value_type = Option<PspTokenization>)]
    pub psp_tokenization: Option<common_types::payment_methods::PspTokenization>,

    /// The network tokenization configuration if applicable
    #[schema(value_type = Option<NetworkTokenization>)]
    pub network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,

    /// Contains data to be passed on to tokenization service ( if present ) to create token_id for given JSON data
    #[schema(value_type = Option<serde_json::Value>)]
    pub tokenization_data: Option<pii::SecretSerdeValue>,

    /// The iso timestamp when the session will expire
    /// Trying to retrieve the session or any operations on the session after this time will result in an error
    #[schema(value_type = PrimitiveDateTime, example = "2023-01-18T11:04:09.922Z")]
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub expires_at: time::PrimitiveDateTime,

    /// Client Secret
    #[schema(value_type = String)]
    pub client_secret: masking::Secret<String>,

    /// The return url to which the user should be redirected to
    #[schema(value_type = Option<String>)]
    pub return_url: Option<common_utils::types::Url>,

    /// The next action details for the payment method session
    #[schema(value_type = Option<NextActionData>)]
    pub next_action: Option<payments::NextActionData>,

    /// The customer authentication details for the payment method
    /// This refers to either the payment / external authentication details
    pub authentication_details: Option<AuthenticationDetails>,

    /// The payment method that was created using this payment method session
    #[schema(value_type = Option<Vec<String>>)]
    pub associated_payment_methods: Option<Vec<String>>,

    /// The token-id created if there is tokenization_data present
    #[schema(value_type = Option<String>, example = "12345_tok_01926c58bc6e77c09e809964e72af8c8")]
    pub associated_token_id: Option<id_type::GlobalTokenId>,
}


OpenAPI Block Path: components.schemas.RefundResponse
{
  "type": "object",
  "required": [
    "refund_id",
    "payment_id",
    "amount",
    "currency",
    "status",
    "connector"
  ],
  "properties": {
    "refund_id": {
      "type": "string",
      "description": "Unique Identifier for the refund"
    },
    "payment_id": {
      "type": "string",
      "description": "The payment id against which refund is initiated"
    },
    "amount": {
      "type": "integer",
      "format": "int64",
      "description": "The refund amount, which should be less than or equal to the total payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc",
      "example": 6540,
      "minimum": 100
    },
    "currency": {
      "type": "string",
      "description": "The three-letter ISO currency code"
    },
    "status": {
      "$ref": "#/components/schemas/RefundStatus"
    },
    "reason": {
      "type": "string",
      "description": "An arbitrary string attached to the object. Often useful for displaying to users and your customer support executive",
      "nullable": true
    },
    "metadata": {
      "type": "object",
      "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object",
      "nullable": true
    },
    "error_message": {
      "type": "string",
      "description": "The error message",
      "nullable": true
    },
    "error_code": {
      "type": "string",
      "description": "The code for the error",
      "nullable": true
    },
    "unified_code": {
      "type": "string",
      "description": "Error code unified across the connectors is received here if there was an error while calling connector",
      "nullable": true
    },
    "unified_message": {
      "type": "string",
      "description": "Error message unified across the connectors is received here if there was an error while calling connector",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "The timestamp at which refund is created",
      "nullable": true
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "The timestamp at which refund is updated",
      "nullable": true
    },
    "connector": {
      "type": "string",
      "description": "The connector used for the refund and the corresponding payment",
      "example": "stripe"
    },
    "profile_id": {
      "type": "string",
      "description": "The id of business profile for this refund",
      "nullable": true
    },
    "merchant_connector_id": {
      "type": "string",
      "description": "The merchant_connector_id of the processor through which this payment went through",
      "nullable": true
    },
    "split_refunds": {
      "allOf": [
        {
          "$ref": "#/components/schemas/SplitRefund"
        }
      ],
      "nullable": true
    },
    "issuer_error_code": {
      "type": "string",
      "description": "Error code received from the issuer in case of failed refunds",
      "nullable": true
    },
    "issuer_error_message": {
      "type": "string",
      "description": "Error message received from the issuer in case of failed refunds",
      "nullable": true
    }
  }
}


pub struct PaymeMetadata {
    payme_transaction_id: Option<String>,
}


impl api::PaymentPostSessionTokens for Paypal {}

impl api::PaymentPostSessionTokens for for Paypal


File: crates/analytics/src/outgoing_webhook_event/events.rs
 Public functions: 1
 Public structs: 1

use api_models::analytics::{outgoing_webhook_event::OutgoingWebhookLogsRequest, Granularity};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow},
};
pub trait OutgoingWebhookLogsFilterAnalytics: LoadRow<OutgoingWebhookLogsResult> {}

pub async fn get_outgoing_webhook_event<T>(
    merchant_id: &common_utils::id_type::MerchantId,
    query_param: OutgoingWebhookLogsRequest,
    pool: &T,
) -> FiltersResult<Vec<OutgoingWebhookLogsResult>>
where
    T: AnalyticsDataSource + OutgoingWebhookLogsFilterAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> =
        QueryBuilder::new(AnalyticsCollection::OutgoingWebhookEvent);
    query_builder.add_select_column("*").switch()?;

    query_builder
        .add_filter_clause("merchant_id", merchant_id)
        .switch()?;

    query_builder
        .add_filter_clause("payment_id", &query_param.payment_id)
        .switch()?;

    if let Some(event_id) = query_param.event_id {
        query_builder
            .add_filter_clause("event_id", &event_id)
            .switch()?;
    }
    if let Some(refund_id) = query_param.refund_id {
        query_builder
            .add_filter_clause("refund_id", &refund_id)
            .switch()?;
    }
    if let Some(dispute_id) = query_param.dispute_id {
        query_builder
            .add_filter_clause("dispute_id", &dispute_id)
            .switch()?;
    }
    if let Some(mandate_id) = query_param.mandate_id {
        query_builder
            .add_filter_clause("mandate_id", &mandate_id)
            .switch()?;
    }
    if let Some(payment_method_id) = query_param.payment_method_id {
        query_builder
            .add_filter_clause("payment_method_id", &payment_method_id)
            .switch()?;
    }
    if let Some(attempt_id) = query_param.attempt_id {
        query_builder
            .add_filter_clause("attempt_id", &attempt_id)
            .switch()?;
    }
    //TODO!: update the execute_query function to return reports instead of plain errors...
    query_builder
        .execute_query::<OutgoingWebhookLogsResult, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct OutgoingWebhookLogsResult {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub event_id: String,
    pub event_type: String,
    pub outgoing_webhook_event_type: String,
    pub payment_id: common_utils::id_type::PaymentId,
    pub refund_id: Option<String>,
    pub attempt_id: Option<String>,
    pub dispute_id: Option<String>,
    pub payment_method_id: Option<String>,
    pub mandate_id: Option<String>,
    pub content: Option<String>,
    pub is_error: bool,
    pub error: Option<String>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
}


pub struct CybersourceNotAvailableErrorObject {
    #[serde(rename = "type")]
    pub error_type: Option<String>,
    pub message: Option<String>,
}


pub async fn delete_tokenized_data(
    state: &routes::SessionState,
    lookup_key: &str,
) -> RouterResult<()> {
    let redis_key = get_redis_locker_key(lookup_key);
    let func = || async {
        metrics::DELETED_TOKENIZED_CARD.add(1, &[]);

        let redis_conn = state
            .store
            .get_redis_conn()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get redis connection")?;

        let response = redis_conn.delete_key(&redis_key.as_str().into()).await;

        match response {
            Ok(redis_interface::DelReply::KeyDeleted) => Ok(()),
            Ok(redis_interface::DelReply::KeyNotDeleted) => {
                Err(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Token invalid or expired")
            }
            Err(err) => {
                metrics::TEMP_LOCKER_FAILURES.add(1, &[]);
                Err(errors::ApiErrorResponse::InternalServerError).attach_printable_lazy(|| {
                    format!("Failed to delete from redis locker: {err:?}")
                })
            }
        }
    };
    match func().await {
        Ok(s) => {
            logger::info!(
                "Delete payload in redis locker successful with lookup key: {:?}",
                redis_key
            );
            Ok(s)
        }
        Err(err) => {
            logger::error!("Redis Temp locker Failed: {:?}", err);
            Err(err)
        }
    }
}


pub struct ArchipelRefundOrderResponse {
    id: String,
}


pub struct ClientAuthCheckInfoResponse {
    id: String,
    client_reference_information: ClientReferenceInformation,
    consumer_authentication_information: BarclaycardConsumerAuthInformationEnrollmentResponse,
    status: BarclaycardAuthEnrollmentStatus,
    error_information: Option<BarclaycardErrorInformation>,
}


pub struct BarclaycardRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}


**Full Changelog:** [`v1.83.1...v1.84.0`](https://github.com/juspay/hyperswitch/compare/v1.83.1...v1.84.0)

- - -

## 1.83.1 (2023-11-17)

### Bug Fixes

- **router:** Add choice to use the appropriate key for jws verification ([#2917](https://github.com/juspay/hyperswitch/pull/2917)) ([`606daa9`](https://github.com/juspay/hyperswitch/commit/606daa9367cac8c2ea926313019deab2f938b591))

**Full Changelog:** [`v1.83.0...v1.83.1`](https://github.com/juspay/hyperswitch/compare/v1.83.0...v1.83.1)

- - -

## 1.83.0 (2023-11-17)

### Features

- **events:** Add incoming webhook payload to api events logger ([#2852](https://github.com/juspay/hyperswitch/pull/2852)) ([`aea390a`](https://github.com/juspay/hyperswitch/commit/aea390a6a1c331f8e0dbea4f41218e43f7323508))
- **router:** Custom payment link config for payment create ([#2741](https://github.com/juspay/hyperswitch/pull/2741)) ([`c39beb2`](https://github.com/juspay/hyperswitch/commit/c39beb2501e63bbf7fd41bbc947280d7ff5a71dc))

### Bug Fixes

- **router:** Add rust locker url in proxy_bypass_urls ([#2902](https://github.com/juspay/hyperswitch/pull/2902)) ([`9a201ae`](https://github.com/juspay/hyperswitch/commit/9a201ae698c2cf52e617660f82d5bf1df2e797ae))

### Documentation

- **README:** Replace cloudformation deployment template with latest s3 url. ([#2891](https://github.com/juspay/hyperswitch/pull/2891)) ([`375108b`](https://github.com/juspay/hyperswitch/commit/375108b6df50e041fc9dbeb35a6a6b46b146037a))

**Full Changelog:** [`v1.82.0...v1.83.0`](https://github.com/juspay/hyperswitch/compare/v1.82.0...v1.83.0)

- - -

## 1.82.0 (2023-11-17)

### Features

- **router:** Add fallback while add card and retrieve card from rust locker ([#2888](https://github.com/juspay/hyperswitch/pull/2888)) ([`f735fb0`](https://github.com/juspay/hyperswitch/commit/f735fb0551812fd781a2db8bac5a0deef4cabb2b))

### Bug Fixes

- **core:** Introduce new attempt and intent status to handle multiple partial captures ([#2802](https://github.com/juspay/hyperswitch/pull/2802)) ([`cb88be0`](https://github.com/juspay/hyperswitch/commit/cb88be01f22725948648976c2a5606a03b5ce92a))

### Testing

- **postman:** Update postman collection files ([`7d05b74`](https://github.com/juspay/hyperswitch/commit/7d05b74b950d9e078b063e17d046cbeb501d006a))

**Full Changelog:** [`v1.81.0...v1.82.0`](https://github.com/juspay/hyperswitch/compare/v1.81.0...v1.82.0)

- - -

## 1.81.0 (2023-11-16)

### Features

- **connector:**
  - [BANKOFAMERICA] Implement Cards for Bank of America ([#2765](https://github.com/juspay/hyperswitch/pull/2765)) ([`e8de3a7`](https://github.com/juspay/hyperswitch/commit/e8de3a710710b92f5c2351c5d67c22352c2b0a30))
  - [ProphetPay] Implement Card Redirect PaymentMethodType and flows for Authorize, CompleteAuthorize, Psync, Refund, Rsync and Void ([#2641](https://github.com/juspay/hyperswitch/pull/2641)) ([`8d4adc5`](https://github.com/juspay/hyperswitch/commit/8d4adc52af57ed0994e6efbb5b2d0d3df3fb3150))

### Testing

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

**Full Changelog:** [`v1.80.0...v1.81.0`](https://github.com/juspay/hyperswitch/compare/v1.80.0...v1.81.0)

- - -

## 1.80.0 (2023-11-16)

### Features

- **router:** Add api to migrate card from basilisk to rust ([#2853](https://github.com/juspay/hyperswitch/pull/2853)) ([`b8b20c4`](https://github.com/juspay/hyperswitch/commit/b8b20c412df0485bf395f9aa21e6e34e90d97acd))
- Spawn webhooks and async scheduling in background ([#2780](https://github.com/juspay/hyperswitch/pull/2780)) ([`f248fe2`](https://github.com/juspay/hyperswitch/commit/f248fe2889c9cb68af4464ab0db1735224ab5c8d))

### Refactors

- **router:** Add openapi spec support for gsm apis ([#2871](https://github.com/juspay/hyperswitch/pull/2871)) ([`62c9cca`](https://github.com/juspay/hyperswitch/commit/62c9ccae6ab0d128c54962675b88739ad7797fe6))

**Full Changelog:** [`v1.79.0...v1.80.0`](https://github.com/juspay/hyperswitch/compare/v1.79.0...v1.80.0)

- - -

## 1.79.0 (2023-11-16)

### Features

- Change async-bb8 fork and tokio spawn for concurrent database calls ([#2774](https://github.com/juspay/hyperswitch/pull/2774)) ([`d634fde`](https://github.com/juspay/hyperswitch/commit/d634fdeac349b92e3619234580299a6c6c38e6d4))

### Bug Fixes

- **connector:** [noon] add validate psync reference ([#2886](https://github.com/juspay/hyperswitch/pull/2886)) ([`b129023`](https://github.com/juspay/hyperswitch/commit/b1290234ba13de2dd8cc4210f63bae514c2988b4))
- **payment_link:** Render SDK for status requires_payment_method ([#2887](https://github.com/juspay/hyperswitch/pull/2887)) ([`d4d2c2c`](https://github.com/juspay/hyperswitch/commit/d4d2c2c7076a46996aa0aa74d1df827169f73155))
- Paypal postman collection changes for surcharge feature ([#2884](https://github.com/juspay/hyperswitch/pull/2884)) ([`5956242`](https://github.com/juspay/hyperswitch/commit/5956242588ef7bdbaa1804a952d48dc47c6e15f1))

### Testing

- **postman:** Update postman collection files ([`5c31365`](https://github.com/juspay/hyperswitch/commit/5c313656a129362b0e905e5fbf349dbbec57199c))

**Full Changelog:** [`v1.78.0...v1.79.0`](https://github.com/juspay/hyperswitch/compare/v1.78.0...v1.79.0)

- - -

## 1.78.0 (2023-11-14)

### Features

- **router:** Add automatic retries and step up 3ds flow ([#2834](https://github.com/juspay/hyperswitch/pull/2834)) ([`d2968c9`](https://github.com/juspay/hyperswitch/commit/d2968c94978a57422fa46a8195d906736a95b864))
- Payment link status page UI ([#2740](https://github.com/juspay/hyperswitch/pull/2740)) ([`856c7af`](https://github.com/juspay/hyperswitch/commit/856c7af77e17599ca0d4d119744ac582e9c3c971))

### Bug Fixes

- Handle session and confirm flow discrepancy in surcharge details ([#2696](https://github.com/juspay/hyperswitch/pull/2696)) ([`cafea45`](https://github.com/juspay/hyperswitch/commit/cafea45982d7b520fe68fde967984ce88f68c6c0))

**Full Changelog:** [`v1.77.0...v1.78.0`](https://github.com/juspay/hyperswitch/compare/v1.77.0...v1.78.0)

- - -

## 1.77.0 (2023-11-13)

### Features

- **apievent:** Added hs latency to api event ([#2734](https://github.com/juspay/hyperswitch/pull/2734)) ([`c124511`](https://github.com/juspay/hyperswitch/commit/c124511052ed8911a2ccfcf648c0793b5c1ca690))
- **router:**
  - Add new JWT authentication variants and use them ([#2835](https://github.com/juspay/hyperswitch/pull/2835)) ([`f88eee7`](https://github.com/juspay/hyperswitch/commit/f88eee7362be2cc3e8e8dc2bb7bfd263892ff01e))
  - Profile specific fallback derivation while routing payments ([#2806](https://github.com/juspay/hyperswitch/pull/2806)) ([`8e538db`](https://github.com/juspay/hyperswitch/commit/8e538dbd5c189047d0a0b24fa752b9a1c67554f5))

### Build System / Dependencies

- **deps:** Remove unused dependencies and features ([#2854](https://github.com/juspay/hyperswitch/pull/2854)) ([`0553587`](https://github.com/juspay/hyperswitch/commit/05535871152f4a6ac24ce6b5b5390da13cc29b96))

**Full Changelog:** [`v1.76.0...v1.77.0`](https://github.com/juspay/hyperswitch/compare/v1.76.0...v1.77.0)

- - -

## 1.76.0 (2023-11-12)

### Features

- **analytics:** Analytics APIs  ([#2792](https://github.com/juspay/hyperswitch/pull/2792)) ([`f847802`](https://github.com/juspay/hyperswitch/commit/f847802339bfedb24cbaa47ad55e31d80cefddca))
- **router:** Added Payment link new design ([#2731](https://github.com/juspay/hyperswitch/pull/2731)) ([`2a4f5d1`](https://github.com/juspay/hyperswitch/commit/2a4f5d13717a78dc2e2e4fc9a492a45b92151dbe))
- **user:** Setup user tables ([#2803](https://github.com/juspay/hyperswitch/pull/2803)) ([`20c4226`](https://github.com/juspay/hyperswitch/commit/20c4226a36e4650a3ba8811b758ac5f7969bcfb3))

### Refactors

- **connector:** [Zen] change error message from NotSupported to NotImplemented ([#2831](https://github.com/juspay/hyperswitch/pull/2831)) ([`b5ea8db`](https://github.com/juspay/hyperswitch/commit/b5ea8db2d2b7e7544931704a7191b42d3a8299be))
- **core:** Remove connector response table and use payment_attempt instead ([#2644](https://github.com/juspay/hyperswitch/pull/2644)) ([`966369b`](https://github.com/juspay/hyperswitch/commit/966369b6f2c205b59524c23ad3b21ebab547631f))
- **events:** Update api events to follow snake case naming ([#2828](https://github.com/juspay/hyperswitch/pull/2828)) ([`b3d5062`](https://github.com/juspay/hyperswitch/commit/b3d5062dc07676ec12e903b1999fdd9138c0891d))

### Documentation

- **README:** Add bootstrap button for cloudformation deployment ([#2827](https://github.com/juspay/hyperswitch/pull/2827)) ([`e67e808`](https://github.com/juspay/hyperswitch/commit/e67e808d70d41c371fff168824e5a4dbb8b3a040))

**Full Changelog:** [`v1.75.0...v1.76.0`](https://github.com/juspay/hyperswitch/compare/v1.75.0...v1.76.0)

- - -

## 1.75.0 (2023-11-09)

### Features

- **events:** Add extracted fields based on req/res types ([#2795](https://github.com/juspay/hyperswitch/pull/2795)) ([`8985794`](https://github.com/juspay/hyperswitch/commit/89857941b09c5fbe0f3e7d5b4f908bb144ae162d))
- **router:**
  - Added merchant custom name support for payment link ([#2685](https://github.com/juspay/hyperswitch/pull/2685)) ([`8b15189`](https://github.com/juspay/hyperswitch/commit/8b151898dc0d8eefe5ed2bbdafe59e8f58b4698c))
  - Add `gateway_status_map` CRUD APIs ([#2809](https://github.com/juspay/hyperswitch/pull/2809)) ([`5c9e235`](https://github.com/juspay/hyperswitch/commit/5c9e235bd30dd3e03d086a83613edfcc62b2ead2))

### Bug Fixes

- **analytics:** Added hs latency to api event for paymentconfirm call ([#2787](https://github.com/juspay/hyperswitch/pull/2787)) ([`aab8f60`](https://github.com/juspay/hyperswitch/commit/aab8f6035c16ca19009f8f1e0db688c17bc0b2b6))
- [mollie] locale validation irrespective of auth type ([#2814](https://github.com/juspay/hyperswitch/pull/2814)) ([`25a73c2`](https://github.com/juspay/hyperswitch/commit/25a73c29a4c4715a54862dd6a28c875fd3752f63))

**Full Changelog:** [`v1.74.0...v1.75.0`](https://github.com/juspay/hyperswitch/compare/v1.74.0...v1.75.0)

- - -

## 1.74.0 (2023-11-08)

### Features

- **core:** Use redis as temp locker instead of basilisk ([#2789](https://github.com/juspay/hyperswitch/pull/2789)) ([`6678689`](https://github.com/juspay/hyperswitch/commit/6678689265ae9a4fbb7a43c1938237d349c5a68e))
- **events:** Add request details to api events ([#2769](https://github.com/juspay/hyperswitch/pull/2769)) ([`164d1c6`](https://github.com/juspay/hyperswitch/commit/164d1c66fbcb84104db07412496114db2f8c5c0c))
- **router:** Add `gateway_status_map` interface ([#2804](https://github.com/juspay/hyperswitch/pull/2804)) ([`a429b23`](https://github.com/juspay/hyperswitch/commit/a429b23c7f21c9d08a79895c0b770b35aab725f7))
- **test_utils:** Add custom-headers and custom delay support to rustman ([#2636](https://github.com/juspay/hyperswitch/pull/2636)) ([`1effddd`](https://github.com/juspay/hyperswitch/commit/1effddd0a0d3985d6df03c4ae9be28712befc05e))

### Bug Fixes

- **connector:** Add attempt_status in field in error_response ([#2794](https://github.com/juspay/hyperswitch/pull/2794)) ([`5642fef`](https://github.com/juspay/hyperswitch/commit/5642fef52a6d591d12c5745ed381f41a1593f183))

### Refactors

- **config:** Update payment method filter of Klarna in Stripe ([#2807](https://github.com/juspay/hyperswitch/pull/2807)) ([`21ce807`](https://github.com/juspay/hyperswitch/commit/21ce8079f4cb11d70c5eaae78f83773141c67d0c))
- **router:** Add parameter connectors to get_request_body function ([#2708](https://github.com/juspay/hyperswitch/pull/2708)) ([`7623ea9`](https://github.com/juspay/hyperswitch/commit/7623ea93bee61b0bb22b68e86f44de17f04f876b))

### Documentation

- **README:** Update README ([#2800](https://github.com/juspay/hyperswitch/pull/2800)) ([`bef0a04`](https://github.com/juspay/hyperswitch/commit/bef0a04edc6323b3b7a2e0dd7eeb7954915ba7cf))

**Full Changelog:** [`v1.73.0...v1.74.0`](https://github.com/juspay/hyperswitch/compare/v1.73.0...v1.74.0)

- - -

## 1.73.0 (2023-11-07)

### Features

- **connector:**
  - [BANKOFAMERICA] Add Connector Template Code ([#2764](https://github.com/juspay/hyperswitch/pull/2764)) ([`4563935`](https://github.com/juspay/hyperswitch/commit/4563935372d2cdff3f746fa86a47f1166ffd32ac))
  - [Bitpay] Add order id as the reference id ([#2591](https://github.com/juspay/hyperswitch/pull/2591)) ([`d47d4ac`](https://github.com/juspay/hyperswitch/commit/d47d4ac682705d6ac692f9381149bbf08ad71264))
- **router:** Make webhook events config disabled only and by default enable all the events ([#2770](https://github.com/juspay/hyperswitch/pull/2770)) ([`d335879`](https://github.com/juspay/hyperswitch/commit/d335879f9289b57a90a76c6587a58a0b3e12c9ad))
- Make drainer logs queryable with request_id and global_id ([#2771](https://github.com/juspay/hyperswitch/pull/2771)) ([`ff73aba`](https://github.com/juspay/hyperswitch/commit/ff73aba8e72d8e072027881760335c0c818df665))

### Bug Fixes

- **connector:** Fix amount conversion incase of minor unit  ([#2793](https://github.com/juspay/hyperswitch/pull/2793)) ([`34f5226`](https://github.com/juspay/hyperswitch/commit/34f52260d3fa68b54e5b46207afaf2ad07a8d8ba))

### Refactors

- **payment_methods:** Added support for account subtype in pmd ([#2651](https://github.com/juspay/hyperswitch/pull/2651)) ([`e7375d0`](https://github.com/juspay/hyperswitch/commit/e7375d0e26099a7e0e6efd1b83b8eb9c7b1c5411))

### Documentation

- **README:** Add one-click deployment information using CDK ([#2798](https://github.com/juspay/hyperswitch/pull/2798)) ([`bb39cd4`](https://github.com/juspay/hyperswitch/commit/bb39cd4081fdcaf68b2b5de2234e93493dbd84b6))

**Full Changelog:** [`v1.72.0...v1.73.0`](https://github.com/juspay/hyperswitch/compare/v1.72.0...v1.73.0)

- - -

## 1.72.0 (2023-11-05)

### Features

- **connector:**
  - [ACI] Currency Unit Conversion ([#2750](https://github.com/juspay/hyperswitch/pull/2750)) ([`cdead78`](https://github.com/juspay/hyperswitch/commit/cdead78ea6a1f2dce92187f499f54498ba4bb173))
  - [Fiserv] Currency Unit Conversion ([#2715](https://github.com/juspay/hyperswitch/pull/2715)) ([`b6b9e4f`](https://github.com/juspay/hyperswitch/commit/b6b9e4f912e1c61cd31ab91be587ffb08c9f3a5b))
  - [Bitpay] Use `connector_request_reference_id` as reference to the connector ([#2697](https://github.com/juspay/hyperswitch/pull/2697)) ([`7141b89`](https://github.com/juspay/hyperswitch/commit/7141b89d231bae0c3b1c10095b88df16129b1665))
  - [NMI] Currency Unit Conversion ([#2707](https://github.com/juspay/hyperswitch/pull/2707)) ([`1b45a30`](https://github.com/juspay/hyperswitch/commit/1b45a302630ed8affc5abff0de1325fb5c6f870e))
  - [Payeezy] Currency Unit Conversion ([#2710](https://github.com/juspay/hyperswitch/pull/2710)) ([`25245b9`](https://github.com/juspay/hyperswitch/commit/25245b965371d93449f4584667adeb38ab7e0e59))

### Refactors

- **connector:** [Stax] Currency Unit Conversion ([#2711](https://github.com/juspay/hyperswitch/pull/2711)) ([`2782923`](https://github.com/juspay/hyperswitch/commit/278292322c7c06f4239dd73861469e436bd941fa))

### Testing

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

**Full Changelog:** [`v1.71.0...v1.72.0`](https://github.com/juspay/hyperswitch/compare/v1.71.0...v1.72.0)

- - -

## 1.71.0 (2023-11-03)

### Features

- **merchant_connector_account:** Add cache for querying by `merchant_connector_id` ([#2738](https://github.com/juspay/hyperswitch/pull/2738)) ([`1ba6282`](https://github.com/juspay/hyperswitch/commit/1ba6282699b7dff5e6e95c9a14e51c0f8bf749cd))
- **router:** Add Smart Routing to route payments efficiently ([#2665](https://github.com/juspay/hyperswitch/pull/2665)) ([`9b618d2`](https://github.com/juspay/hyperswitch/commit/9b618d24476967d364835d04010d9076a80aeb9c))

### Bug Fixes

- **connector:**
  - [Cryptopay]Remove default case handling for Cryptopay ([#2699](https://github.com/juspay/hyperswitch/pull/2699)) ([`255a4f8`](https://github.com/juspay/hyperswitch/commit/255a4f89a8e0124310d42bb63ad459bd8cde2cba))
  - [Bluesnap] fix psync status to failure when it is '403'  ([#2772](https://github.com/juspay/hyperswitch/pull/2772)) ([`9314d14`](https://github.com/juspay/hyperswitch/commit/9314d1446326fd8a69f1f69657a976bbe7c27901))
- Response spelling ([#2779](https://github.com/juspay/hyperswitch/pull/2779)) ([`5859372`](https://github.com/juspay/hyperswitch/commit/585937204d9071baa37d402f73159f8f650d0a07))

### Testing

- **postman:** Update postman collection files ([`21e8a10`](https://github.com/juspay/hyperswitch/commit/21e8a105f9b47ded232b457a0420ad71ec2414ed))

**Full Changelog:** [`v1.70.1...v1.71.0`](https://github.com/juspay/hyperswitch/compare/v1.70.1...v1.71.0)

- - -

## 1.70.1 (2023-11-03)

### Revert

- Fix(analytics): feat(analytics): analytics APIs ([#2777](https://github.com/juspay/hyperswitch/pull/2777)) ([`169d33b`](https://github.com/juspay/hyperswitch/commit/169d33bf8157b1a9910c841c8c55eddc4d2ad168))

**Full Changelog:** [`v1.70.0...v1.70.1`](https://github.com/juspay/hyperswitch/compare/v1.70.0...v1.70.1)

- - -

## 1.70.0 (2023-11-03)

### Features

- **analytics:** Analytics APIs ([#2676](https://github.com/juspay/hyperswitch/pull/2676)) ([`c0a5e7b`](https://github.com/juspay/hyperswitch/commit/c0a5e7b7d945095053606e35c9bb23a06090c4e3))
- **connector:** [Multisafepay] add error handling ([#2595](https://github.com/juspay/hyperswitch/pull/2595)) ([`b3c846d`](https://github.com/juspay/hyperswitch/commit/b3c846d637dd32a2d6d7044c118abbb2616642f0))
- **events:** Add api auth type details to events ([#2760](https://github.com/juspay/hyperswitch/pull/2760)) ([`1094493`](https://github.com/juspay/hyperswitch/commit/10944937a02502e0727f16368d8d055e575dd518))

### Bug Fixes


pub struct FormattingLayer<W, F>
where
    W: for<'a> MakeWriter<'a> + 'static,
    F: Formatter + Clone,
{
    dst_writer: W,
    pid: u32,
    hostname: String,
    env: String,
    service: String,
    #[cfg(feature = "vergen")]
    version: String,
    #[cfg(feature = "vergen")]
    build: String,
    default_fields: HashMap<String, Value>,
    formatter: F,
}


impl AccountReferenceMap {
    pub fn new(
        hash_map: HashMap<id_type::MerchantConnectorAccountId, String>,
    ) -> Result<Self, api_error_response::ApiErrorResponse> {
        Self::validate(&hash_map)?;

        let recovery_to_billing = hash_map.clone();
        let mut billing_to_recovery = HashMap::new();

        for (key, value) in &hash_map {
            billing_to_recovery.insert(value.clone(), key.clone());
        }

        Ok(Self {
            recovery_to_billing,
            billing_to_recovery,
        })
    }

    fn validate(
        hash_map: &HashMap<id_type::MerchantConnectorAccountId, String>,
    ) -> Result<(), api_error_response::ApiErrorResponse> {
        let mut seen_values = std::collections::HashSet::new(); // To check uniqueness of values

        for value in hash_map.values() {
            if !seen_values.insert(value.clone()) {
                return Err(api_error_response::ApiErrorResponse::InvalidRequestData {
                    message: "Duplicate account reference IDs found in Recovery feature metadata. Each account reference ID must be unique.".to_string(),
                });
            }
        }
        Ok(())
    }
}


pub async fn log_refund_delete(
        &self,
        delete_old_refund: &Refund,
        tenant_id: TenantID,
    ) -> MQResult<()> {
        self.log_event(&KafkaEvent::old(
            &KafkaRefund::from_storage(delete_old_refund),
            tenant_id.clone(),
            self.ckh_database_name.clone(),
        ))
        .attach_printable_lazy(|| {
            format!("Failed to add negative refund event {delete_old_refund:?}")
        })
    }


Documentation: crates/config_importer/README.md
# Type: Doc File

# config_importer

A simple utility tool to import a Hyperswitch TOML configuration file, convert
it into environment variable key-value pairs, and export it in the specified
format.
As of now, it supports only exporting the environment variables to a JSON format
compatible with Kubernetes, but it can be easily extended to export to a YAML
format compatible with Kubernetes or the env file format.

## Usage

You can find the usage information from the help message by specifying the
`--help` flag:

```shell
cargo run --bin config_importer -- --help
```

### Specifying the output location

If the `--output-file` flag is not specified, the utility prints the output to
stdout.
If you would like to write the output to a file instead, you can specify the
`--output-file` flag with the path to the output file:

```shell
cargo run --bin config_importer -- --input-file config/development.toml --output-file config/development.json
```

### Specifying a different prefix

If the `--prefix` flag is not specified, the default prefix `ROUTER` is
considered, which generates the environment variables accepted by the `router`
binary/application.
If you'd want to generate environment variables for the `drainer`
binary/application, then you can specify the `--prefix` flag with value
`drainer` (or `DRAINER`, both work).

```shell
cargo run --bin config_importer -- --input-file config/drainer.toml --prefix drainer
```


impl api::PaymentToken for Juspaythreedsserver {}

impl api::PaymentToken for for Juspaythreedsserver


pub struct CoinbaseProcessingFee {
    pub local: Option<OverpaymentAbsoluteThreshold>,
    pub crypto: OverpaymentAbsoluteThreshold,
}


impl api::PaymentToken for UnifiedAuthenticationService {}

impl api::PaymentToken for for UnifiedAuthenticationService


pub fn is_empty_result(&self) -> bool {
        self.surcharge_results.is_empty()
    }


/// Checks if the decision is to mandate a 3DS challenge
    pub fn should_force_3ds_challenge(self) -> bool {
        matches!(self, Self::ChallengeRequested)
    }


impl RelayWebhooks {
    pub fn server(state: AppState) -> Scope {
        use api_models::webhooks as webhook_type;
        web::scope("/webhooks/relay")
            .app_data(web::Data::new(state))
            .service(web::resource("/{merchant_id}/{connector_id}").route(
                web::post().to(receive_incoming_relay_webhook::<webhook_type::OutgoingWebhook>),
            ))
    }
}


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


impl StorageInterface for MockDb {
    fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface> {
        Box::new(self.clone())
    }
    fn get_payment_methods_store(&self) -> Box<dyn PaymentMethodsStorageInterface> {
        Box::new(self.clone())
    }

    fn get_cache_store(&self) -> Box<(dyn RedisConnInterface + Send + Sync + 'static)> {
        Box::new(self.clone())
    }
}


impl NuveiAuthorizePreprocessingCommon for SetupMandateRequestData {
    fn get_browser_info(&self) -> Option<BrowserInformation> {
        self.browser_info.clone()
    }

    fn get_related_transaction_id(&self) -> Option<String> {
        self.related_transaction_id.clone()
    }
    fn get_is_moto(&self) -> Option<bool> {
        match self.payment_channel {
            Some(PaymentChannel::MailOrder) | Some(PaymentChannel::TelephoneOrder) => Some(true),
            _ => None,
        }
    }

    fn get_customer_id_required(&self) -> Option<CustomerId> {
        self.customer_id.clone()
    }

    fn get_complete_authorize_url(&self) -> Option<String> {
        self.complete_authorize_url.clone()
    }

    fn get_connector_mandate_id(&self) -> Option<String> {
        self.mandate_id.as_ref().and_then(|mandate_ids| {
            mandate_ids.mandate_reference_id.as_ref().and_then(
                |mandate_ref_id| match mandate_ref_id {
                    api_models::payments::MandateReferenceId::ConnectorMandateId(id) => {
                        id.get_connector_mandate_id()
                    }
                    _ => None,
                },
            )
        })
    }

    fn get_return_url_required(
        &self,
    ) -> Result<String, error_stack::Report<errors::ConnectorError>> {
        self.router_return_url
            .clone()
            .ok_or_else(missing_field_err("return_url"))
    }

    fn get_capture_method(&self) -> Option<CaptureMethod> {
        self.capture_method
    }

    fn get_currency_required(
        &self,
    ) -> Result<enums::Currency, error_stack::Report<errors::ConnectorError>> {
        Ok(self.currency)
    }
    fn get_payment_method_data_required(
        &self,
    ) -> Result<PaymentMethodData, error_stack::Report<errors::ConnectorError>> {
        Ok(self.payment_method_data.clone())
    }
    fn get_order_tax_amount(
        &self,
    ) -> Result<Option<MinorUnit>, error_stack::Report<errors::ConnectorError>> {
        Ok(None)
    }

    fn get_minor_amount_required(
        &self,
    ) -> Result<MinorUnit, error_stack::Report<errors::ConnectorError>> {
        self.minor_amount
            .ok_or_else(missing_field_err("minor_amount"))
    }

    fn get_is_partial_approval(&self) -> Option<PartialApprovalFlag> {
        self.enable_partial_authorization
            .map(PartialApprovalFlag::from)
    }

    fn get_email_required(&self) -> Result<Email, error_stack::Report<errors::ConnectorError>> {
        self.email.clone().ok_or_else(missing_field_err("email"))
    }
    fn is_customer_initiated_mandate_payment(&self) -> bool {
        (self.customer_acceptance.is_some() || self.setup_mandate_details.is_some())
            && self.setup_future_usage == Some(FutureUsage::OffSession)
    }
    fn get_ntid(&self) -> Option<String> {
        None
    }
}

impl NuveiAuthorizePreprocessingCommon for for SetupMandateRequestData