File: crates/api_models/src/analytics/auth_events.rs
 Public functions: 1
 Public structs: 14

use std::{
    collections::hash_map::DefaultHasher,
    hash::{Hash, Hasher},
};

use common_enums::{
    AuthenticationConnectors, AuthenticationStatus, Currency, DecoupledAuthenticationType,
    TransactionStatus,
};

use super::{NameDescription, TimeRange};

#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct AuthEventFilters {
    #[serde(default)]
    pub authentication_status: Vec<AuthenticationStatus>,
    #[serde(default)]
    pub trans_status: Vec<TransactionStatus>,
    #[serde(default)]
    pub authentication_type: Vec<DecoupledAuthenticationType>,
    #[serde(default)]
    pub error_message: Vec<String>,
    #[serde(default)]
    pub authentication_connector: Vec<AuthenticationConnectors>,
    #[serde(default)]
    pub message_version: Vec<String>,
    #[serde(default)]
    pub platform: Vec<String>,
    #[serde(default)]
    pub acs_reference_number: Vec<String>,
    #[serde(default)]
    pub mcc: Vec<String>,
    #[serde(default)]
    pub currency: Vec<Currency>,
    #[serde(default)]
    pub merchant_country: Vec<String>,
    #[serde(default)]
    pub billing_country: Vec<String>,
    #[serde(default)]
    pub shipping_country: Vec<String>,
    #[serde(default)]
    pub issuer_country: Vec<String>,
    #[serde(default)]
    pub earliest_supported_version: Vec<String>,
    #[serde(default)]
    pub latest_supported_version: Vec<String>,
    #[serde(default)]
    pub whitelist_decision: Vec<bool>,
    #[serde(default)]
    pub device_manufacturer: Vec<String>,
    #[serde(default)]
    pub device_type: Vec<String>,
    #[serde(default)]
    pub device_brand: Vec<String>,
    #[serde(default)]
    pub device_os: Vec<String>,
    #[serde(default)]
    pub device_display: Vec<String>,
    #[serde(default)]
    pub browser_name: Vec<String>,
    #[serde(default)]
    pub browser_version: Vec<String>,
    #[serde(default)]
    pub issuer_id: Vec<String>,
    #[serde(default)]
    pub scheme_name: Vec<String>,
    #[serde(default)]
    pub exemption_requested: Vec<bool>,
    #[serde(default)]
    pub exemption_accepted: Vec<bool>,
}

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    strum::AsRefStr,
    PartialEq,
    PartialOrd,
    Eq,
    Ord,
    strum::Display,
    strum::EnumIter,
    Clone,
    Copy,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum AuthEventDimensions {
    AuthenticationStatus,
    #[strum(serialize = "trans_status")]
    #[serde(rename = "trans_status")]
    TransactionStatus,
    AuthenticationType,
    ErrorMessage,
    AuthenticationConnector,
    MessageVersion,
    AcsReferenceNumber,
    Platform,
    Mcc,
    Currency,
    MerchantCountry,
    BillingCountry,
    ShippingCountry,
    IssuerCountry,
    EarliestSupportedVersion,
    LatestSupportedVersion,
    WhitelistDecision,
    DeviceManufacturer,
    DeviceType,
    DeviceBrand,
    DeviceOs,
    DeviceDisplay,
    BrowserName,
    BrowserVersion,
    IssuerId,
    SchemeName,
    ExemptionRequested,
    ExemptionAccepted,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumIter,
    strum::AsRefStr,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum AuthEventMetrics {
    AuthenticationCount,
    AuthenticationAttemptCount,
    AuthenticationSuccessCount,
    ChallengeFlowCount,
    FrictionlessFlowCount,
    FrictionlessSuccessCount,
    ChallengeAttemptCount,
    ChallengeSuccessCount,
    AuthenticationErrorMessage,
    AuthenticationFunnel,
    AuthenticationExemptionApprovedCount,
    AuthenticationExemptionRequestedCount,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumIter,
    strum::AsRefStr,
)]
pub enum AuthEventFlows {
    IncomingWebhookReceive,
    PaymentsExternalAuthentication,
}

pub mod metric_behaviour {
    pub struct AuthenticationCount;
    pub struct AuthenticationAttemptCount;
    pub struct AuthenticationSuccessCount;
    pub struct ChallengeFlowCount;
    pub struct FrictionlessFlowCount;
    pub struct FrictionlessSuccessCount;
    pub struct ChallengeAttemptCount;
    pub struct ChallengeSuccessCount;
    pub struct AuthenticationErrorMessage;
    pub struct AuthenticationFunnel;
}

impl From<AuthEventMetrics> for NameDescription {
    fn from(value: AuthEventMetrics) -> Self {
        Self {
            name: value.to_string(),
            desc: String::new(),
        }
    }
}

impl From<AuthEventDimensions> for NameDescription {
    fn from(value: AuthEventDimensions) -> Self {
        Self {
            name: value.to_string(),
            desc: String::new(),
        }
    }
}

#[derive(Debug, serde::Serialize, Eq)]
pub struct AuthEventMetricsBucketIdentifier {
    pub authentication_status: Option<AuthenticationStatus>,
    pub trans_status: Option<TransactionStatus>,
    pub authentication_type: Option<DecoupledAuthenticationType>,
    pub error_message: Option<String>,
    pub authentication_connector: Option<AuthenticationConnectors>,
    pub message_version: Option<String>,
    pub acs_reference_number: Option<String>,
    pub mcc: Option<String>,
    pub currency: Option<Currency>,
    pub merchant_country: Option<String>,
    pub billing_country: Option<String>,
    pub shipping_country: Option<String>,
    pub issuer_country: Option<String>,
    pub earliest_supported_version: Option<String>,
    pub latest_supported_version: Option<String>,
    pub whitelist_decision: Option<bool>,
    pub device_manufacturer: Option<String>,
    pub device_type: Option<String>,
    pub device_brand: Option<String>,
    pub device_os: Option<String>,
    pub device_display: Option<String>,
    pub browser_name: Option<String>,
    pub browser_version: Option<String>,
    pub issuer_id: Option<String>,
    pub scheme_name: Option<String>,
    pub exemption_requested: Option<bool>,
    pub exemption_accepted: Option<bool>,
    #[serde(rename = "time_range")]
    pub time_bucket: TimeRange,
    #[serde(rename = "time_bucket")]
    #[serde(with = "common_utils::custom_serde::iso8601custom")]
    pub start_time: time::PrimitiveDateTime,
}

impl AuthEventMetricsBucketIdentifier {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        authentication_status: Option<AuthenticationStatus>,
        trans_status: Option<TransactionStatus>,
        authentication_type: Option<DecoupledAuthenticationType>,
        error_message: Option<String>,
        authentication_connector: Option<AuthenticationConnectors>,
        message_version: Option<String>,
        acs_reference_number: Option<String>,
        mcc: Option<String>,
        currency: Option<Currency>,
        merchant_country: Option<String>,
        billing_country: Option<String>,
        shipping_country: Option<String>,
        issuer_country: Option<String>,
        earliest_supported_version: Option<String>,
        latest_supported_version: Option<String>,
        whitelist_decision: Option<bool>,
        device_manufacturer: Option<String>,
        device_type: Option<String>,
        device_brand: Option<String>,
        device_os: Option<String>,
        device_display: Option<String>,
        browser_name: Option<String>,
        browser_version: Option<String>,
        issuer_id: Option<String>,
        scheme_name: Option<String>,
        exemption_requested: Option<bool>,
        exemption_accepted: Option<bool>,
        normalized_time_range: TimeRange,
    ) -> Self {
        Self {
            authentication_status,
            trans_status,
            authentication_type,
            error_message,
            authentication_connector,
            message_version,
            acs_reference_number,
            mcc,
            currency,
            merchant_country,
            billing_country,
            shipping_country,
            issuer_country,
            earliest_supported_version,
            latest_supported_version,
            whitelist_decision,
            device_manufacturer,
            device_type,
            device_brand,
            device_os,
            device_display,
            browser_name,
            browser_version,
            issuer_id,
            scheme_name,
            exemption_requested,
            exemption_accepted,
            time_bucket: normalized_time_range,
            start_time: normalized_time_range.start_time,
        }
    }
}

