Documentation: docs/architecture.md
# Type: Doc File

# HyperSwitch Architecture

- [Introduction](#introduction)
- [Router](#router)
- [Scheduler](#scheduler)
  - [Producer (Job scheduler)](#producer-job-scheduler)
  - [Consumer (Job executor)](#consumer-job-executor)
- [Database](#database)
  - [Postgres](#postgres)
  - [Redis](#redis)
- [Locker](#locker)
- [Monitoring](#monitoring)

## Introduction

Hyperswitch comprises two distinct app services: **Router** and **Scheduler** which in turn consists of **Producer** and **Consumer**, where each service has its specific responsibilities to process payment-related tasks efficiently.

Fig.1 - Typical Deployment

## Router

The Router is the main component of Hyperswitch, serving as the primary crate where all the core payment functionalities are implemented. It is a crucial component responsible for managing and coordinating different aspects of the payment processing system. Within the Router, the core payment flows serve as the central hub through which all payment activities are directed. When a payment request is received, it goes through the Router, which handles important processing and routing tasks.

## Scheduler

Suppose a scenario where a customer has saved their card details in your application, but for security reasons, you want to remove the saved card information after a certain period.
To automate this process, Scheduler comes into picture. It schedules a task with a specific time for execution and stores it in the database. When the scheduled time arrives, the job associated with the task starts executing, here in this case, allowing the saved card details to be deleted automatically. One other situation in which we use this service in Hyperswitch is when we want to notify the merchant that their api key is about to expire.

### Producer (Job scheduler)

The Producer is one of the components responsible for the Scheduler's functionality. Its primary responsibility is to handle the tracking of tasks which are yet to be executed. When the Router Service inserts a new task into the database, specifying a scheduled time, the producer retrieves the task from the database when the scheduled time is up and proceeds to group or batch these tasks together. These batches of tasks are then stored in a Redis queue, ready for execution, which will be picked up by consumer service.

### Consumer (Job executor)

The Consumer is another key component of the Scheduler. Its main role is to retrieve batches of tasks from the Redis queue for processing, which were previously added by the Producer. Once the tasks are retrieved, the Consumer executes them. It ensures that the tasks within the batches are handled promptly and in accordance with the required processing logic.

## Database

### Postgres

The application relies on a PostgreSQL database for storing various types of data, including customer information, merchant details, payment-related data, and other relevant information. The application maintains a master-database and replica-database setup to optimize read and write operations.

### Redis

In addition to the database, Hyperswitch incorporates Redis for two main purposes. It is used to **cache** frequently accessed data in order to decrease the application latencies and reduce the load on the database. It is also used as a **queuing mechanism** by the Scheduler.

## Locker

The application utilizes a Rust locker built with a GDPR compliant PII (personal identifiable information) storage. It also uses secure encryption algorithms to be fully compliant with **PCI DSS** (Payment Card Industry Data Security Standard) requirements, this ensures that all payment-related data is handled and stored securely. You can find the source code of locker [here](https://github.com/juspay/hyperswitch-card-vault).

## Monitoring

Fig.2 - HyperSwitch Monitoring Architecture

The monitoring services in Hyperswitch ensure the effective collection and analysis of metrics to monitor the system's performance.

Hyperswitch pushes the metrics and traces in **OTLP** format to the [OpenTelemetry collector]. [Prometheus] utilizes a pull-based model, where it periodically retrieves application metrics from the OpenTelemetry collector. [Promtail] scrapes application logs from the router, which in turn are pushed to the [Loki] instance. Users can query and visualize the logs in Grafana through Loki. [Tempo] is used for querying the application traces.

Except for the OpenTelemetry collector, all other monitoring services like Loki, Tempo, Prometheus can be easily replaced with a preferred equivalent, with minimal to no code changes.

[OpenTelemetry collector]: https://opentelemetry.io/docs/collector/
[Prometheus]: https://prometheus.io/docs/introduction/overview/
[Promtail]: https://grafana.com/docs/loki/latest/clients/promtail/
[Loki]: https://grafana.com/docs/loki/latest/
[Tempo]: https://grafana.com/docs/tempo/latest/


impl Event {
    pub async fn find_by_merchant_id_event_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::event_id.eq(event_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_idempotent_event_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        idempotent_event_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::idempotent_event_id.eq(idempotent_event_id.to_owned())),
        )
        .await
    }

    pub async fn list_initial_attempts_by_merchant_id_primary_object_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        primary_object_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::event_id
                .nullable()
                .eq(dsl::initial_attempt_id) // Filter initial attempts only
                .and(dsl::merchant_id.eq(merchant_id.to_owned()))
                .and(dsl::primary_object_id.eq(primary_object_id.to_owned())),
            None,
            None,
            Some(dsl::created_at.desc()),
        )
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn list_initial_attempts_by_merchant_id_constraints(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        created_after: time::PrimitiveDateTime,
        created_before: time::PrimitiveDateTime,
        limit: Option<i64>,
        offset: Option<i64>,
        event_types: HashSet<common_enums::EventType>,
        is_delivered: Option<bool>,
    ) -> StorageResult<Vec<Self>> {
        use async_bb8_diesel::AsyncRunQueryDsl;
        use diesel::{debug_query, pg::Pg, QueryDsl};
        use error_stack::ResultExt;
        use router_env::logger;

        use super::generics::db_metrics::{track_database_call, DatabaseOperation};
        use crate::errors::DatabaseError;

        let mut query = Self::table()
            .filter(
                dsl::event_id
                    .nullable()
                    .eq(dsl::initial_attempt_id) // Filter initial attempts only
                    .and(dsl::merchant_id.eq(merchant_id.to_owned())),
            )
            .order(dsl::created_at.desc())
            .into_boxed();

        query = Self::apply_filters(
            query,
            None,
            (dsl::created_at, created_after, created_before),
            limit,
            offset,
            event_types,
            is_delivered,
        );

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

        track_database_call::<Self, _, _>(query.get_results_async(conn), DatabaseOperation::Filter)
            .await
            .change_context(DatabaseError::Others) // Query returns empty Vec when no records are found
            .attach_printable("Error filtering events by constraints")
    }

    pub async fn list_by_merchant_id_initial_attempt_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        initial_attempt_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::initial_attempt_id.eq(initial_attempt_id.to_owned())),
            None,
            None,
            Some(dsl::created_at.desc()),
        )
        .await
    }

    pub async fn list_initial_attempts_by_profile_id_primary_object_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        primary_object_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::event_id
                .nullable()
                .eq(dsl::initial_attempt_id) // Filter initial attempts only
                .and(dsl::business_profile_id.eq(profile_id.to_owned()))
                .and(dsl::primary_object_id.eq(primary_object_id.to_owned())),
            None,
            None,
            Some(dsl::created_at.desc()),
        )
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn list_initial_attempts_by_profile_id_constraints(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        created_after: time::PrimitiveDateTime,
        created_before: time::PrimitiveDateTime,
        limit: Option<i64>,
        offset: Option<i64>,
        event_types: HashSet<common_enums::EventType>,
        is_delivered: Option<bool>,
    ) -> StorageResult<Vec<Self>> {
        use async_bb8_diesel::AsyncRunQueryDsl;
        use diesel::{debug_query, pg::Pg, QueryDsl};
        use error_stack::ResultExt;
        use router_env::logger;

        use super::generics::db_metrics::{track_database_call, DatabaseOperation};
        use crate::errors::DatabaseError;

        let mut query = Self::table()
            .filter(
                dsl::event_id
                    .nullable()
                    .eq(dsl::initial_attempt_id) // Filter initial attempts only
                    .and(dsl::business_profile_id.eq(profile_id.to_owned())),
            )
            .order(dsl::created_at.desc())
            .into_boxed();

        query = Self::apply_filters(
            query,
            None,
            (dsl::created_at, created_after, created_before),
            limit,
            offset,
            event_types,
            is_delivered,
        );

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

        track_database_call::<Self, _, _>(query.get_results_async(conn), DatabaseOperation::Filter)
            .await
            .change_context(DatabaseError::Others) // Query returns empty Vec when no records are found
            .attach_printable("Error filtering events by constraints")
    }

    pub async fn list_by_profile_id_initial_attempt_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        initial_attempt_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::business_profile_id
                .eq(profile_id.to_owned())
                .and(dsl::initial_attempt_id.eq(initial_attempt_id.to_owned())),
            None,
            None,
            Some(dsl::created_at.desc()),
        )
        .await
    }

    pub async fn update_by_merchant_id_event_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
        event: EventUpdateInternal,
    ) -> StorageResult<Self> {
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::event_id.eq(event_id.to_owned())),
            event,
        )
        .await
    }

    fn apply_filters<T>(
        mut query: T,
        profile_id: Option<common_utils::id_type::ProfileId>,
        (column, created_after, created_before): (
            dsl::created_at,
            time::PrimitiveDateTime,
            time::PrimitiveDateTime,
        ),
        limit: Option<i64>,
        offset: Option<i64>,
        event_types: HashSet<common_enums::EventType>,
        is_delivered: Option<bool>,
    ) -> T
    where
        T: diesel::query_dsl::methods::LimitDsl<Output = T>
            + diesel::query_dsl::methods::OffsetDsl<Output = T>,
        T: diesel::query_dsl::methods::FilterDsl<
            diesel::dsl::GtEq<dsl::created_at, time::PrimitiveDateTime>,
            Output = T,
        >,
        T: diesel::query_dsl::methods::FilterDsl<
            diesel::dsl::LtEq<dsl::created_at, time::PrimitiveDateTime>,
            Output = T,
        >,
        T: diesel::query_dsl::methods::FilterDsl<
            diesel::dsl::Eq<dsl::business_profile_id, common_utils::id_type::ProfileId>,
            Output = T,
        >,
        T: diesel::query_dsl::methods::FilterDsl<
            diesel::dsl::EqAny<dsl::event_type, HashSet<common_enums::EventType>>,
            Output = T,
        >,
        T: diesel::query_dsl::methods::FilterDsl<
            diesel::dsl::Eq<dsl::is_overall_delivery_successful, bool>,
            Output = T,
        >,
    {
        if let Some(profile_id) = profile_id {
            query = query.filter(dsl::business_profile_id.eq(profile_id));
        }

        query = query
            .filter(column.ge(created_after))
            .filter(column.le(created_before));

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

        if let Some(offset) = offset {
            query = query.offset(offset);
        }

        if !event_types.is_empty() {
            query = query.filter(dsl::event_type.eq_any(event_types));
        }

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

        query
    }

    pub async fn count_initial_attempts_by_constraints(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        profile_id: Option<common_utils::id_type::ProfileId>,
        created_after: time::PrimitiveDateTime,
        created_before: time::PrimitiveDateTime,
        event_types: HashSet<common_enums::EventType>,
        is_delivered: Option<bool>,
    ) -> StorageResult<i64> {
        use async_bb8_diesel::AsyncRunQueryDsl;
        use diesel::{debug_query, pg::Pg, QueryDsl};
        use error_stack::ResultExt;
        use router_env::logger;

        use super::generics::db_metrics::{track_database_call, DatabaseOperation};
        use crate::errors::DatabaseError;

        let mut query = Self::table()
            .count()
            .filter(
                dsl::event_id
                    .nullable()
                    .eq(dsl::initial_attempt_id) // Filter initial attempts only
                    .and(dsl::merchant_id.eq(merchant_id.to_owned())),
            )
            .into_boxed();

        query = Self::apply_filters(
            query,
            profile_id,
            (dsl::created_at, created_after, created_before),
            None,
            None,
            event_types,
            is_delivered,
        );

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

        track_database_call::<Self, _, _>(
            query.get_result_async::<i64>(conn),
            DatabaseOperation::Count,
        )
        .await
        .change_context(DatabaseError::Others)
        .attach_printable("Error counting events by constraints")
    }
}