impl Hash for AuthEventMetricsBucketIdentifier {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.authentication_status.hash(state);
        self.trans_status.hash(state);
        self.authentication_type.hash(state);
        self.authentication_connector.hash(state);
        self.message_version.hash(state);
        self.acs_reference_number.hash(state);
        self.error_message.hash(state);
        self.mcc.hash(state);
        self.currency.hash(state);
        self.merchant_country.hash(state);
        self.billing_country.hash(state);
        self.shipping_country.hash(state);
        self.issuer_country.hash(state);
        self.earliest_supported_version.hash(state);
        self.latest_supported_version.hash(state);
        self.whitelist_decision.hash(state);
        self.device_manufacturer.hash(state);
        self.device_type.hash(state);
        self.device_brand.hash(state);
        self.device_os.hash(state);
        self.device_display.hash(state);
        self.browser_name.hash(state);
        self.browser_version.hash(state);
        self.issuer_id.hash(state);
        self.scheme_name.hash(state);
        self.exemption_requested.hash(state);
        self.exemption_accepted.hash(state);
        self.time_bucket.hash(state);
    }
}

impl PartialEq for AuthEventMetricsBucketIdentifier {
    fn eq(&self, other: &Self) -> bool {
        let mut left = DefaultHasher::new();
        self.hash(&mut left);
        let mut right = DefaultHasher::new();
        other.hash(&mut right);
        left.finish() == right.finish()
    }
}

#[derive(Debug, serde::Serialize)]
pub struct AuthEventMetricsBucketValue {
    pub authentication_count: Option<u64>,
    pub authentication_attempt_count: Option<u64>,
    pub authentication_success_count: Option<u64>,
    pub challenge_flow_count: Option<u64>,
    pub challenge_attempt_count: Option<u64>,
    pub challenge_success_count: Option<u64>,
    pub frictionless_flow_count: Option<u64>,
    pub frictionless_success_count: Option<u64>,
    pub error_message_count: Option<u64>,
    pub authentication_funnel: Option<u64>,
    pub authentication_exemption_approved_count: Option<u64>,
    pub authentication_exemption_requested_count: Option<u64>,
}

#[derive(Debug, serde::Serialize)]
pub struct MetricsBucketResponse {
    #[serde(flatten)]
    pub values: AuthEventMetricsBucketValue,
    #[serde(flatten)]
    pub dimensions: AuthEventMetricsBucketIdentifier,
}


pub struct AddCardResponse {
    pub card_id: String,
    pub external_id: String,
    pub card_fingerprint: Secret<String>,
    pub card_global_fingerprint: Secret<String>,
    #[serde(rename = "merchant_id")]
    pub merchant_id: Option<id_type::MerchantId>,
    pub card_number: Option<cards::CardNumber>,
    pub card_exp_year: Option<Secret<String>>,
    pub card_exp_month: Option<Secret<String>>,
    pub name_on_card: Option<Secret<String>>,
    pub nickname: Option<String>,
    pub customer_id: Option<id_type::CustomerId>,
    pub duplicate: Option<bool>,
}


pub struct DataTransCreateAlias {
    pub create_alias: bool,
}


pub struct GooglePayPredecryptDataInternal {
    pub message_expiration: String,
    pub message_id: String,
    #[serde(rename = "paymentMethod")]
    pub payment_method_type: String,
    pub payment_method_details: GooglePayPaymentMethodDetails,
}


impl ApiEventMetric for BlocklistResponse {}

impl ApiEventMetric for for BlocklistResponse


pub struct FrmFulfillmentResponse {
    ///unique order_id for the transaction
    #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")]
    pub order_id: String,
    ///shipment_ids used in the fulfillment overall...also data from previous fulfillments for the same transactions/order is sent
    #[schema(example = r#"["ship_101", "ship_102"]"#)]
    pub shipment_ids: Vec<String>,
}


pub(crate) fn get_status(status: String) -> api_models::webhooks::IncomingWebhookEvent {
    match status.as_str() {
        //any response with status != 0 are a failed deposit transaction
        "0" => api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess,
        _ => api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure,
    }
}


impl Responder {
    let flow = Flow::ApiKeyRevoke;
    let (merchant_id, key_id) = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        (&merchant_id, &key_id),
        |state, _, (merchant_id, key_id), _| {
            api_keys::revoke_api_key(state, merchant_id.clone(), key_id)
        },
        auth::auth_type(
            &auth::PlatformOrgAdminAuthWithMerchantIdFromRoute {
                merchant_id_from_route: merchant_id.clone(),
                is_admin_auth_allowed: true,
            },
            &auth::JWTAuthMerchantFromRoute {
                merchant_id: merchant_id.clone(),
                required_permission: Permission::MerchantApiKeyWrite,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl PartialEq for UserRole {
    fn eq(&self, other: &Self) -> bool {
        match (
            self.get_entity_id_and_type(),
            other.get_entity_id_and_type(),
        ) {
            (
                Some((self_entity_id, self_entity_type)),
                Some((other_entity_id, other_entity_type)),
            ) => {
                self.user_id == other.user_id
                    && self_entity_id == other_entity_id
                    && self_entity_type == other_entity_type
            }
            _ => self.user_id == other.user_id,
        }
    }
}


pub fn collect(self) -> PaymentMetricsBucketValue {
        let (
            payment_processed_amount,
            payment_processed_count,
            payment_processed_amount_without_smart_retries,
            payment_processed_count_without_smart_retries,
            payment_processed_amount_in_usd,
            payment_processed_amount_without_smart_retries_usd,
        ) = self.processed_amount.collect();
        let (
            payments_success_rate_distribution,
            payments_success_rate_distribution_without_smart_retries,
            payments_success_rate_distribution_with_only_retries,
            payments_failure_rate_distribution,
            payments_failure_rate_distribution_without_smart_retries,
            payments_failure_rate_distribution_with_only_retries,
        ) = self.payments_distribution.collect();
        let (failure_reason_count, failure_reason_count_without_smart_retries) =
            self.failure_reasons_distribution.collect();
        let (debit_routed_transaction_count, debit_routing_savings, debit_routing_savings_in_usd) =
            self.debit_routing.collect();

        PaymentMetricsBucketValue {
            payment_success_rate: self.payment_success_rate.collect(),
            payment_count: self.payment_count.collect(),
            payment_success_count: self.payment_success.collect(),
            payment_processed_amount,
            payment_processed_count,
            payment_processed_amount_without_smart_retries,
            payment_processed_count_without_smart_retries,
            avg_ticket_size: self.avg_ticket_size.collect(),
            payment_error_message: self.payment_error_message.collect(),
            retries_count: self.retries_count.collect(),
            retries_amount_processed: self.retries_amount_processed.collect(),
            connector_success_rate: self.connector_success_rate.collect(),
            payments_success_rate_distribution,
            payments_success_rate_distribution_without_smart_retries,
            payments_success_rate_distribution_with_only_retries,
            payments_failure_rate_distribution,
            payments_failure_rate_distribution_without_smart_retries,
            payments_failure_rate_distribution_with_only_retries,
            failure_reason_count,
            failure_reason_count_without_smart_retries,
            payment_processed_amount_in_usd,
            payment_processed_amount_without_smart_retries_usd,
            debit_routed_transaction_count,
            debit_routing_savings,
            debit_routing_savings_in_usd,
        }
    }


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


pub trait BlackList {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync;
}


impl api::Payouts for Nomupay {}


pub fn new(
        sub_code: &'static str,
        error_identifier: u16,
        error_message: impl ToString,
        extra: Option<Extra>,
    ) -> Self {
        Self {
            sub_code,
            error_identifier,
            error_message: error_message.to_string(),
            extra,
            #[cfg(feature = "detailed_errors")]
            stacktrace: None,
        }
    }


File: crates/router/src/core/pm_auth.rs
 Public functions: 6

use std::{collections::HashMap, str::FromStr};

use api_models::{
    enums,
    payment_methods::{self, BankAccountAccessCreds},
};
use common_enums::{enums::MerchantStorageScheme, PaymentMethodType};
use hex;
pub mod helpers;
pub mod transformers;

use common_utils::{
    consts,
    crypto::{HmacSha256, SignMessage},
    ext_traits::{AsyncExt, ValueExt},
    generate_id,
    types::{self as util_types, AmountConvertor},
};
use error_stack::ResultExt;
use helpers::PaymentAuthConnectorDataExt;
use hyperswitch_domain_models::payments::PaymentIntent;
use masking::{ExposeInterface, PeekInterface, Secret};
use pm_auth::{
    connector::plaid::transformers::PlaidAuthType,
    types::{
        self as pm_auth_types,
        api::{
            auth_service::{BankAccountCredentials, ExchangeToken, LinkToken},
            BoxedConnectorIntegration, PaymentAuthConnectorData,
        },
    },
};

use crate::{
    core::{
        errors::{self, ApiErrorResponse, RouterResponse, RouterResult, StorageErrorExt},
        payment_methods::cards,
        payments::helpers as oss_helpers,
        pm_auth::helpers as pm_auth_helpers,
    },
    db::StorageInterface,
    logger,
    routes::SessionState,
    services::{pm_auth as pm_auth_services, ApplicationResponse},
    types::{self, domain, storage, transformers::ForeignTryFrom},
};

#[cfg(feature = "v1")]
pub async fn create_link_token(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    payload: api_models::pm_auth::LinkTokenCreateRequest,
    headers: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResponse<api_models::pm_auth::LinkTokenCreateResponse> {
    let db = &*state.store;

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

    let pm_auth_key = payload.payment_id.get_pm_auth_key();

    redis_conn
        .exists::<Vec<u8>>(&pm_auth_key.as_str().into())
        .await
        .change_context(ApiErrorResponse::InvalidRequestData {
            message: "Incorrect payment_id provided in request".to_string(),
        })
        .attach_printable("Corresponding pm_auth_key does not exist in redis")?
        .then_some(())
        .ok_or(ApiErrorResponse::InvalidRequestData {
            message: "Incorrect payment_id provided in request".to_string(),
        })
        .attach_printable("Corresponding pm_auth_key does not exist in redis")?;

    let pm_auth_configs = redis_conn
        .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
            &pm_auth_key.as_str().into(),
            "Vec<PaymentMethodAuthConnectorChoice>",
        )
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get payment method auth choices from redis")?;

    let selected_config = pm_auth_configs
        .into_iter()
        .find(|config| {
            config.payment_method == payload.payment_method
                && config.payment_method_type == payload.payment_method_type
        })
        .ok_or(ApiErrorResponse::GenericNotFoundError {
            message: "payment method auth connector name not found".to_string(),
        })?;

    let connector_name = selected_config.connector_name.as_str();

    let connector = PaymentAuthConnectorData::get_connector_by_name(connector_name)?;
    let connector_integration: BoxedConnectorIntegration<
        '_,
        LinkToken,
        pm_auth_types::LinkTokenRequest,
        pm_auth_types::LinkTokenResponse,
    > = connector.connector.get_connector_integration();

    let payment_intent = oss_helpers::verify_payment_intent_time_and_client_secret(
        &state,
        &merchant_context,
        payload.client_secret,
    )
    .await?;

    let billing_country = payment_intent
        .as_ref()
        .async_map(|pi| async {
            oss_helpers::get_address_by_id(
                &state,
                pi.billing_address_id.clone(),
                merchant_context.get_merchant_key_store(),
                &pi.payment_id,
                merchant_context.get_merchant_account().get_id(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
        })
        .await
        .transpose()?
        .flatten()
        .and_then(|address| address.country)
        .map(|country| country.to_string());

    #[cfg(feature = "v1")]
    let merchant_connector_account = state
        .store
        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            &(&state).into(),
            merchant_context.get_merchant_account().get_id(),
            &selected_config.mca_id,
            merchant_context.get_merchant_key_store(),
        )
        .await
        .change_context(ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: merchant_context
                .get_merchant_account()
                .get_id()
                .get_string_repr()
                .to_owned(),
        })?;

    #[cfg(feature = "v2")]
    let merchant_connector_account = {
        let _ = billing_country;
        todo!()
    };

    let auth_type = helpers::get_connector_auth_type(merchant_connector_account)?;

    let router_data = pm_auth_types::LinkTokenRouterData {
        flow: std::marker::PhantomData,
        merchant_id: Some(merchant_context.get_merchant_account().get_id().clone()),
        connector: Some(connector_name.to_string()),
        request: pm_auth_types::LinkTokenRequest {
            client_name: "HyperSwitch".to_string(),
            country_codes: Some(vec![billing_country.ok_or(
                ApiErrorResponse::MissingRequiredField {
                    field_name: "billing_country",
                },
            )?]),
            language: payload.language,
            user_info: payment_intent.and_then(|pi| pi.customer_id),
            client_platform: headers
                .as_ref()
                .and_then(|header| header.x_client_platform.clone()),
            android_package_name: headers.as_ref().and_then(|header| header.x_app_id.clone()),
            redirect_uri: headers
                .as_ref()
                .and_then(|header| header.x_redirect_uri.clone()),
        },
        response: Ok(pm_auth_types::LinkTokenResponse {
            link_token: "".to_string(),
        }),
        connector_http_status_code: None,
        connector_auth_type: auth_type,
    };

    let connector_resp = pm_auth_services::execute_connector_processing_step(
        &state,
        connector_integration,
        &router_data,
        &connector.connector_name,
    )
    .await
    .change_context(ApiErrorResponse::InternalServerError)
    .attach_printable("Failed while calling link token creation connector api")?;

    let link_token_resp =
        connector_resp
            .response
            .map_err(|err| ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: connector.connector_name.to_string(),
                status_code: err.status_code,
                reason: err.reason,
            })?;

    let response = api_models::pm_auth::LinkTokenCreateResponse {
        link_token: link_token_resp.link_token,
        connector: connector.connector_name.to_string(),
    };

    Ok(ApplicationResponse::Json(response))
}

#[cfg(feature = "v2")]
pub async fn create_link_token(
    _state: SessionState,
    _merchant_context: domain::MerchantContext,
    _payload: api_models::pm_auth::LinkTokenCreateRequest,
    _headers: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResponse<api_models::pm_auth::LinkTokenCreateResponse> {
    todo!()
}

impl ForeignTryFrom<&types::ConnectorAuthType> for PlaidAuthType {
    type Error = errors::ConnectorError;

    fn foreign_try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            types::ConnectorAuthType::BodyKey { api_key, key1 } => {
                Ok::<Self, errors::ConnectorError>(Self {
                    client_id: api_key.to_owned(),
                    secret: key1.to_owned(),
                })
            }
            _ => Err(errors::ConnectorError::FailedToObtainAuthType),
        }
    }
}

pub async fn exchange_token_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    payload: api_models::pm_auth::ExchangeTokenCreateRequest,
) -> RouterResponse<()> {
    let db = &*state.store;

    let config = get_selected_config_from_redis(db, &payload).await?;

    let connector_name = config.connector_name.as_str();

    let connector = PaymentAuthConnectorData::get_connector_by_name(connector_name)?;

    #[cfg(feature = "v1")]
    let merchant_connector_account = state
        .store
        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            &(&state).into(),
            merchant_context.get_merchant_account().get_id(),
            &config.mca_id,
            merchant_context.get_merchant_key_store(),
        )
        .await
        .change_context(ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: merchant_context
                .get_merchant_account()
                .get_id()
                .get_string_repr()
                .to_owned(),
        })?;

    #[cfg(feature = "v2")]
    let merchant_connector_account: domain::MerchantConnectorAccount = {
        let _ = merchant_context.get_merchant_account();
        let _ = connector;
        let _ = merchant_context.get_merchant_key_store();
        todo!()
    };

    let auth_type = helpers::get_connector_auth_type(merchant_connector_account.clone())?;

    let access_token = get_access_token_from_exchange_api(
        &connector,
        connector_name,
        &payload,
        &auth_type,
        &state,
    )
    .await?;

    let bank_account_details_resp = get_bank_account_creds(
        connector,
        &merchant_context,
        connector_name,
        &access_token,
        auth_type,
        &state,
        None,
    )
    .await?;

    Box::pin(store_bank_details_in_payment_methods(
        payload,
        merchant_context,
        state,
        bank_account_details_resp,
        (connector_name, access_token),
        merchant_connector_account.get_id(),
    ))
    .await?;

    Ok(ApplicationResponse::StatusOk)
}