pub async fn update_user_role(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: user_role_api::UpdateUserRoleRequest,
    _req_state: ReqState,
) -> UserResponse<()> {
    let role_info = roles::RoleInfo::from_role_id_in_lineage(
        &state,
        &req.role_id,
        &user_from_token.merchant_id,
        &user_from_token.org_id,
        &user_from_token.profile_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InvalidRoleId)?;

    if !role_info.is_updatable() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable(format!("User role cannot be updated to {}", req.role_id));
    }

    let user_to_be_updated =
        utils::user::get_user_from_db_by_email(&state, domain::UserEmail::try_from(req.email)?)
            .await
            .to_not_found_response(UserErrors::InvalidRoleOperation)
            .attach_printable("User not found in our records".to_string())?;

    if user_from_token.user_id == user_to_be_updated.get_user_id() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User Changing their own role");
    }

    let updator_role = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let mut is_updated = false;

    let v2_user_role_to_be_updated = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_to_be_updated.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V2,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(user_role) = v2_user_role_to_be_updated {
        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
            &state,
            &user_role.role_id,
            &user_from_token.org_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !role_to_be_updated.is_updatable() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "User role cannot be updated from {}",
                role_to_be_updated.get_role_id()
            ));
        }

        if role_info.get_entity_type() != role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Upgrade and downgrade of roles is not allowed, user_entity_type = {} req_entity_type = {}",
                role_to_be_updated.get_entity_type(),
                role_info.get_entity_type(),
            ));
        }

        if updator_role.get_entity_type() < role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Invalid operation, update requestor = {} cannot update target = {}",
                updator_role.get_entity_type(),
                role_to_be_updated.get_entity_type()
            ));
        }

        state
            .global_store
            .update_user_role_by_user_id_and_lineage(
                user_to_be_updated.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                Some(&user_from_token.merchant_id),
                Some(&user_from_token.profile_id),
                UserRoleUpdate::UpdateRole {
                    role_id: req.role_id.clone(),
                    modified_by: user_from_token.user_id.clone(),
                },
                UserRoleVersion::V2,
            )
            .await
            .change_context(UserErrors::InternalServerError)?;

        is_updated = true;
    }

    let v1_user_role_to_be_updated = match state
        .global_store
        .find_user_role_by_user_id_and_lineage(
            user_to_be_updated.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V1,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(e) => {
            if e.current_context().is_db_not_found() {
                None
            } else {
                return Err(UserErrors::InternalServerError.into());
            }
        }
    };

    if let Some(user_role) = v1_user_role_to_be_updated {
        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
            &state,
            &user_role.role_id,
            &user_from_token.org_id,
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

        if !role_to_be_updated.is_updatable() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "User role cannot be updated from {}",
                role_to_be_updated.get_role_id()
            ));
        }

        if role_info.get_entity_type() != role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Upgrade and downgrade of roles is not allowed, user_entity_type = {} req_entity_type = {}",
                role_to_be_updated.get_entity_type(),
                role_info.get_entity_type(),
            ));
        }

        if updator_role.get_entity_type() < role_to_be_updated.get_entity_type() {
            return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
                "Invalid operation, update requestor = {} cannot update target = {}",
                updator_role.get_entity_type(),
                role_to_be_updated.get_entity_type()
            ));
        }

        state
            .global_store
            .update_user_role_by_user_id_and_lineage(
                user_to_be_updated.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                Some(&user_from_token.merchant_id),
                Some(&user_from_token.profile_id),
                UserRoleUpdate::UpdateRole {
                    role_id: req.role_id.clone(),
                    modified_by: user_from_token.user_id,
                },
                UserRoleVersion::V1,
            )
            .await
            .change_context(UserErrors::InternalServerError)?;

        is_updated = true;
    }

    if !is_updated {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User with given email is not found in the organization")?;
    }

    auth::blacklist::insert_user_in_blacklist(&state, user_to_be_updated.get_user_id()).await?;

    Ok(ApplicationResponse::StatusOk)
}