#[cfg(feature = "v1")]
async fn store_bank_details_in_payment_methods(
    payload: api_models::pm_auth::ExchangeTokenCreateRequest,
    merchant_context: domain::MerchantContext,
    state: SessionState,
    bank_account_details_resp: pm_auth_types::BankAccountCredentialsResponse,
    connector_details: (&str, Secret<String>),
    mca_id: common_utils::id_type::MerchantConnectorAccountId,
) -> RouterResult<()> {
    let db = &*state.clone().store;
    let (connector_name, access_token) = connector_details;

    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &payload.payment_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(ApiErrorResponse::PaymentNotFound)?;

    let customer_id = payment_intent
        .customer_id
        .ok_or(ApiErrorResponse::CustomerNotFound)?;

    let payment_methods = db
        .find_payment_method_by_customer_id_merchant_id_list(
            &((&state).into()),
            merchant_context.get_merchant_key_store(),
            &customer_id,
            merchant_context.get_merchant_account().get_id(),
            None,
        )
        .await
        .change_context(ApiErrorResponse::InternalServerError)?;

    let mut hash_to_payment_method: HashMap<
        String,
        (
            domain::PaymentMethod,
            payment_methods::PaymentMethodDataBankCreds,
        ),
    > = HashMap::new();
    let key_manager_state = (&state).into();
    for pm in payment_methods {
        if pm.get_payment_method_type() == Some(enums::PaymentMethod::BankDebit)
            && pm.payment_method_data.is_some()
        {
            let bank_details_pm_data = pm
                .payment_method_data
                .clone()
                .map(|x| x.into_inner().expose())
                .map(|v| v.parse_value("PaymentMethodsData"))
                .transpose()
                .unwrap_or_else(|error| {
                    logger::error!(?error);
                    None
                })
                .and_then(|pmd| match pmd {
                    payment_methods::PaymentMethodsData::BankDetails(bank_creds) => {
                        Some(bank_creds)
                    }
                    _ => None,
                })
                .ok_or(ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to parse PaymentMethodsData")?;

            hash_to_payment_method.insert(
                bank_details_pm_data.hash.clone(),
                (pm, bank_details_pm_data),
            );
        }
    }

    let pm_auth_key = state
        .conf
        .payment_method_auth
        .get_inner()
        .pm_auth_key
        .clone()
        .expose();

    let mut update_entries: Vec<(domain::PaymentMethod, storage::PaymentMethodUpdate)> = Vec::new();
    let mut new_entries: Vec<domain::PaymentMethod> = Vec::new();

    for creds in bank_account_details_resp.credentials {
        let (account_number, hash_string) = match creds.account_details {
            pm_auth_types::PaymentMethodTypeDetails::Ach(ach) => (
                ach.account_number.clone(),
                format!(
                    "{}-{}-{}",
                    ach.account_number.peek(),
                    ach.routing_number.peek(),
                    PaymentMethodType::Ach,
                ),
            ),
            pm_auth_types::PaymentMethodTypeDetails::Bacs(bacs) => (
                bacs.account_number.clone(),
                format!(
                    "{}-{}-{}",
                    bacs.account_number.peek(),
                    bacs.sort_code.peek(),
                    PaymentMethodType::Bacs
                ),
            ),
            pm_auth_types::PaymentMethodTypeDetails::Sepa(sepa) => (
                sepa.iban.clone(),
                format!("{}-{}", sepa.iban.expose(), PaymentMethodType::Sepa),
            ),
        };

        let generated_hash = hex::encode(
            HmacSha256::sign_message(&HmacSha256, pm_auth_key.as_bytes(), hash_string.as_bytes())
                .change_context(ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to sign the message")?,
        );

        let contains_account = hash_to_payment_method.get(&generated_hash);
        let mut pmd = payment_methods::PaymentMethodDataBankCreds {
            mask: account_number
                .peek()
                .chars()
                .rev()
                .take(4)
                .collect::<String>()
                .chars()
                .rev()
                .collect::<String>(),
            hash: generated_hash,
            account_type: creds.account_type,
            account_name: creds.account_name,
            payment_method_type: creds.payment_method_type,
            connector_details: vec![payment_methods::BankAccountConnectorDetails {
                connector: connector_name.to_string(),
                mca_id: mca_id.clone(),
                access_token: BankAccountAccessCreds::AccessToken(access_token.clone()),
                account_id: creds.account_id,
            }],
        };

        if let Some((pm, details)) = contains_account {
            pmd.connector_details.extend(
                details
                    .connector_details
                    .clone()
                    .into_iter()
                    .filter(|conn| conn.mca_id != mca_id),
            );

            let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd);

            let encrypted_data = cards::create_encrypted_data(
                &key_manager_state,
                merchant_context.get_merchant_key_store(),
                payment_method_data,
            )
            .await
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt customer details")?;

            let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate {
                payment_method_data: Some(encrypted_data.into()),
            };

            update_entries.push((pm.clone(), pm_update));
        } else {
            let payment_method_data = payment_methods::PaymentMethodsData::BankDetails(pmd);
            let encrypted_data = cards::create_encrypted_data(
                &key_manager_state,
                merchant_context.get_merchant_key_store(),
                Some(payment_method_data),
            )
            .await
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt customer details")?;

            let pm_id = generate_id(consts::ID_LENGTH, "pm");

            let now = common_utils::date_time::now();

            let pm_new = domain::PaymentMethod {
                customer_id: customer_id.clone(),
                merchant_id: merchant_context.get_merchant_account().get_id().clone(),
                payment_method_id: pm_id,
                payment_method: Some(enums::PaymentMethod::BankDebit),
                payment_method_type: Some(creds.payment_method_type),
                status: enums::PaymentMethodStatus::Active,
                payment_method_issuer: None,
                scheme: None,
                metadata: None,
                payment_method_data: Some(encrypted_data),
                payment_method_issuer_code: None,
                accepted_currency: None,
                token: None,
                cardholder_name: None,
                issuer_name: None,
                issuer_country: None,
                payer_country: None,
                is_stored: None,
                swift_code: None,
                direct_debit_token: None,
                created_at: now,
                last_modified: now,
                locker_id: None,
                last_used_at: now,
                connector_mandate_details: None,
                customer_acceptance: None,
                network_transaction_id: None,
                client_secret: None,
                payment_method_billing_address: None,
                updated_by: None,
                version: common_types::consts::API_VERSION,
                network_token_requestor_reference_id: None,
                network_token_locker_id: None,
                network_token_payment_method_data: None,
            };

            new_entries.push(pm_new);
        };
    }

    store_in_db(
        &state,
        merchant_context.get_merchant_key_store(),
        update_entries,
        new_entries,
        db,
        merchant_context.get_merchant_account().storage_scheme,
    )
    .await?;

    Ok(())
}

#[cfg(feature = "v2")]
async fn store_bank_details_in_payment_methods(
    _payload: api_models::pm_auth::ExchangeTokenCreateRequest,
    _merchant_context: domain::MerchantContext,
    _state: SessionState,
    _bank_account_details_resp: pm_auth_types::BankAccountCredentialsResponse,
    _connector_details: (&str, Secret<String>),
    _mca_id: common_utils::id_type::MerchantConnectorAccountId,
) -> RouterResult<()> {
    todo!()
}

async fn store_in_db(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    update_entries: Vec<(domain::PaymentMethod, storage::PaymentMethodUpdate)>,
    new_entries: Vec<domain::PaymentMethod>,
    db: &dyn StorageInterface,
    storage_scheme: MerchantStorageScheme,
) -> RouterResult<()> {
    let key_manager_state = &(state.into());
    let update_entries_futures = update_entries
        .into_iter()
        .map(|(pm, pm_update)| {
            db.update_payment_method(key_manager_state, key_store, pm, pm_update, storage_scheme)
        })
        .collect::<Vec<_>>();

    let new_entries_futures = new_entries
        .into_iter()
        .map(|pm_new| {
            db.insert_payment_method(key_manager_state, key_store, pm_new, storage_scheme)
        })
        .collect::<Vec<_>>();

    let update_futures = futures::future::join_all(update_entries_futures);
    let new_futures = futures::future::join_all(new_entries_futures);

    let (update, new) = tokio::join!(update_futures, new_futures);

    let _ = update
        .into_iter()
        .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}")));

    let _ = new
        .into_iter()
        .map(|res| res.map_err(|err| logger::error!("Payment method storage failed {err:?}")));

    Ok(())
}