pub struct TrustpaymentsRefundRequest {
    pub amount: StringMinorUnit,
}


OpenAPI Block Path: components.schemas.AdyenSplitItem
{
  "type": "object",
  "description": "Data for the split items",
  "required": [
    "amount",
    "split_type",
    "reference"
  ],
  "properties": {
    "amount": {
      "type": "integer",
      "format": "int64",
      "description": "The amount of the split item",
      "example": 6540
    },
    "split_type": {
      "$ref": "#/components/schemas/AdyenSplitType"
    },
    "account": {
      "type": "string",
      "description": "The unique identifier of the account to which the split amount is allocated.",
      "nullable": true
    },
    "reference": {
      "type": "string",
      "description": "Unique Identifier for the split item"
    },
    "description": {
      "type": "string",
      "description": "Description for the part of the payment that will be allocated to the specified account.",
      "nullable": true
    }
  },
  "additionalProperties": false
}


/// Read the env variable `CONFIG_DIR` or fallback to `config`
    pub fn get_config_directory() -> PathBuf {
        let mut config_path = PathBuf::new();

        let config_directory =
            std::env::var(crate::env::vars::CONFIG_DIR).unwrap_or_else(|_| "config".into());

        config_path.push(crate::env::workspace_path());
        config_path.push(config_directory);
        config_path
    }


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


pub async fn update_profile(
    state: SessionState,
    profile_id: &id_type::ProfileId,
    key_store: domain::MerchantKeyStore,
    request: api::ProfileUpdate,
) -> RouterResponse<api::ProfileResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let business_profile = db
        .find_business_profile_by_profile_id(key_manager_state, &key_store, profile_id)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    let profile_update = request
        .get_update_profile_object(&state, &key_store, &business_profile)
        .await?;

    let updated_business_profile = db
        .update_profile_by_profile_id(
            key_manager_state,
            &key_store,
            business_profile,
            profile_update,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
            id: profile_id.get_string_repr().to_owned(),
        })?;

    Ok(service_api::ApplicationResponse::Json(
        api_models::admin::ProfileResponse::foreign_try_from(updated_business_profile)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse business profile details")?,
    ))
}


pub async fn log_fraud_check(
        &self,
        attempt: &FraudCheck,
        old_attempt: Option<FraudCheck>,
        tenant_id: TenantID,
    ) -> MQResult<()> {
        if let Some(negative_event) = old_attempt {
            self.log_event(&KafkaEvent::old(
                &KafkaFraudCheck::from_storage(&negative_event),
                tenant_id.clone(),
                self.ckh_database_name.clone(),
            ))
            .attach_printable_lazy(|| {
                format!("Failed to add negative fraud check event {negative_event:?}")
            })?;
        };

        self.log_event(&KafkaEvent::new(
            &KafkaFraudCheck::from_storage(attempt),
            tenant_id.clone(),
            self.ckh_database_name.clone(),
        ))
        .attach_printable_lazy(|| {
            format!("Failed to add positive fraud check event {attempt:?}")
        })?;

        self.log_event(&KafkaConsolidatedEvent::new(
            &KafkaFraudCheckEvent::from_storage(attempt),
            tenant_id.clone(),
        ))
        .attach_printable_lazy(|| {
            format!("Failed to add consolidated fraud check  event {attempt:?}")
        })
    }


impl Deref for CardExpirationYear {
    type Target = StrongSecret<u16>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}


            } else {
                let card_info = card_isin
                    .clone()
                    .async_and_then(|card_isin| async move {
                        db.get_card_info(&card_isin)
                            .await
                            .map_err(|error| services::logger::warn!(card_info_error=?error))
                            .ok()
                    })
                    .await
                    .flatten()
                    .map(|card_info| {
                        api_models::payments::AdditionalPaymentData::Card(Box::new(
                            api_models::payments::AdditionalCardInfo {
                                card_issuer: card_info.card_issuer,
                                card_network: card_network.clone().or(card_info.card_network),
                                bank_code: card_info.bank_code,
                                card_type: card_info.card_type,
                                card_issuing_country: card_info.card_issuing_country,
                                last4: last4.clone(),
                                card_isin: card_isin.clone(),
                                card_extended_bin: card_extended_bin.clone(),
                                card_exp_month: Some(card_data.card_exp_month.clone()),
                                card_exp_year: Some(card_data.card_exp_year.clone()),
                                card_holder_name: card_data.card_holder_name.clone(),
                                // These are filled after calling the processor / connector
                                payment_checks: None,
                                authentication_data: None,
                                is_regulated: None,
                                signature_network: None,
                            },
                        ))
                    });
                Ok(Some(card_info.unwrap_or_else(|| {
                    api_models::payments::AdditionalPaymentData::Card(Box::new(
                        api_models::payments::AdditionalCardInfo {
                            card_issuer: None,
                            card_network,
                            bank_code: None,
                            card_type: None,
                            card_issuing_country: None,
                            last4,
                            card_isin,
                            card_extended_bin,
                            card_exp_month: Some(card_data.card_exp_month.clone()),
                            card_exp_year: Some(card_data.card_exp_year.clone()),
                            card_holder_name: card_data.card_holder_name.clone(),
                            // These are filled after calling the processor / connector
                            payment_checks: None,
                            authentication_data: None,
                            is_regulated: None,
                            signature_network: None,
                        },
                    ))
                })))
            }
        }
        domain::PaymentMethodData::MobilePayment(mobile_payment) => Ok(Some(
            api_models::payments::AdditionalPaymentData::MobilePayment {
                details: Some(mobile_payment.to_owned().into()),
            },
        )),
        domain::PaymentMethodData::NetworkToken(_) => Ok(None),
    }
}