pub async fn get_bank_account_creds(
    connector: PaymentAuthConnectorData,
    merchant_context: &domain::MerchantContext,
    connector_name: &str,
    access_token: &Secret<String>,
    auth_type: pm_auth_types::ConnectorAuthType,
    state: &SessionState,
    bank_account_id: Option<Secret<String>>,
) -> RouterResult<pm_auth_types::BankAccountCredentialsResponse> {
    let connector_integration_bank_details: BoxedConnectorIntegration<
        '_,
        BankAccountCredentials,
        pm_auth_types::BankAccountCredentialsRequest,
        pm_auth_types::BankAccountCredentialsResponse,
    > = connector.connector.get_connector_integration();

    let router_data_bank_details = pm_auth_types::BankDetailsRouterData {
        flow: std::marker::PhantomData,
        merchant_id: Some(merchant_context.get_merchant_account().get_id().clone()),
        connector: Some(connector_name.to_string()),
        request: pm_auth_types::BankAccountCredentialsRequest {
            access_token: access_token.clone(),
            optional_ids: bank_account_id
                .map(|id| pm_auth_types::BankAccountOptionalIDs { ids: vec![id] }),
        },
        response: Ok(pm_auth_types::BankAccountCredentialsResponse {
            credentials: Vec::new(),
        }),
        connector_http_status_code: None,
        connector_auth_type: auth_type,
    };

    let bank_details_resp = pm_auth_services::execute_connector_processing_step(
        state,
        connector_integration_bank_details,
        &router_data_bank_details,
        &connector.connector_name,
    )
    .await
    .change_context(ApiErrorResponse::InternalServerError)
    .attach_printable("Failed while calling bank account details connector api")?;

    let bank_account_details_resp =
        bank_details_resp
            .response
            .map_err(|err| ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: connector.connector_name.to_string(),
                status_code: err.status_code,
                reason: err.reason,
            })?;

    Ok(bank_account_details_resp)
}

async fn get_access_token_from_exchange_api(
    connector: &PaymentAuthConnectorData,
    connector_name: &str,
    payload: &api_models::pm_auth::ExchangeTokenCreateRequest,
    auth_type: &pm_auth_types::ConnectorAuthType,
    state: &SessionState,
) -> RouterResult<Secret<String>> {
    let connector_integration: BoxedConnectorIntegration<
        '_,
        ExchangeToken,
        pm_auth_types::ExchangeTokenRequest,
        pm_auth_types::ExchangeTokenResponse,
    > = connector.connector.get_connector_integration();

    let router_data = pm_auth_types::ExchangeTokenRouterData {
        flow: std::marker::PhantomData,
        merchant_id: None,
        connector: Some(connector_name.to_string()),
        request: pm_auth_types::ExchangeTokenRequest {
            public_token: payload.public_token.clone(),
        },
        response: Ok(pm_auth_types::ExchangeTokenResponse {
            access_token: "".to_string(),
        }),
        connector_http_status_code: None,
        connector_auth_type: auth_type.clone(),
    };

    let resp = pm_auth_services::execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        &connector.connector_name,
    )
    .await
    .change_context(ApiErrorResponse::InternalServerError)
    .attach_printable("Failed while calling exchange token connector api")?;

    let exchange_token_resp =
        resp.response
            .map_err(|err| ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: connector.connector_name.to_string(),
                status_code: err.status_code,
                reason: err.reason,
            })?;

    let access_token = exchange_token_resp.access_token;
    Ok(Secret::new(access_token))
}

async fn get_selected_config_from_redis(
    db: &dyn StorageInterface,
    payload: &api_models::pm_auth::ExchangeTokenCreateRequest,
) -> RouterResult<api_models::pm_auth::PaymentMethodAuthConnectorChoice> {
    let redis_conn = db
        .get_redis_conn()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")?;

    let pm_auth_key = payload.payment_id.get_pm_auth_key();

    redis_conn
        .exists::<Vec<u8>>(&pm_auth_key.as_str().into())
        .await
        .change_context(ApiErrorResponse::InvalidRequestData {
            message: "Incorrect payment_id provided in request".to_string(),
        })
        .attach_printable("Corresponding pm_auth_key does not exist in redis")?
        .then_some(())
        .ok_or(ApiErrorResponse::InvalidRequestData {
            message: "Incorrect payment_id provided in request".to_string(),
        })
        .attach_printable("Corresponding pm_auth_key does not exist in redis")?;

    let pm_auth_configs = redis_conn
        .get_and_deserialize_key::<Vec<api_models::pm_auth::PaymentMethodAuthConnectorChoice>>(
            &pm_auth_key.as_str().into(),
            "Vec<PaymentMethodAuthConnectorChoice>",
        )
        .await
        .change_context(ApiErrorResponse::GenericNotFoundError {
            message: "payment method auth connector name not found".to_string(),
        })
        .attach_printable("Failed to get payment method auth choices from redis")?;

    let selected_config = pm_auth_configs
        .iter()
        .find(|conf| {
            conf.payment_method == payload.payment_method
                && conf.payment_method_type == payload.payment_method_type
        })
        .ok_or(ApiErrorResponse::GenericNotFoundError {
            message: "payment method auth connector name not found".to_string(),
        })?
        .clone();

    Ok(selected_config)
}

#[cfg(feature = "v2")]
pub async fn retrieve_payment_method_from_auth_service(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    auth_token: &payment_methods::BankAccountTokenData,
    payment_intent: &PaymentIntent,
    _customer: &Option<domain::Customer>,
) -> RouterResult<Option<(domain::PaymentMethodData, enums::PaymentMethod)>> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn retrieve_payment_method_from_auth_service(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    auth_token: &payment_methods::BankAccountTokenData,
    payment_intent: &PaymentIntent,
    _customer: &Option<domain::Customer>,
) -> RouterResult<Option<(domain::PaymentMethodData, enums::PaymentMethod)>> {
    let db = state.store.as_ref();

    let connector = PaymentAuthConnectorData::get_connector_by_name(
        auth_token.connector_details.connector.as_str(),
    )?;
    let key_manager_state = &state.into();
    let merchant_account = db
        .find_merchant_account_by_merchant_id(
            key_manager_state,
            &payment_intent.merchant_id,
            key_store,
        )
        .await
        .to_not_found_response(ApiErrorResponse::MerchantAccountNotFound)?;

    #[cfg(feature = "v1")]
    let mca = db
        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            key_manager_state,
            &payment_intent.merchant_id,
            &auth_token.connector_details.mca_id,
            key_store,
        )
        .await
        .to_not_found_response(ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: auth_token
                .connector_details
                .mca_id
                .get_string_repr()
                .to_string()
                .clone(),
        })
        .attach_printable(
            "error while fetching merchant_connector_account from merchant_id and connector name",
        )?;

    let auth_type = pm_auth_helpers::get_connector_auth_type(mca)?;

    let BankAccountAccessCreds::AccessToken(access_token) =
        &auth_token.connector_details.access_token;

    let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
        merchant_account.clone(),
        key_store.clone(),
    )));
    let bank_account_creds = get_bank_account_creds(
        connector,
        &merchant_context,
        &auth_token.connector_details.connector,
        access_token,
        auth_type,
        state,
        Some(auth_token.connector_details.account_id.clone()),
    )
    .await?;

    let bank_account = bank_account_creds
        .credentials
        .iter()
        .find(|acc| {
            acc.payment_method_type == auth_token.payment_method_type
                && acc.payment_method == auth_token.payment_method
        })
        .ok_or(ApiErrorResponse::InternalServerError)
        .attach_printable("Bank account details not found")?;

    if let (Some(balance), Some(currency)) = (bank_account.balance, payment_intent.currency) {
        let required_conversion = util_types::FloatMajorUnitForConnector;
        let converted_amount = required_conversion
            .convert_back(balance, currency)
            .change_context(ApiErrorResponse::InternalServerError)
            .attach_printable("Could not convert FloatMajorUnit to MinorUnit")?;

        if converted_amount < payment_intent.amount {
            return Err((ApiErrorResponse::PreconditionFailed {
                message: "selected bank account has insufficient balance".to_string(),
            })
            .into());
        }
    }

    let mut bank_type = None;
    if let Some(account_type) = bank_account.account_type.clone() {
        bank_type = common_enums::BankType::from_str(account_type.as_str())
            .map_err(|error| logger::error!(%error,"unable to parse account_type {account_type:?}"))
            .ok();
    }

    let payment_method_data = match &bank_account.account_details {
        pm_auth_types::PaymentMethodTypeDetails::Ach(ach) => {
            domain::PaymentMethodData::BankDebit(domain::BankDebitData::AchBankDebit {
                account_number: ach.account_number.clone(),
                routing_number: ach.routing_number.clone(),
                bank_name: None,
                bank_type,
                bank_holder_type: None,
                card_holder_name: None,
                bank_account_holder_name: None,
            })
        }
        pm_auth_types::PaymentMethodTypeDetails::Bacs(bacs) => {
            domain::PaymentMethodData::BankDebit(domain::BankDebitData::BacsBankDebit {
                account_number: bacs.account_number.clone(),
                sort_code: bacs.sort_code.clone(),
                bank_account_holder_name: None,
            })
        }
        pm_auth_types::PaymentMethodTypeDetails::Sepa(sepa) => {
            domain::PaymentMethodData::BankDebit(domain::BankDebitData::SepaBankDebit {
                iban: sepa.iban.clone(),
                bank_account_holder_name: None,
            })
        }
    };

    Ok(Some((payment_method_data, enums::PaymentMethod::BankDebit)))
}