#[cfg(feature = "v1")]
pub fn validate_customer_access(
    payment_intent: &PaymentIntent,
    auth_flow: services::AuthFlow,
    request: &api::PaymentsRequest,
) -> Result<(), errors::ApiErrorResponse> {
    if auth_flow == services::AuthFlow::Client && request.get_customer_id().is_some() {
        let is_same_customer = request.get_customer_id() == payment_intent.customer_id.as_ref();
        if !is_same_customer {
            Err(errors::ApiErrorResponse::GenericUnauthorized {
                message: "Unauthorised access to update customer".to_string(),
            })?;
        }
    }
    Ok(())
}

pub fn is_apple_pay_simplified_flow(
    connector_metadata: Option<pii::SecretSerdeValue>,
    connector_name: Option<&String>,
) -> CustomResult<bool, errors::ApiErrorResponse> {
    let option_apple_pay_metadata = get_applepay_metadata(connector_metadata)
        .map_err(|error| {
            logger::info!(
                "Apple pay metadata parsing for {:?} in is_apple_pay_simplified_flow {:?}",
                connector_name,
                error
            )
        })
        .ok();

    // return true only if the apple flow type is simplified
    Ok(matches!(
        option_apple_pay_metadata,
        Some(
            api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
                api_models::payments::ApplePayCombinedMetadata::Simplified { .. }
            )
        )
    ))
}

// This function will return the encrypted connector wallets details with Apple Pay certificates
// Currently apple pay certifiactes are stored in the metadata which is not encrypted.
// In future we want those certificates to be encrypted and stored in the connector_wallets_details.
// As part of migration fallback this function checks apple pay details are present in connector_wallets_details
// If yes, it will encrypt connector_wallets_details and store it in the database.
// If no, it will check if apple pay details are present in metadata and merge it with connector_wallets_details, encrypt and store it.
pub async fn get_connector_wallets_details_with_apple_pay_certificates(
    connector_metadata: &Option<masking::Secret<tera::Value>>,
    connector_wallets_details_optional: &Option<api_models::admin::ConnectorWalletDetails>,
) -> RouterResult<Option<masking::Secret<serde_json::Value>>> {
    let connector_wallet_details_with_apple_pay_metadata_optional =
        get_apple_pay_metadata_if_needed(connector_metadata, connector_wallets_details_optional)
            .await?;

    let connector_wallets_details = connector_wallet_details_with_apple_pay_metadata_optional
        .map(|details| {
            serde_json::to_value(details)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to serialize Apple Pay metadata as JSON")
        })
        .transpose()?
        .map(masking::Secret::new);

    Ok(connector_wallets_details)
}

async fn get_apple_pay_metadata_if_needed(
    connector_metadata: &Option<masking::Secret<tera::Value>>,
    connector_wallets_details_optional: &Option<api_models::admin::ConnectorWalletDetails>,
) -> RouterResult<Option<api_models::admin::ConnectorWalletDetails>> {
    if let Some(connector_wallets_details) = connector_wallets_details_optional {
        if connector_wallets_details.apple_pay_combined.is_some()
            || connector_wallets_details.apple_pay.is_some()
        {
            return Ok(Some(connector_wallets_details.clone()));
        }
        // Otherwise, merge Apple Pay metadata
        return get_and_merge_apple_pay_metadata(
            connector_metadata.clone(),
            Some(connector_wallets_details.clone()),
        )
        .await;
    }

    // If connector_wallets_details_optional is None, attempt to get Apple Pay metadata
    get_and_merge_apple_pay_metadata(connector_metadata.clone(), None).await
}

async fn get_and_merge_apple_pay_metadata(
    connector_metadata: Option<masking::Secret<tera::Value>>,
    connector_wallets_details_optional: Option<api_models::admin::ConnectorWalletDetails>,
) -> RouterResult<Option<api_models::admin::ConnectorWalletDetails>> {
    let apple_pay_metadata_optional = get_applepay_metadata(connector_metadata)
        .map_err(|error| {
            logger::error!(
                "Apple Pay metadata parsing failed in get_encrypted_connector_wallets_details_with_apple_pay_certificates {:?}",
                error
            );
        })
        .ok();

    if let Some(apple_pay_metadata) = apple_pay_metadata_optional {
        let updated_wallet_details = match apple_pay_metadata {
            api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
                apple_pay_combined_metadata,
            ) => {
                let combined_metadata_json = serde_json::to_value(apple_pay_combined_metadata)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to serialize Apple Pay combined metadata as JSON")?;

                api_models::admin::ConnectorWalletDetails {
                    apple_pay_combined: Some(masking::Secret::new(combined_metadata_json)),
                    apple_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.apple_pay.clone()),
                    amazon_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.amazon_pay.clone()),
                    samsung_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.samsung_pay.clone()),
                    paze: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.paze.clone()),
                    google_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.google_pay.clone()),
                }
            }
            api_models::payments::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => {
                let metadata_json = serde_json::to_value(apple_pay_metadata)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to serialize Apple Pay metadata as JSON")?;

                api_models::admin::ConnectorWalletDetails {
                    apple_pay: Some(masking::Secret::new(metadata_json)),
                    apple_pay_combined: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.apple_pay_combined.clone()),
                    amazon_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.amazon_pay.clone()),
                    samsung_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.samsung_pay.clone()),
                    paze: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.paze.clone()),
                    google_pay: connector_wallets_details_optional
                        .as_ref()
                        .and_then(|d| d.google_pay.clone()),
                }
            }
        };

        return Ok(Some(updated_wallet_details));
    }

    // Return connector_wallets_details if no Apple Pay metadata was found
    Ok(connector_wallets_details_optional)
}

pub fn get_applepay_metadata(
    connector_metadata: Option<pii::SecretSerdeValue>,
) -> RouterResult<api_models::payments::ApplepaySessionTokenMetadata> {
    connector_metadata
        .clone()
        .parse_value::<api_models::payments::ApplepayCombinedSessionTokenData>(
            "ApplepayCombinedSessionTokenData",
        )
        .map(|combined_metadata| {
            api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined(
                combined_metadata.apple_pay_combined,
            )
        })
        .or_else(|_| {
            connector_metadata
                .parse_value::<api_models::payments::ApplepaySessionTokenData>(
                    "ApplepaySessionTokenData",
                )
                .map(|old_metadata| {
                    api_models::payments::ApplepaySessionTokenMetadata::ApplePay(
                        old_metadata.apple_pay,
                    )
                })
        })
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "connector_metadata".to_string(),
            expected_format: "applepay_metadata_format".to_string(),
        })
}

pub fn calculate_debit_routing_savings(net_amount: i64, saving_percentage: f64) -> MinorUnit {
    logger::debug!(
        ?net_amount,
        ?saving_percentage,
        "Calculating debit routing saving amount"
    );

    let net_decimal = Decimal::from_i64(net_amount).unwrap_or_else(|| {
        logger::warn!(?net_amount, "Invalid net_amount, using 0");
        Decimal::ZERO
    });

    let percentage_decimal = Decimal::from_f64(saving_percentage).unwrap_or_else(|| {
        logger::warn!(?saving_percentage, "Invalid saving_percentage, using 0");
        Decimal::ZERO
    });

    let savings_decimal = net_decimal * percentage_decimal / Decimal::from(100);
    let rounded_savings = savings_decimal.round();

    let savings_int = rounded_savings.to_i64().unwrap_or_else(|| {
        logger::warn!(
            ?rounded_savings,
            "Debit routing savings calculation overflowed when converting to i64"
        );
        0
    });

    MinorUnit::new(savings_int)
}

pub fn get_debit_routing_savings_amount(
    payment_method_data: &domain::PaymentMethodData,
    payment_attempt: &PaymentAttempt,
) -> Option<MinorUnit> {
    let card_network = payment_attempt.extract_card_network()?;

    let saving_percentage =
        payment_method_data.extract_debit_routing_saving_percentage(&card_network)?;

    let net_amount = payment_attempt.get_total_amount().get_amount_as_i64();

    Some(calculate_debit_routing_savings(
        net_amount,
        saving_percentage,
    ))
}

#[cfg(all(feature = "retry", feature = "v1"))]
pub async fn get_apple_pay_retryable_connectors<F, D>(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: &D,
    pre_routing_connector_data_list: &[api::ConnectorRoutingData],
    merchant_connector_id: Option<&id_type::MerchantConnectorAccountId>,
    business_profile: domain::Profile,
) -> CustomResult<Option<Vec<api::ConnectorRoutingData>>, errors::ApiErrorResponse>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F> + Send,
{
    let profile_id = business_profile.get_id();

    let pre_decided_connector_data_first = pre_routing_connector_data_list
        .first()
        .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration)?;

    let merchant_connector_account_type = get_merchant_connector_account(
        state,
        merchant_context.get_merchant_account().get_id(),
        payment_data.get_creds_identifier(),
        merchant_context.get_merchant_key_store(),
        profile_id,
        &pre_decided_connector_data_first
            .connector_data
            .connector_name
            .to_string(),
        merchant_connector_id,
    )
    .await?;

    let connector_data_list = if is_apple_pay_simplified_flow(
        merchant_connector_account_type.get_metadata(),
        merchant_connector_account_type
            .get_connector_name()
            .as_ref(),
    )? {
        let merchant_connector_account_list = state
            .store
            .find_merchant_connector_account_by_merchant_id_and_disabled_list(
                &state.into(),
                merchant_context.get_merchant_account().get_id(),
                false,
                merchant_context.get_merchant_key_store(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

        let profile_specific_merchant_connector_account_list = merchant_connector_account_list
            .filter_based_on_profile_and_connector_type(
                profile_id,
                ConnectorType::PaymentProcessor,
            );

        let mut connector_data_list = vec![pre_decided_connector_data_first.clone()];

        for merchant_connector_account in profile_specific_merchant_connector_account_list {
            if is_apple_pay_simplified_flow(
                merchant_connector_account.metadata.clone(),
                Some(&merchant_connector_account.connector_name),
            )? {
                let routing_data: api::ConnectorRoutingData =
                    api::ConnectorData::get_connector_by_name(
                        &state.conf.connectors,
                        &merchant_connector_account.connector_name.to_string(),
                        api::GetToken::Connector,
                        Some(merchant_connector_account.get_id()),
                    )
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Invalid connector name received")?
                    .into();

                if !connector_data_list.iter().any(|connector_details| {
                    connector_details.connector_data.merchant_connector_id
                        == routing_data.connector_data.merchant_connector_id
                }) {
                    connector_data_list.push(routing_data)
                }
            }
        }
        #[cfg(feature = "v1")]
        let fallback_connetors_list = crate::core::routing::helpers::get_merchant_default_config(
            &*state.clone().store,
            profile_id.get_string_repr(),
            &api_enums::TransactionType::Payment,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get merchant default fallback connectors config")?;

        let mut routing_connector_data_list = Vec::new();

        pre_routing_connector_data_list.iter().for_each(|pre_val| {
            routing_connector_data_list.push(pre_val.connector_data.merchant_connector_id.clone())
        });

        fallback_connetors_list.iter().for_each(|fallback_val| {
            routing_connector_data_list
                .iter()
                .all(|val| *val != fallback_val.merchant_connector_id)
                .then(|| {
                    routing_connector_data_list.push(fallback_val.merchant_connector_id.clone())
                });
        });

        // connector_data_list is the list of connectors for which Apple Pay simplified flow is configured.
        // This list is arranged in the same order as the merchant's connectors routingconfiguration.

        let mut ordered_connector_data_list = Vec::new();

        routing_connector_data_list
            .iter()
            .for_each(|merchant_connector_id| {
                let connector_data = connector_data_list.iter().find(|connector_data| {
                    *merchant_connector_id == connector_data.connector_data.merchant_connector_id
                });
                if let Some(connector_data_details) = connector_data {
                    ordered_connector_data_list.push(connector_data_details.clone());
                }
            });

        Some(ordered_connector_data_list)
    } else {
        None
    };
    Ok(connector_data_list)
}

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

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

impl ApplePayData {
    pub fn token_json(
        wallet_data: domain::WalletData,
    ) -> CustomResult<Self, errors::ConnectorError> {
        let json_wallet_data: Self = connector::utils::WalletData::get_wallet_token_as_json(
            &wallet_data,
            "Apple Pay".to_string(),
        )?;
        Ok(json_wallet_data)
    }

    pub async fn decrypt(
        &self,
        payment_processing_certificate: &masking::Secret<String>,
        payment_processing_certificate_key: &masking::Secret<String>,
    ) -> CustomResult<serde_json::Value, errors::ApplePayDecryptionError> {
        let merchant_id = self.merchant_id(payment_processing_certificate)?;
        let shared_secret = self.shared_secret(payment_processing_certificate_key)?;
        let symmetric_key = self.symmetric_key(&merchant_id, &shared_secret)?;
        let decrypted = self.decrypt_ciphertext(&symmetric_key)?;
        let parsed_decrypted: serde_json::Value = serde_json::from_str(&decrypted)
            .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?;
        Ok(parsed_decrypted)
    }

    pub fn merchant_id(
        &self,
        payment_processing_certificate: &masking::Secret<String>,
    ) -> CustomResult<String, errors::ApplePayDecryptionError> {
        let cert_data = payment_processing_certificate.clone().expose();

        let base64_decode_cert_data = BASE64_ENGINE
            .decode(cert_data)
            .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?;

        // Parsing the certificate using x509-parser
        let (_, certificate) = parse_x509_certificate(&base64_decode_cert_data)
            .change_context(errors::ApplePayDecryptionError::CertificateParsingFailed)
            .attach_printable("Error parsing apple pay PPC")?;

        // Finding the merchant ID extension
        let apple_pay_m_id = certificate
            .extensions()
            .iter()
            .find(|extension| {
                extension
                    .oid
                    .to_string()
                    .eq(consts::MERCHANT_ID_FIELD_EXTENSION_ID)
            })
            .map(|ext| {
                let merchant_id = String::from_utf8_lossy(ext.value)
                    .trim()
                    .trim_start_matches('@')
                    .to_string();

                merchant_id
            })
            .ok_or(errors::ApplePayDecryptionError::MissingMerchantId)
            .attach_printable("Unable to find merchant ID extension in the certificate")?;

        Ok(apple_pay_m_id)
    }

    pub fn shared_secret(
        &self,
        payment_processing_certificate_key: &masking::Secret<String>,
    ) -> CustomResult<Vec<u8>, errors::ApplePayDecryptionError> {
        let public_ec_bytes = BASE64_ENGINE
            .decode(self.header.ephemeral_public_key.peek().as_bytes())
            .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?;

        let public_key = PKey::public_key_from_der(&public_ec_bytes)
            .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed)
            .attach_printable("Failed to deserialize the public key")?;

        let decrypted_apple_pay_ppc_key = payment_processing_certificate_key.clone().expose();

        // Create PKey objects from EcKey
        let private_key = PKey::private_key_from_pem(decrypted_apple_pay_ppc_key.as_bytes())
            .change_context(errors::ApplePayDecryptionError::KeyDeserializationFailed)
            .attach_printable("Failed to deserialize the private key")?;

        // Create the Deriver object and set the peer public key
        let mut deriver = Deriver::new(&private_key)
            .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed)
            .attach_printable("Failed to create a deriver for the private key")?;

        deriver
            .set_peer(&public_key)
            .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed)
            .attach_printable("Failed to set the peer key for the secret derivation")?;

        // Compute the shared secret
        let shared_secret = deriver
            .derive_to_vec()
            .change_context(errors::ApplePayDecryptionError::DerivingSharedSecretKeyFailed)
            .attach_printable("Final key derivation failed")?;
        Ok(shared_secret)
    }

    pub fn symmetric_key(
        &self,
        merchant_id: &str,
        shared_secret: &[u8],
    ) -> CustomResult<Vec<u8>, errors::ApplePayDecryptionError> {
        let kdf_algorithm = b"\x0did-aes256-GCM";
        let kdf_party_v = hex::decode(merchant_id)
            .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?;
        let kdf_party_u = b"Apple";
        let kdf_info = [&kdf_algorithm[..], kdf_party_u, &kdf_party_v[..]].concat();

        let mut hash = openssl::sha::Sha256::new();
        hash.update(b"\x00\x00\x00");
        hash.update(b"\x01");
        hash.update(shared_secret);
        hash.update(&kdf_info[..]);
        let symmetric_key = hash.finish();
        Ok(symmetric_key.to_vec())
    }

    pub fn decrypt_ciphertext(
        &self,
        symmetric_key: &[u8],
    ) -> CustomResult<String, errors::ApplePayDecryptionError> {
        logger::info!("Decrypt apple pay token");

        let data = BASE64_ENGINE
            .decode(self.data.peek().as_bytes())
            .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?;
        let iv = [0u8; 16]; //Initialization vector IV is typically used in AES-GCM (Galois/Counter Mode) encryption for randomizing the encryption process.
        let ciphertext = data
            .get(..data.len() - 16)
            .ok_or(errors::ApplePayDecryptionError::DecryptionFailed)?;
        let tag = data
            .get(data.len() - 16..)
            .ok_or(errors::ApplePayDecryptionError::DecryptionFailed)?;
        let cipher = Cipher::aes_256_gcm();
        let decrypted_data = decrypt_aead(cipher, symmetric_key, Some(&iv), &[], ciphertext, tag)
            .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?;
        let decrypted = String::from_utf8(decrypted_data)
            .change_context(errors::ApplePayDecryptionError::DecryptionFailed)?;

        Ok(decrypted)
    }
}