pub fn get_payment_method_sub_type(&self) -> Option<common_enums::PaymentMethodType> {
        self.payment_revenue_recovery_metadata
            .as_ref()
            .map(|rrm| rrm.payment_method_subtype)
    }


pub struct ConnectorWebhookSecrets {
    pub secret: Vec<u8>,
    pub additional_secret: Option<masking::Secret<String>>,
}


pub struct WellsfargoConsumerAuthInformation {
    ucaf_collection_indicator: Option<String>,
    cavv: Option<Secret<String>>,
    ucaf_authentication_data: Option<Secret<String>>,
    xid: Option<String>,
    directory_server_transaction_id: Option<Secret<String>>,
    specification_version: Option<String>,
    /// This field specifies the 3ds version
    pa_specification_version: Option<SemanticVersion>,
    /// Verification response enrollment status.
    ///
    /// This field is supported only on Asia, Middle East, and Africa Gateway.
    ///
    /// For external authentication, this field will always be "Y"
    veres_enrolled: Option<String>,
}


pub struct NoonRefundSyncResponseResult {
    transactions: Vec<NoonRefundResponseTransactions>,
}


impl Deref for CycleCheck {
    type Target = FxHashMap<NodeId, (Strength, RelationResolution)>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}


pub struct MessageExtensionAttribute {
    pub id: String,
    pub name: String,
    pub criticality_indicator: bool,
    pub data: serde_json::Value,
}


pub struct Reason {
    reason: Option<String>,
    code: Option<u64>,
}


pub struct ChargeResponseData {
    charge: SingleChargeResponseData,
}


impl webhooks::IncomingWebhook for Nordea {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

impl webhooks::IncomingWebhook for for Nordea


pub struct CheckoutResponse {
    redirect_url: url::Url,
}


impl DummyConnectorPaymentData {
    pub fn is_eligible_for_refund(&self, refund_amount: i64) -> DummyConnectorResult<()> {
        if self.eligible_amount < refund_amount {
            return Err(
                report!(DummyConnectorErrors::RefundAmountExceedsPaymentAmount)
                    .attach_printable("Eligible amount is lesser than refund amount"),
            );
        }
        if self.status != DummyConnectorStatus::Succeeded {
            return Err(report!(DummyConnectorErrors::PaymentNotSuccessful)
                .attach_printable("Payment is not successful to process the refund"));
        }
        Ok(())
    }
}


impl api::ConnectorAccessToken for Tokenio {}

impl api::ConnectorAccessToken for for Tokenio


Web Documentation: Previous Roadmap - Q4 2024 | Hyperswitch
# Type: Web Doc

🗺 Our Roadmap typically pans out over a 3-month period and we establish topics we work on upfront.
👂And as always, we listen to your feedback and adapt our plans if needed.
Recap of Q3 2024
Hyperswitch is now PCI Software Security Standard (S3) certified
Network Tokenization capability with Visa, Master and Amex card networks. This shall enable merchant to use network tokens to improve auth rates for one-time/ recurring payments and reduce the interchange fee
Payment Method Management experience to view, add and delete payment methods (for Web platform)
New connector and payment method integrations
Datatrans (
Planet.com
) for card payments
Wells Fargo (US) for card payments
Deutsche Bank (DE) for SEPA direct debits
Novalnet for card payments
Fiuu for cards, bank transfer and inter-operable QR based payments
Itau Bank for instant payments
Payouts via PayOne, and Wells Fargo
Razorpay UPI payments
Pay by Bank Experience through
Plaid Open banking
. This is to allow merchants to enable instant bank transfer (push payments) in the UK and EU via with support for app2app redirection experience
Account verification via Plaid for pull payments (ACH, SEPA) in the EU and US
React Native SDK was Open Sourced
Native 3DS Authentication Experience via Netcetera for mobile
Merchant Initiated Transactions (MIT) were made PSP agnostic with Network Transaction ID (NTI)
User management and dashboard analytics views at entity level granularity (org to profile)
Payment plugin for Saleor - headless commerce platform to facilitate faster integrations
Localisation support for Payouts across 17 languages
Control Centre - Enable SSO sign in with Okta
Core Values
Our core values have pretty much remained the same since the early days and here they are:
Make payments more
accessible
and
affordable
to every digital business
Staying
simple
and
super-lightweight
, and at the same time
reliable
and
scalable
payment switch
Being
community-first
in ideation, planning and execution of features
Themes for Roadmap
There are a lot of problems to be solved in payments, but our majority of our current focus falls under 5 themes below.
🌎
Community Feature Requests:
Most of our community feature requests falls under one of the above themes, but we still keep this as a separate theme, because we intend to actively explore new problem statements and themes from the community before scheduling actual feature work.
👨💻
Developer Experience:
Providing a great self-service and self-installation experience for developers who wish to use or contribute back to Hyperswitch.
💰
Reducing Payment Costs:
Payments should be like a free utility for digital businesses. Any business should be able reduce payment processing costs by embracing the diversity in payments.
📈
Improving Authorization Rates:
Ensuring a best-in-class payment experience and access to latest innovations in the payments ecosystem for all businesses.
👍
Reducing Payment Operations:
Managing payments across multiple countries, currencies and processors should not add to the administrative burden on businesses. Hence, Hyperswitch intends to eliminate all such operational burdens so that businesses can focus on the core activities.
Legend
Description
🟩
Feature completed
🟧
Feature in progress
🟥
Work not started
💪
Stretch target
🚛
Backlog feature
Roadmap
Community Feature Requests
🟩 More payment authorization workflows - split payments and incremental authorization
New integrations -
🟩 SamsungPay
🟩 Nexi Xpay card payments
🟩 PAZE for card payments in the US
🟩 Dynamic Tax updater for express checkout wallets (Paypal, Applepay, Googlepay and Klarna) using Taxjar integration
Improving Authorization Rates
🟩 Extending smart retries to 7 more PSPs: Adyen, Worldpay, Braintree, Deutsche Bank, Novalnet, Fiuu and Nexi Xpay
🟩 Implement MPAN (merchant tokens) for Applepay recurring payments
🟩 Enabling guest checkout flow with
Click to Pay
Reducing Payments Cost
More direct bank acquirer integrations
🟩 JP Morgan
Reducing Payment Operations
🟩 Data reporting at an organization, merchant and profile level for easier reconciliation
🟩 Enhancements in analytics module for Refunds, Disputes and Smart Retries
🟩 Migration of Network Tokens for business continuity
Developer Experience
🟩 Hyperswitch widgets to support Alternate payment methods, express checkout payment methods and Authentication solutions
Want to contribute to the roadmap?
Submit an idea or feature request here
with a simple explanation on
What?
and
Why?
included.
9 months ago
Was this helpful?


pub struct NuveiAuthType {
    pub(super) merchant_id: Secret<String>,
    pub(super) merchant_site_id: Secret<String>,
    pub(super) merchant_secret: Secret<String>,
}


pub struct ThreeDSRequestor {
    #[serde(rename = "threeDSRequestorAuthenticationInd")]
    pub three_ds_requestor_authentication_ind: ThreeDSRequestorAuthenticationIndicator,
    /// Format of this field was changed with EMV 3DS 2.3.1 version:
    /// In versions prior to 2.3.1, this field is a single object.
    /// Starting from EMVCo version 2.3.1, this field is now an array of objects. Accepted value length is 1-3 elements.
    ///
    /// This field is optional, but recommended to include.
    #[serde(rename = "threeDSRequestorAuthenticationInfo")]
    pub three_ds_requestor_authentication_info:
        Option<SingleOrListElement<ThreeDSRequestorAuthenticationInformation>>,
    #[serde(rename = "threeDSRequestorChallengeInd")]
    pub three_ds_requestor_challenge_ind:
        Option<SingleOrListElement<ThreeDSRequestorChallengeIndicator>>,
    #[serde(rename = "threeDSRequestorPriorAuthenticationInfo")]
    pub three_ds_requestor_prior_authentication_info:
        Option<SingleOrListElement<ThreeDSRequestorPriorTransactionAuthenticationInformation>>,
    #[serde(rename = "threeDSRequestorDecReqInd")]
    pub three_ds_requestor_dec_req_ind: Option<ThreeDSRequestorDecoupledRequestIndicator>,
    /// Indicates the maximum amount of time that the 3DS Requestor will wait for an ACS to provide the results
    /// of a Decoupled Authentication transaction (in minutes). Valid values are between 1 and 10080.
    ///
    /// The field is optional and if value is not present, the expected action is for the ACS to interpret it as
    /// 10080 minutes (7 days).
    /// Available for supporting EMV 3DS 2.2.0 and later versions.
    ///
    /// Starting from EMV 3DS 2.3.1:
    /// This field is required if threeDSRequestorDecReqInd = Y, F or B.
    #[serde(rename = "threeDSRequestorDecMaxTime")]
    pub three_ds_requestor_dec_max_time: Option<u32>,
    /// External IP address (i.e., the device public IP address) used by the 3DS Requestor App when it connects to the
    /// 3DS Requestor environment. The value length is maximum 45 characters. Accepted values are:
    ///
    /// - IPv4 address is represented in the dotted decimal f. Refer to RFC 791.
    /// - IPv6 address. Refer to RFC 4291.
    ///
    /// This field is required when deviceChannel = 01 (APP) and unless market or regional mandate restricts sending
    /// this information.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    pub app_ip: Option<std::net::IpAddr>,
    /// Indicate if the 3DS Requestor supports the SPC authentication.
    ///
    /// The accepted values are:
    ///
    /// - Y -> Supported
    ///
    /// This field is required if deviceChannel = 02 (BRW) and it is supported by the 3DS Requestor.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    #[serde(rename = "threeDSRequestorSpcSupport")]
    pub three_ds_requestor_spc_support: Option<String>,
    /// Reason that the SPC authentication was not completed.
    /// Accepted value length is 2 characters.
    ///
    /// The accepted values are:
    ///
    /// - 01 -> SPC did not run or did not successfully complete
    /// - 02 -> Cardholder cancels the SPC authentication
    ///
    /// This field is required if deviceChannel = 02 (BRW) and the 3DS Requestor attempts to invoke SPC API and there is an
    /// error.
    /// Available for supporting EMV 3DS 2.3.1 and later versions.
    pub spc_incomp_ind: Option<String>,
}


impl api::ConnectorAccessToken for Itaubank {}

impl api::ConnectorAccessToken for for Itaubank


pub struct WisePayoutsWebhookBody {
    pub data: WisePayoutsWebhookData,
}


impl ConnectorSpecifications for Signifyd {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&SYGNIFYD_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::enums::EventClass]> {
        None
    }
}