// Structs for keys and the main decryptor
pub struct GooglePayTokenDecryptor {
    root_signing_keys: Vec<GooglePayRootSigningKey>,
    recipient_id: masking::Secret<String>,
    private_key: PKey<openssl::pkey::Private>,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EncryptedData {
    signature: String,
    intermediate_signing_key: IntermediateSigningKey,
    protocol_version: GooglePayProtocolVersion,
    #[serde(with = "common_utils::custom_serde::json_string")]
    signed_message: GooglePaySignedMessage,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePaySignedMessage {
    #[serde(with = "common_utils::Base64Serializer")]
    encrypted_message: masking::Secret<Vec<u8>>,
    #[serde(with = "common_utils::Base64Serializer")]
    ephemeral_public_key: masking::Secret<Vec<u8>>,
    #[serde(with = "common_utils::Base64Serializer")]
    tag: masking::Secret<Vec<u8>>,
}

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

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePaySignedKey {
    key_value: masking::Secret<String>,
    key_expiration: String,
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GooglePayRootSigningKey {
    key_value: masking::Secret<String>,
    key_expiration: String,
    protocol_version: GooglePayProtocolVersion,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub enum GooglePayProtocolVersion {
    #[serde(rename = "ECv2")]
    EcProtocolVersion2,
}

// Check expiration date validity
fn check_expiration_date_is_valid(
    expiration: &str,
) -> CustomResult<bool, errors::GooglePayDecryptionError> {
    let expiration_ms = expiration
        .parse::<i128>()
        .change_context(errors::GooglePayDecryptionError::InvalidExpirationTime)?;
    // convert milliseconds to nanoseconds (1 millisecond = 1_000_000 nanoseconds) to create OffsetDateTime
    let expiration_time =
        time::OffsetDateTime::from_unix_timestamp_nanos(expiration_ms * 1_000_000)
            .change_context(errors::GooglePayDecryptionError::InvalidExpirationTime)?;
    let now = time::OffsetDateTime::now_utc();

    Ok(expiration_time > now)
}

// Construct little endian format of u32
fn get_little_endian_format(number: u32) -> Vec<u8> {
    number.to_le_bytes().to_vec()
}

// Filter and parse the root signing keys based on protocol version and expiration time
fn filter_root_signing_keys(
    root_signing_keys: Vec<GooglePayRootSigningKey>,
) -> CustomResult<Vec<GooglePayRootSigningKey>, errors::GooglePayDecryptionError> {
    let filtered_root_signing_keys = root_signing_keys
        .iter()
        .filter(|key| {
            key.protocol_version == GooglePayProtocolVersion::EcProtocolVersion2
                && matches!(
                    check_expiration_date_is_valid(&key.key_expiration).inspect_err(
                        |err| logger::warn!(
                            "Failed to check expirattion due to invalid format: {:?}",
                            err
                        )
                    ),
                    Ok(true)
                )
        })
        .cloned()
        .collect::<Vec<GooglePayRootSigningKey>>();

    logger::info!(
        "Filtered {} out of {} root signing keys",
        filtered_root_signing_keys.len(),
        root_signing_keys.len()
    );

    Ok(filtered_root_signing_keys)
}

impl GooglePayTokenDecryptor {
    pub fn new(
        root_keys: masking::Secret<String>,
        recipient_id: masking::Secret<String>,
        private_key: masking::Secret<String>,
    ) -> CustomResult<Self, errors::GooglePayDecryptionError> {
        // base64 decode the private key
        let decoded_key = BASE64_ENGINE
            .decode(private_key.expose())
            .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;
        // base64 decode the root signing keys
        let decoded_root_signing_keys = BASE64_ENGINE
            .decode(root_keys.expose())
            .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;
        // create a private key from the decoded key
        let private_key = PKey::private_key_from_pkcs8(&decoded_key)
            .change_context(errors::GooglePayDecryptionError::KeyDeserializationFailed)
            .attach_printable("cannot convert private key from decode_key")?;

        // parse the root signing keys
        let root_keys_vector: Vec<GooglePayRootSigningKey> = decoded_root_signing_keys
            .parse_struct("GooglePayRootSigningKey")
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // parse and filter the root signing keys by protocol version
        let filtered_root_signing_keys = filter_root_signing_keys(root_keys_vector)?;

        Ok(Self {
            root_signing_keys: filtered_root_signing_keys,
            recipient_id,
            private_key,
        })
    }

    // Decrypt the Google pay token
    pub fn decrypt_token(
        &self,
        data: String,
        should_verify_signature: bool,
    ) -> CustomResult<
        hyperswitch_domain_models::router_data::GooglePayPredecryptDataInternal,
        errors::GooglePayDecryptionError,
    > {
        // parse the encrypted data
        let encrypted_data: EncryptedData = data
            .parse_struct("EncryptedData")
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // verify the signature if required
        if should_verify_signature {
            self.verify_signature(&encrypted_data)?;
        }

        let ephemeral_public_key = encrypted_data.signed_message.ephemeral_public_key.peek();
        let tag = encrypted_data.signed_message.tag.peek();
        let encrypted_message = encrypted_data.signed_message.encrypted_message.peek();

        // derive the shared key
        let shared_key = self.get_shared_key(ephemeral_public_key)?;

        // derive the symmetric encryption key and MAC key
        let derived_key = self.derive_key(ephemeral_public_key, &shared_key)?;
        // First 32 bytes for AES-256 and Remaining bytes for HMAC
        let (symmetric_encryption_key, mac_key) = derived_key
            .split_at_checked(32)
            .ok_or(errors::GooglePayDecryptionError::ParsingFailed)?;

        // verify the HMAC of the message
        self.verify_hmac(mac_key, tag, encrypted_message)?;

        // decrypt the message
        let decrypted = self.decrypt_message(symmetric_encryption_key, encrypted_message)?;

        // parse the decrypted data
        let decrypted_data: hyperswitch_domain_models::router_data::GooglePayPredecryptDataInternal =
            decrypted
                .parse_struct("GooglePayPredecryptDataInternal")
                .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // check the expiration date of the decrypted data
        if matches!(
            check_expiration_date_is_valid(&decrypted_data.message_expiration),
            Ok(true)
        ) {
            Ok(decrypted_data)
        } else {
            Err(errors::GooglePayDecryptionError::DecryptedTokenExpired.into())
        }
    }

    // Verify the signature of the token
    fn verify_signature(
        &self,
        encrypted_data: &EncryptedData,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        // check the protocol version
        if encrypted_data.protocol_version != GooglePayProtocolVersion::EcProtocolVersion2 {
            return Err(errors::GooglePayDecryptionError::InvalidProtocolVersion.into());
        }

        // verify the intermediate signing key
        self.verify_intermediate_signing_key(encrypted_data)?;
        // validate and fetch the signed key
        let signed_key = self.validate_signed_key(&encrypted_data.intermediate_signing_key)?;
        // verify the signature of the token
        self.verify_message_signature(encrypted_data, &signed_key)
    }

    // Verify the intermediate signing key
    fn verify_intermediate_signing_key(
        &self,
        encrypted_data: &EncryptedData,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        let mut signatrues: Vec<openssl::ecdsa::EcdsaSig> = Vec::new();

        // decode and parse the signatures
        for signature in encrypted_data.intermediate_signing_key.signatures.iter() {
            let signature = BASE64_ENGINE
                .decode(signature.peek())
                .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;
            let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature)
                .change_context(errors::GooglePayDecryptionError::EcdsaSignatureParsingFailed)?;
            signatrues.push(ecdsa_signature);
        }

        // get the sender id i.e. Google
        let sender_id = String::from_utf8(consts::SENDER_ID.to_vec())
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // construct the signed data
        let signed_data = self.construct_signed_data_for_intermediate_signing_key_verification(
            &sender_id,
            consts::PROTOCOL,
            encrypted_data.intermediate_signing_key.signed_key.peek(),
        )?;

        // check if any of the signatures are valid for any of the root signing keys
        for key in self.root_signing_keys.iter() {
            // decode and create public key
            let public_key = self
                .load_public_key(key.key_value.peek())
                .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?;
            // fetch the ec key from public key
            let ec_key = public_key
                .ec_key()
                .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

            // hash the signed data
            let message_hash = openssl::sha::sha256(&signed_data);

            // verify if any of the signatures is valid against the given key
            for signature in signatrues.iter() {
                let result = signature.verify(&message_hash, &ec_key).change_context(
                    errors::GooglePayDecryptionError::SignatureVerificationFailed,
                )?;

                if result {
                    return Ok(());
                }
            }
        }

        Err(errors::GooglePayDecryptionError::InvalidIntermediateSignature.into())
    }

    // Construct signed data for intermediate signing key verification
    fn construct_signed_data_for_intermediate_signing_key_verification(
        &self,
        sender_id: &str,
        protocol_version: &str,
        signed_key: &str,
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        let length_of_sender_id = u32::try_from(sender_id.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_protocol_version = u32::try_from(protocol_version.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_signed_key = u32::try_from(signed_key.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;

        let mut signed_data: Vec<u8> = Vec::new();
        signed_data.append(&mut get_little_endian_format(length_of_sender_id));
        signed_data.append(&mut sender_id.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_protocol_version));
        signed_data.append(&mut protocol_version.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_signed_key));
        signed_data.append(&mut signed_key.as_bytes().to_vec());

        Ok(signed_data)
    }

    // Validate and parse signed key
    fn validate_signed_key(
        &self,
        intermediate_signing_key: &IntermediateSigningKey,
    ) -> CustomResult<GooglePaySignedKey, errors::GooglePayDecryptionError> {
        let signed_key: GooglePaySignedKey = intermediate_signing_key
            .signed_key
            .clone()
            .expose()
            .parse_struct("GooglePaySignedKey")
            .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?;
        if !matches!(
            check_expiration_date_is_valid(&signed_key.key_expiration),
            Ok(true)
        ) {
            return Err(errors::GooglePayDecryptionError::SignedKeyExpired)?;
        }
        Ok(signed_key)
    }

    // Verify the signed message
    fn verify_message_signature(
        &self,
        encrypted_data: &EncryptedData,
        signed_key: &GooglePaySignedKey,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        // create a public key from the intermediate signing key
        let public_key = self.load_public_key(signed_key.key_value.peek())?;
        // base64 decode the signature
        let signature = BASE64_ENGINE
            .decode(&encrypted_data.signature)
            .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;

        // parse the signature using ECDSA
        let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature)
            .change_context(errors::GooglePayDecryptionError::EcdsaSignatureFailed)?;

        // get the EC key from the public key
        let ec_key = public_key
            .ec_key()
            .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

        // get the sender id i.e. Google
        let sender_id = String::from_utf8(consts::SENDER_ID.to_vec())
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // serialize the signed message to string
        let signed_message = serde_json::to_string(&encrypted_data.signed_message)
            .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?;

        // construct the signed data
        let signed_data = self.construct_signed_data_for_signature_verification(
            &sender_id,
            consts::PROTOCOL,
            &signed_message,
        )?;

        // hash the signed data


impl Responder {
    let flow = Flow::ApiKeyUpdate;
    let (merchant_id, key_id) = path.into_inner();
    let mut payload = json_payload.into_inner();
    payload.key_id = key_id;
    payload.merchant_id.clone_from(&merchant_id);

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


pub async fn store_data_in_redis(
    state: &SessionState,
    key: String,
    data: impl serde::Serialize + Debug,
    ttl: i64,
) -> types::DummyConnectorResult<()> {
    let redis_conn = state
        .store
        .get_redis_conn()
        .change_context(errors::DummyConnectorErrors::InternalServerError)
        .attach_printable("Failed to get redis connection")?;

    redis_conn
        .serialize_and_set_key_with_expiry(&key.into(), data, ttl)
        .await
        .change_context(errors::DummyConnectorErrors::PaymentStoringError)
        .attach_printable("Failed to add data in redis")?;
    Ok(())
}


pub fn apply_routing_decision<F, D>(&self, payment_data: &mut D)


OpenAPI Block Path: components.schemas.StripeSplitRefundRequest
{
  "type": "object",
  "description": "Charge specific fields for controlling the revert of funds from either platform or connected account for Stripe. Check sub-fields for more details.",
  "properties": {
    "revert_platform_fee": {
      "type": "boolean",
      "description": "Toggle for reverting the application fee that was collected for the payment.\nIf set to false, the funds are pulled from the destination account.",
      "nullable": true
    },
    "revert_transfer": {
      "type": "boolean",
      "description": "Toggle for reverting the transfer that was made during the charge.\nIf set to false, the funds are pulled from the main platform's account.",
      "nullable": true
    }
  },
  "additionalProperties": false
}


pub fn get_card_range_if_available(&self) -> Option<CardRange> {
        let card_range = self
            .card_ranges
            .iter()
            .max_by_key(|card_range| &card_range.highest_common_supported_version);
        card_range.cloned()
    }


pub struct Billing {
    pub name: Name,
    pub address: Address,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone_number: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<pii::Email>,
}


pub fn from_storage(check: &'a FraudCheck) -> Self {
        Self {
            frm_id: &check.frm_id,
            payment_id: &check.payment_id,
            merchant_id: &check.merchant_id,
            attempt_id: &check.attempt_id,
            created_at: check.created_at.assume_utc(),
            frm_name: &check.frm_name,
            frm_transaction_id: check.frm_transaction_id.as_ref(),
            frm_transaction_type: check.frm_transaction_type,
            frm_status: check.frm_status,
            frm_score: check.frm_score,
            frm_reason: check.frm_reason.clone(),
            frm_error: check.frm_error.as_ref(),
            payment_details: check.payment_details.clone(),
            metadata: check.metadata.clone(),
            modified_at: check.modified_at.assume_utc(),
            last_step: check.last_step,
            payment_capture_method: check.payment_capture_method,
        }
    }


pub struct MonerisError {
    pub reason_code: String,
    pub parameter_name: String,
}


impl api::RefundExecute for Forte {}


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

# Masking

Personal Identifiable Information protection.
Wrapper types and traits for secret management which help ensure they aren't
accidentally copied, logged, or otherwise exposed (as much as possible), and
also ensure secrets are securely wiped from memory when dropped.
Secret-keeping library inspired by `secrecy`.

This solution has such advantages over alternatives:

- alternatives have not implemented several traits from the box which are needed
- alternatives do not have WeakSecret and Secret differentiation
- alternatives do not support masking strategies
- alternatives had several minor problems

## How to use

To convert a non-secret variable into a secret, use `Secret::new()`:

```rust
use masking::Secret;

let card_number: Secret<String> = Secret::new(String::from("1234 5678 9012 3456"));
assert_eq!(format!("{:?}", card_number), "*** alloc::string::String ***");
```

To get a reference to the inner value from the secret, use `peek()`:

```rust
use masking::{PeekInterface, Secret};

let card_number: Secret<String> = Secret::new(String::from("1234 5678 9012 3456"));
let last4_digits: &str = card_number.peek();
```

To get the owned inner value from the secret, use `expose()`:

```rust
use masking::{ExposeInterface, Secret};

let card_number: Secret<String> = Secret::new(String::from("1234 5678 9012 3456"));
let last4_digits: String = card_number.expose();
```

For fields that are `Option<T>`, you can use `expose_option()`:

```rust
use masking::{ExposeOptionInterface, Secret};

let card_number: Option<Secret<String>> = Some(Secret::new(String::from("1234 5678 9012 3456")));
let card_number_str: String = card_number.expose_option().unwrap_or_default();
assert_eq!(format!("{}", card_number_str), "1234 5678 9012 3456");

let card_number: Option<Secret<String>> = None;
let card_number_str: String = card_number.expose_option().unwrap_or_default();
assert_eq!(format!("{}", card_number_str), "");
```


OpenAPI Block Path: components.schemas.PaymentMethodsEnabled
{
  "type": "object",
  "description": "Details of all the payment methods enabled for the connector for the given merchant account",
  "required": [
    "payment_method"
  ],
  "properties": {
    "payment_method": {
      "$ref": "#/components/schemas/PaymentMethod"
    },
    "payment_method_types": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RequestPaymentMethodTypes"
      },
      "description": "Subtype of payment method",
      "example": [
        "credit"
      ],
      "nullable": true
    }
  },
  "additionalProperties": false
}


pub struct PayoutDetails<'a> {
    pub description: Option<&'a str>,
    pub kind: PayoutDirKeyKind,
}


impl api::ConnectorAccessToken for Hipay {}

impl api::ConnectorAccessToken for for Hipay