impl ConnectorSpecifications for for Signifyd


pub async fn update_by_merchant_id_customer_id(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::CustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        address: AddressUpdateInternal,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::customer_id.eq(customer_id.to_owned())),
            address,
        )
        .await
    }


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


pub struct StripebillingCard {
    number: cards::CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    cvc: Secret<String>,
    complete: bool,
}


pub struct CardVerification {
    result_code: Option<String>,
    result_code_raw: Option<String>,
}


impl Serializer for PIISerializer {
        type Ok = Value;
        type Error = serde_json::Error;

        type SerializeSeq = SerializeVec<Self>;
        type SerializeTuple = SerializeVec<Self>;
        type SerializeTupleStruct = SerializeVec<Self>;
        type SerializeTupleVariant = SerializeTupleVariant<Self>;
        type SerializeMap = SerializeMap<Self>;
        type SerializeStruct = SerializeMap<Self>;
        type SerializeStructVariant = SerializeStructVariant<Self>;

        #[inline]
        fn serialize_bool(self, value: bool) -> Result<Self::Ok, Self::Error> {
            self.inner.serialize_bool(value)
        }

        #[inline]
        fn serialize_i8(self, value: i8) -> Result<Self::Ok, Self::Error> {
            self.serialize_i64(value.into())
        }

        #[inline]
        fn serialize_i16(self, value: i16) -> Result<Self::Ok, Self::Error> {
            self.serialize_i64(value.into())
        }

        #[inline]
        fn serialize_i32(self, value: i32) -> Result<Self::Ok, Self::Error> {
            self.serialize_i64(value.into())
        }

        fn serialize_i64(self, value: i64) -> Result<Self::Ok, Self::Error> {
            self.inner.serialize_i64(value)
        }

        fn serialize_i128(self, value: i128) -> Result<Self::Ok, Self::Error> {
            self.inner.serialize_i128(value)
        }

        #[inline]
        fn serialize_u8(self, value: u8) -> Result<Self::Ok, Self::Error> {
            self.serialize_u64(value.into())
        }

        #[inline]
        fn serialize_u16(self, value: u16) -> Result<Self::Ok, Self::Error> {
            self.serialize_u64(value.into())
        }

        #[inline]
        fn serialize_u32(self, value: u32) -> Result<Self::Ok, Self::Error> {
            self.serialize_u64(value.into())
        }

        #[inline]
        fn serialize_u64(self, value: u64) -> Result<Self::Ok, Self::Error> {
            Ok(Value::Number(value.into()))
        }

        fn serialize_u128(self, value: u128) -> Result<Self::Ok, Self::Error> {
            self.inner.serialize_u128(value)
        }

        #[inline]
        fn serialize_f32(self, float: f32) -> Result<Self::Ok, Self::Error> {
            Ok(Value::from(float))
        }

        #[inline]
        fn serialize_f64(self, float: f64) -> Result<Self::Ok, Self::Error> {
            Ok(Value::from(float))
        }

        #[inline]
        fn serialize_char(self, value: char) -> Result<Self::Ok, Self::Error> {
            let mut s = String::new();
            s.push(value);
            Ok(Value::String(s))
        }

        #[inline]
        fn serialize_str(self, value: &str) -> Result<Self::Ok, Self::Error> {
            Ok(Value::String(value.to_owned()))
        }

        fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok, Self::Error> {
            let vec = value.iter().map(|&b| Value::Number(b.into())).collect();
            Ok(Value::Array(vec))
        }

        #[inline]
        fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
            Ok(Value::Null)
        }

        #[inline]
        fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
            self.serialize_unit()
        }

        #[inline]
        fn serialize_unit_variant(
            self,
            _name: &'static str,
            _variant_index: u32,
            variant: &'static str,
        ) -> Result<Self::Ok, Self::Error> {
            self.serialize_str(variant)
        }

        #[inline]
        fn serialize_newtype_struct<T>(
            self,
            _name: &'static str,
            value: &T,
        ) -> Result<Self::Ok, Self::Error>
        where
            T: ?Sized + Serialize,
        {
            value.serialize(self)
        }

        fn serialize_newtype_variant<T>(
            self,
            _name: &'static str,
            _variant_index: u32,
            variant: &'static str,
            value: &T,
        ) -> Result<Self::Ok, Self::Error>
        where
            T: ?Sized + Serialize,
        {
            let mut values = Map::new();
            values.insert(String::from(variant), value.serialize(self)?);
            Ok(Value::Object(values))
        }

        #[inline]
        fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
            self.serialize_unit()
        }

        #[inline]
        fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
        where
            T: ?Sized + Serialize,
        {
            value.serialize(self)
        }

        fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
            Ok(SerializeVec {
                vec: Vec::with_capacity(len.unwrap_or(0)),
                ser: self,
            })
        }

        fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
            self.serialize_seq(Some(len))
        }

        fn serialize_tuple_struct(
            self,
            _name: &'static str,
            len: usize,
        ) -> Result<Self::SerializeTupleStruct, Self::Error> {
            self.serialize_seq(Some(len))
        }

        fn serialize_tuple_variant(
            self,
            _name: &'static str,
            _variant_index: u32,
            variant: &'static str,
            len: usize,
        ) -> Result<Self::SerializeTupleVariant, Self::Error> {
            Ok(SerializeTupleVariant {
                name: String::from(variant),
                vec: Vec::with_capacity(len),
                ser: self,
            })
        }

        fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
            Ok(SerializeMap {
                inner: self.clone().inner.serialize_map(len)?,
                ser: self,
            })
        }

        fn serialize_struct(
            self,
            _name: &'static str,
            len: usize,
        ) -> Result<Self::SerializeStruct, Self::Error> {
            self.serialize_map(Some(len))
        }

        fn serialize_struct_variant(
            self,
            _name: &'static str,
            _variant_index: u32,
            variant: &'static str,
            _len: usize,
        ) -> Result<Self::SerializeStructVariant, Self::Error> {
            Ok(SerializeStructVariant {
                name: String::from(variant),
                map: Map::new(),
                ser: self,
            })
        }

        fn collect_str<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
        where
            T: ?Sized + Display,
        {
            self.inner.collect_str(value)
        }
    }


OpenAPI Block Path: paths."/mandates/{mandate_id}"
{
  "get": {
    "tags": [
      "Mandates"
    ],
    "summary": "Mandates - Retrieve Mandate",
    "description": "Retrieves a mandate created using the Payments/Create API",
    "operationId": "Retrieve a Mandate",
    "parameters": [
      {
        "name": "mandate_id",
        "in": "path",
        "description": "The identifier for mandate",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    ],
    "responses": {
      "200": {
        "description": "The mandate was retrieved successfully",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/MandateResponse"
            }
          }
        }
      },
      "404": {
        "description": "Mandate does not exist in our records"
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  }
}


pub struct PaymentAddress {
    pub address: Address,
    pub payment_id: id_type::PaymentId,
    // This is present in `PaymentAddress` because even `payouts` uses `PaymentAddress`
    pub customer_id: Option<id_type::CustomerId>,
}


impl api::PaymentSession for Paysafe {}

impl api::PaymentSession for for Paysafe


impl api::PaymentToken for Paypal {}


pub struct WiseFulfillResponse {
    status: WiseStatus,
    error_code: Option<String>,
    error_message: Option<String>,
    balance_transaction_id: Option<i64>,
}


pub struct SantanderPixQRCodeErrorResponse {
    #[serde(rename = "type")]
    pub field_type: Secret<String>,
    pub title: String,
    pub status: i64,
    pub detail: Option<String>,
    pub correlation_id: Option<String>,
    #[serde(rename = "violacoes")]
    pub violations: Option<Vec<SantanderViolations>>,
}


impl SecretsHandler for PaymentMethodAuth {
    async fn convert_to_raw_secret(
        value: SecretStateContainer<Self, SecuredSecret>,
        secret_management_client: &dyn SecretManagementInterface,
    ) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
        let payment_method_auth = value.get_inner();

        let pm_auth_key = secret_management_client
            .get_secret(payment_method_auth.pm_auth_key.clone())
            .await?;

        Ok(value.transition_state(|payment_method_auth| Self {
            pm_auth_key,
            ..payment_method_auth
        }))
    }
}

impl SecretsHandler for for PaymentMethodAuth


impl GetLockingInput for FPaymentsRejectRequest<'_> {
    fn get_locking_input<F>(&self, flow: F) -> api_locking::LockAction
    where
        F: types::FlowMetric,
        lock_utils::ApiIdentifier: From<F>,
    {
        api_locking::LockAction::Hold {
            input: api_locking::LockingInput {
                unique_locking_key: self.0.payment_id.get_string_repr().to_owned(),
                api_identifier: lock_utils::ApiIdentifier::from(flow),
                override_lock_retries: None,
            },
        }
    }
}

impl GetLockingInput for for FPaymentsRejectRequest


File: crates/router/src/types/storage.rs

pub mod address;
pub mod api_keys;
pub mod authentication;
pub mod authorization;
pub mod blocklist;
pub mod blocklist_fingerprint;
pub mod blocklist_lookup;
pub mod business_profile;
pub mod callback_mapper;
pub mod capture;
pub mod cards_info;
pub mod configs;
pub mod customers;
pub mod dashboard_metadata;
pub mod dispute;
pub mod dynamic_routing_stats;
pub mod enums;
pub mod ephemeral_key;
pub mod events;
pub mod file;
pub mod fraud_check;
pub mod generic_link;
pub mod gsm;
#[cfg(feature = "kv_store")]
pub mod kv;
pub mod locker_mock_up;
pub mod mandate;
pub mod merchant_account;
pub mod merchant_connector_account;
pub mod merchant_key_store;
pub mod payment_attempt;
pub mod payment_link;
pub mod payment_method;
pub mod payout_attempt;
pub mod payouts;
pub mod refund;
#[cfg(feature = "v2")]
pub mod revenue_recovery;
#[cfg(feature = "v2")]
pub mod revenue_recovery_redis_operation;
pub mod reverse_lookup;
pub mod role;
pub mod routing_algorithm;
pub mod subscription;
pub mod unified_translations;
pub mod user;
pub mod user_authentication_method;
pub mod user_role;

pub use diesel_models::{
    process_tracker::business_status, ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner,
    ProcessTrackerUpdate,
};
#[cfg(feature = "v1")]
pub use hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptNew;
#[cfg(feature = "payouts")]
pub use hyperswitch_domain_models::payouts::{
    payout_attempt::{PayoutAttempt, PayoutAttemptNew, PayoutAttemptUpdate},
    payouts::{Payouts, PayoutsNew, PayoutsUpdate},
};
pub use hyperswitch_domain_models::{
    payments::{
        payment_attempt::{PaymentAttempt, PaymentAttemptUpdate},
        payment_intent::{PaymentIntentUpdate, PaymentIntentUpdateFields},
        PaymentIntent,
    },
    routing::{
        PaymentRoutingInfo, PaymentRoutingInfoInner, PreRoutingConnectorChoice, RoutingData,
    },
};
pub use scheduler::db::process_tracker;

pub use self::{
    address::*, api_keys::*, authentication::*, authorization::*, blocklist::*,
    blocklist_fingerprint::*, blocklist_lookup::*, business_profile::*, callback_mapper::*,
    capture::*, cards_info::*, configs::*, customers::*, dashboard_metadata::*, dispute::*,
    dynamic_routing_stats::*, ephemeral_key::*, events::*, file::*, fraud_check::*,
    generic_link::*, gsm::*, locker_mock_up::*, mandate::*, merchant_account::*,
    merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*,
    process_tracker::*, refund::*, reverse_lookup::*, role::*, routing_algorithm::*,
    subscription::*, unified_translations::*, user::*, user_authentication_method::*, user_role::*,
};


impl api::RefundExecute for Volt {}


pub struct MetricsBucketResponse {
    #[serde(flatten)]
    pub values: PaymentIntentMetricsBucketValue,
    #[serde(flatten)]
    pub dimensions: PaymentIntentMetricsBucketIdentifier,
}


pub struct ElavonAuthType {
    pub(super) account_id: Secret<String>,
    pub(super) user_id: Secret<String>,
    pub(super) pin: Secret<String>,
}


pub fn get_primary_details_as_value(
        &self,
    ) -> CustomResult<serde_json::Value, errors::ParsingError> {
        self.primary_business_details
            .clone()
            .unwrap_or_default()
            .encode_to_value()
    }


pub fn add_ca_certificate_pem(mut self, ca_certificate: Option<Secret<String>>) -> Self {
        self.ca_certificate = ca_certificate;
        self
    }


impl PaymentCapture for Payone {}


pub fn diesel_enum(
    args: proc_macro::TokenStream,
    item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let args_parsed = parse_macro_input!(args as DieselEnumMeta);
    let item = syn::parse_macro_input!(item as syn::ItemEnum);

    macros::diesel::diesel_enum_attribute_macro(args_parsed, &item)
        .unwrap_or_else(|error| error.to_compile_error())
        .into()
}