File: crates/router/src/db/ephemeral_key.rs

#[cfg(feature = "v2")]
use common_utils::id_type;
use time::ext::NumericalDuration;

#[cfg(feature = "v2")]
use crate::types::storage::ephemeral_key::{ClientSecretType, ClientSecretTypeNew};
use crate::{
    core::errors::{self, CustomResult},
    db::MockDb,
    types::storage::ephemeral_key::{EphemeralKey, EphemeralKeyNew},
};

#[async_trait::async_trait]
pub trait EphemeralKeyInterface {
    #[cfg(feature = "v1")]
    async fn create_ephemeral_key(
        &self,
        _ek: EphemeralKeyNew,
        _validity: i64,
    ) -> CustomResult<EphemeralKey, errors::StorageError>;

    #[cfg(feature = "v1")]
    async fn get_ephemeral_key(
        &self,
        _key: &str,
    ) -> CustomResult<EphemeralKey, errors::StorageError>;

    #[cfg(feature = "v1")]
    async fn delete_ephemeral_key(
        &self,
        _id: &str,
    ) -> CustomResult<EphemeralKey, errors::StorageError>;
}

#[async_trait::async_trait]
pub trait ClientSecretInterface {
    #[cfg(feature = "v2")]
    async fn create_client_secret(
        &self,
        _ek: ClientSecretTypeNew,
        _validity: i64,
    ) -> CustomResult<ClientSecretType, errors::StorageError>;

    #[cfg(feature = "v2")]
    async fn get_client_secret(
        &self,
        _key: &str,
    ) -> CustomResult<ClientSecretType, errors::StorageError>;

    #[cfg(feature = "v2")]
    async fn delete_client_secret(
        &self,
        _id: &str,
    ) -> CustomResult<ClientSecretType, errors::StorageError>;
}

mod storage {
    use common_utils::date_time;
    #[cfg(feature = "v2")]
    use common_utils::id_type;
    use error_stack::ResultExt;
    #[cfg(feature = "v2")]
    use masking::PeekInterface;
    #[cfg(feature = "v2")]
    use redis_interface::errors::RedisError;
    use redis_interface::HsetnxReply;
    use router_env::{instrument, tracing};
    use storage_impl::redis::kv_store::RedisConnInterface;
    use time::ext::NumericalDuration;

    use super::{ClientSecretInterface, EphemeralKeyInterface};
    #[cfg(feature = "v2")]
    use crate::types::storage::ephemeral_key::{ClientSecretType, ClientSecretTypeNew};
    use crate::{
        core::errors::{self, CustomResult},
        services::Store,
        types::storage::ephemeral_key::{EphemeralKey, EphemeralKeyNew},
    };

    #[async_trait::async_trait]
    impl EphemeralKeyInterface for Store {
        #[cfg(feature = "v1")]
        #[instrument(skip_all)]
        async fn create_ephemeral_key(
            &self,
            new: EphemeralKeyNew,
            validity: i64,
        ) -> CustomResult<EphemeralKey, errors::StorageError> {
            let secret_key = format!("epkey_{}", &new.secret);
            let id_key = format!("epkey_{}", &new.id);

            let created_at = date_time::now();
            let expires = created_at.saturating_add(validity.hours());
            let created_ek = EphemeralKey {
                id: new.id,
                created_at: created_at.assume_utc().unix_timestamp(),
                expires: expires.assume_utc().unix_timestamp(),
                customer_id: new.customer_id,
                merchant_id: new.merchant_id,
                secret: new.secret,
            };

            match self
                .get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .serialize_and_set_multiple_hash_field_if_not_exist(
                    &[
                        (&secret_key.as_str().into(), &created_ek),
                        (&id_key.as_str().into(), &created_ek),
                    ],
                    "ephkey",
                    None,
                )
                .await
            {
                Ok(v) if v.contains(&HsetnxReply::KeyNotSet) => {
                    Err(errors::StorageError::DuplicateValue {
                        entity: "ephemeral key",
                        key: None,
                    }
                    .into())
                }
                Ok(_) => {
                    let expire_at = expires.assume_utc().unix_timestamp();
                    self.get_redis_conn()
                        .map_err(Into::<errors::StorageError>::into)?
                        .set_expire_at(&secret_key.into(), expire_at)
                        .await
                        .change_context(errors::StorageError::KVError)?;
                    self.get_redis_conn()
                        .map_err(Into::<errors::StorageError>::into)?
                        .set_expire_at(&id_key.into(), expire_at)
                        .await
                        .change_context(errors::StorageError::KVError)?;
                    Ok(created_ek)
                }
                Err(er) => Err(er).change_context(errors::StorageError::KVError),
            }
        }

        #[cfg(feature = "v1")]
        #[instrument(skip_all)]
        async fn get_ephemeral_key(
            &self,
            key: &str,
        ) -> CustomResult<EphemeralKey, errors::StorageError> {
            let key = format!("epkey_{key}");
            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKey")
                .await
                .change_context(errors::StorageError::KVError)
        }

        #[cfg(feature = "v1")]
        async fn delete_ephemeral_key(
            &self,
            id: &str,
        ) -> CustomResult<EphemeralKey, errors::StorageError> {
            let ek = self.get_ephemeral_key(id).await?;

            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .delete_key(&format!("epkey_{}", &ek.id).into())
                .await
                .change_context(errors::StorageError::KVError)?;

            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .delete_key(&format!("epkey_{}", &ek.secret).into())
                .await
                .change_context(errors::StorageError::KVError)?;
            Ok(ek)
        }
    }

    #[async_trait::async_trait]
    impl ClientSecretInterface for Store {
        #[cfg(feature = "v2")]
        #[instrument(skip_all)]
        async fn create_client_secret(
            &self,
            new: ClientSecretTypeNew,
            validity: i64,
        ) -> CustomResult<ClientSecretType, errors::StorageError> {
            let created_at = date_time::now();
            let expires = created_at.saturating_add(validity.hours());
            let id_key = new.id.generate_redis_key();

            let created_client_secret = ClientSecretType {
                id: new.id,
                created_at,
                expires,
                merchant_id: new.merchant_id,
                secret: new.secret,
                resource_id: new.resource_id,
            };
            let secret_key = created_client_secret.generate_secret_key();

            match self
                .get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .serialize_and_set_multiple_hash_field_if_not_exist(
                    &[
                        (&secret_key.as_str().into(), &created_client_secret),
                        (&id_key.as_str().into(), &created_client_secret),
                    ],
                    "csh",
                    None,
                )
                .await
            {
                Ok(v) if v.contains(&HsetnxReply::KeyNotSet) => {
                    Err(errors::StorageError::DuplicateValue {
                        entity: "ephemeral key",
                        key: None,
                    }
                    .into())
                }
                Ok(_) => {
                    let expire_at = expires.assume_utc().unix_timestamp();
                    self.get_redis_conn()
                        .map_err(Into::<errors::StorageError>::into)?
                        .set_expire_at(&secret_key.into(), expire_at)
                        .await
                        .change_context(errors::StorageError::KVError)?;
                    self.get_redis_conn()
                        .map_err(Into::<errors::StorageError>::into)?
                        .set_expire_at(&id_key.into(), expire_at)
                        .await
                        .change_context(errors::StorageError::KVError)?;
                    Ok(created_client_secret)
                }
                Err(er) => Err(er).change_context(errors::StorageError::KVError),
            }
        }

        #[cfg(feature = "v2")]
        #[instrument(skip_all)]
        async fn get_client_secret(
            &self,
            key: &str,
        ) -> CustomResult<ClientSecretType, errors::StorageError> {
            let key = format!("cs_{key}");
            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .get_hash_field_and_deserialize(&key.into(), "csh", "ClientSecretType")
                .await
                .change_context(errors::StorageError::KVError)
        }

        #[cfg(feature = "v2")]
        async fn delete_client_secret(
            &self,
            id: &str,
        ) -> CustomResult<ClientSecretType, errors::StorageError> {
            let client_secret = self.get_client_secret(id).await?;
            let redis_id_key = client_secret.id.generate_redis_key();
            let secret_key = client_secret.generate_secret_key();

            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .delete_key(&redis_id_key.as_str().into())
                .await
                .map_err(|err| match err.current_context() {
                    RedisError::NotFound => {
                        err.change_context(errors::StorageError::ValueNotFound(redis_id_key))
                    }
                    _ => err.change_context(errors::StorageError::KVError),
                })?;

            self.get_redis_conn()
                .map_err(Into::<errors::StorageError>::into)?
                .delete_key(&secret_key.as_str().into())
                .await
                .map_err(|err| match err.current_context() {
                    RedisError::NotFound => {
                        err.change_context(errors::StorageError::ValueNotFound(secret_key))
                    }
                    _ => err.change_context(errors::StorageError::KVError),
                })?;
            Ok(client_secret)
        }
    }
}

#[async_trait::async_trait]
impl EphemeralKeyInterface for MockDb {
    #[cfg(feature = "v1")]
    async fn create_ephemeral_key(
        &self,
        ek: EphemeralKeyNew,
        validity: i64,
    ) -> CustomResult<EphemeralKey, errors::StorageError> {
        let mut ephemeral_keys = self.ephemeral_keys.lock().await;
        let created_at = common_utils::date_time::now();
        let expires = created_at.saturating_add(validity.hours());

        let ephemeral_key = EphemeralKey {
            id: ek.id,
            merchant_id: ek.merchant_id,
            customer_id: ek.customer_id,
            created_at: created_at.assume_utc().unix_timestamp(),
            expires: expires.assume_utc().unix_timestamp(),
            secret: ek.secret,
        };
        ephemeral_keys.push(ephemeral_key.clone());
        Ok(ephemeral_key)
    }

    #[cfg(feature = "v1")]
    async fn get_ephemeral_key(
        &self,
        key: &str,
    ) -> CustomResult<EphemeralKey, errors::StorageError> {
        match self
            .ephemeral_keys
            .lock()
            .await
            .iter()
            .find(|ephemeral_key| ephemeral_key.secret.eq(key))
        {
            Some(ephemeral_key) => Ok(ephemeral_key.clone()),
            None => Err(
                errors::StorageError::ValueNotFound("ephemeral key not found".to_string()).into(),
            ),
        }
    }

    #[cfg(feature = "v1")]
    async fn delete_ephemeral_key(
        &self,
        id: &str,
    ) -> CustomResult<EphemeralKey, errors::StorageError> {
        let mut ephemeral_keys = self.ephemeral_keys.lock().await;
        if let Some(pos) = ephemeral_keys.iter().position(|x| (*x.id).eq(id)) {
            let ek = ephemeral_keys.remove(pos);
            Ok(ek)
        } else {
            return Err(
                errors::StorageError::ValueNotFound("ephemeral key not found".to_string()).into(),
            );
        }
    }
}

#[async_trait::async_trait]
impl ClientSecretInterface for MockDb {
    #[cfg(feature = "v2")]
    async fn create_client_secret(
        &self,
        new: ClientSecretTypeNew,
        validity: i64,
    ) -> CustomResult<ClientSecretType, errors::StorageError> {
        todo!()
    }

    #[cfg(feature = "v2")]
    async fn get_client_secret(
        &self,
        key: &str,
    ) -> CustomResult<ClientSecretType, errors::StorageError> {
        todo!()
    }

    #[cfg(feature = "v2")]
    async fn delete_client_secret(
        &self,
        id: &str,
    ) -> CustomResult<ClientSecretType, errors::StorageError> {
        todo!()
    }
}


pub fn get_connector_transaction_id(
        &self,
    ) -> Result<String, error_stack::Report<ApiErrorResponse>> {
        match self {
            Self::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(txn_id),
                ..
            } => Ok(txn_id.to_string()),
            _ => Err(ApiErrorResponse::MissingRequiredField {
                field_name: "ConnectorTransactionId",
            }
            .into()),
        }
    }


pub struct RuleMigrationQuery {
    pub profile_id: common_utils::id_type::ProfileId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
}


pub async fn api_key_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::MerchantId>,
    json_payload: web::Json<api_types::CreateApiKeyRequest>,
) -> impl Responder {
    let flow = Flow::ApiKeyCreate;
    let payload = json_payload.into_inner();
    let merchant_id = path.into_inner();

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth_data, payload, _| async {
            api_keys::create_api_key(state, payload, auth_data.key_store).await
        },
        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 api::PaymentCapture for Worldpay {}

impl api::PaymentCapture for for Worldpay


OpenAPI Block Path: components.schemas.DecisionEngineEliminationData
{
  "type": "object",
  "required": [
    "threshold"
  ],
  "properties": {
    "threshold": {
      "type": "number",
      "format": "double"
    }
  }
}


impl api::PaymentSync for Santander {}


pub async fn insert_entry_into_blocklist(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    to_block: api_blocklist::AddToBlocklistRequest,
) -> RouterResult<api_blocklist::AddToBlocklistResponse> {
    let blocklist_entry = match &to_block {
        api_blocklist::AddToBlocklistRequest::CardBin(bin) => {
            validate_card_bin(bin)?;
            duplicate_check_insert_bin(
                bin,
                state,
                merchant_id,
                common_enums::BlocklistDataKind::CardBin,
            )
            .await?
        }

        api_blocklist::AddToBlocklistRequest::ExtendedCardBin(bin) => {
            validate_extended_card_bin(bin)?;
            duplicate_check_insert_bin(
                bin,
                state,
                merchant_id,
                common_enums::BlocklistDataKind::ExtendedCardBin,
            )
            .await?
        }

        api_blocklist::AddToBlocklistRequest::Fingerprint(fingerprint_id) => {
            let blocklist_entry_result = state
                .store
                .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint_id)
                .await;

            match blocklist_entry_result {
                Ok(_) => {
                    return Err(errors::ApiErrorResponse::PreconditionFailed {
                        message: "data associated with the given fingerprint is already blocked"
                            .to_string(),
                    }
                    .into());
                }

                // if it is a db not found error, we can proceed as normal
                Err(inner) if inner.current_context().is_db_not_found() => {}

                err @ Err(_) => {
                    err.change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("error fetching blocklist entry from table")?;
                }
            }

            state
                .store
                .insert_blocklist_entry(storage::BlocklistNew {
                    merchant_id: merchant_id.to_owned(),
                    fingerprint_id: fingerprint_id.clone(),
                    data_kind: api_models::enums::enums::BlocklistDataKind::PaymentMethod,
                    metadata: None,
                    created_at: common_utils::date_time::now(),
                })
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("failed to add fingerprint to blocklist")?
        }
    };
    Ok(blocklist_entry.foreign_into())
}


impl EmailClient for SmtpServer {
    type RichText = String;
    fn convert_to_rich_text(
        &self,
        intermediate_string: IntermediateString,
    ) -> CustomResult<Self::RichText, EmailError> {
        Ok(intermediate_string.into_inner())
    }

    async fn send_email(
        &self,
        recipient: pii::Email,
        subject: String,
        body: Self::RichText,
        _proxy_url: Option<&String>,
    ) -> EmailResult<()> {
        // Create a client every time when the email is being sent
        let email_client =
            Self::create_client(self).change_context(EmailError::EmailSendingFailure)?;

        let email = Message::builder()
            .to(Self::to_mail_box(recipient.peek().to_string())?)
            .from(Self::to_mail_box(self.sender.clone())?)
            .subject(subject)
            .header(ContentType::TEXT_HTML)
            .body(body)
            .map_err(SmtpError::MessageBuildingFailed)
            .change_context(EmailError::EmailSendingFailure)?;

        email_client
            .send(&email)
            .map_err(SmtpError::SendingFailure)
            .change_context(EmailError::EmailSendingFailure)?;
        Ok(())
    }
}


impl Responder {
        // safety: This shouldn't panic owing to the data type
        #[allow(clippy::expect_used)]
        let payload = json_payload
            .into_inner()
            .to_vec()
            .pop()
            .expect("Couldn't get GetAuthEventMetricRequest");
        let flow = AnalyticsFlow::GetAuthMetrics;
        Box::pin(api::server_wrap(
            flow,
            state,
            &req,
            payload,
            |state, auth: AuthenticationData, req, _| async move {
                let org_id = auth.merchant_account.get_org_id();
                let merchant_id = auth.merchant_account.get_id();
                let auth: AuthInfo = AuthInfo::MerchantLevel {
                    org_id: org_id.clone(),
                    merchant_ids: vec![merchant_id.clone()],
                };

                analytics::auth_events::get_metrics(&state.pool, &auth, req)
                    .await
                    .map(ApplicationResponse::Json)
            },
            &auth::JWTAuth {
                permission: Permission::MerchantAnalyticsRead,
            },
            api_locking::LockAction::NotApplicable,
        ))
        .await
    }


impl api::Refund for Tsys {}


OpenAPI Block Path: components.schemas.GpayAllowedPaymentMethods
{
  "type": "object",
  "required": [
    "type",
    "parameters",
    "tokenization_specification"
  ],
  "properties": {
    "type": {
      "type": "string",
      "description": "The type of payment method"
    },
    "parameters": {
      "$ref": "#/components/schemas/GpayAllowedMethodsParameters"
    },
    "tokenization_specification": {
      "$ref": "#/components/schemas/GpayTokenizationSpecification"
    }
  }
}


pub trait PayoutIndividualDetailsExt {
    type Error;
    fn get_external_account_account_holder_type(&self) -> Result<String, Self::Error>;
}


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

use std::str::FromStr;

use error_stack::ResultExt;
use pm_auth::{
    connector::plaid,
    types::{
        self as pm_auth_types,
        api::{BoxedPaymentAuthConnector, PaymentAuthConnectorData},
    },
};

use crate::core::{
    errors::{self, ApiErrorResponse},
    pm_auth::helpers::PaymentAuthConnectorDataExt,
};

impl PaymentAuthConnectorDataExt for PaymentAuthConnectorData {
    fn get_connector_by_name(name: &str) -> errors::CustomResult<Self, ApiErrorResponse> {
        let connector_name = pm_auth_types::PaymentMethodAuthConnectors::from_str(name)
            .change_context(ApiErrorResponse::IncorrectConnectorNameGiven)
            .attach_printable_lazy(|| {
                format!("unable to parse connector: {:?}", name.to_string())
            })?;
        let connector = Self::convert_connector(connector_name.clone())?;
        Ok(Self {
            connector,
            connector_name,
        })
    }
    fn convert_connector(
        connector_name: pm_auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<BoxedPaymentAuthConnector, ApiErrorResponse> {
        match connector_name {
            pm_auth_types::PaymentMethodAuthConnectors::Plaid => Ok(Box::new(&plaid::Plaid)),
        }
    }
}


impl api::UasAuthentication for Juspaythreedsserver {}

impl api::UasAuthentication for for Juspaythreedsserver


impl utils::MultipleCaptureSyncResponse for HelcimPaymentsResponse {
//     fn get_connector_capture_id(&self) -> String {
//         self.transaction_id.to_string()
//     }

//     fn get_capture_attempt_status(&self) -> diesel_models::enums::AttemptStatus {
//         enums::AttemptStatus::from(self.to_owned())
//     }

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

//     fn get_amount_captured(&self) -> Option<i64> {
//         Some(self.amount)
//     }
//     fn get_connector_reference_id(&self) -> Option<String> {
//         None
//     }
// }

impl utils::MultipleCaptureSyncResponse for for HelcimPaymentsResponse


impl ConnectorValidation for Mpgs {
    fn validate_mandate_payment(
        &self,
        _pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        match pm_data {
            PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented(
                "validate_mandate_payment does not support cards".to_string(),
            )
            .into()),
            _ => Ok(()),
        }
    }

    fn validate_psync_reference_id(
        &self,
        _data: &PaymentsSyncData,
        _is_three_ds: bool,
        _status: enums::AttemptStatus,
        _connector_meta_data: Option<common_utils::pii::SecretSerdeValue>,
    ) -> CustomResult<(), errors::ConnectorError> {
        Ok(())
    }
}


pub struct Fulfillments {
    pub shipment_id: String,
    pub products: Option<Vec<Product>>,
    pub destination: Destination,
    pub fulfillment_method: Option<String>,
    pub carrier: Option<String>,
    pub shipment_status: Option<String>,
    pub tracking_urls: Option<Vec<String>>,
    pub tracking_numbers: Option<Vec<String>>,
    pub shipped_at: Option<String>,
}


pub struct ProfileAcquirer;


impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
    type PaymentFlowResponse = router_types::RedirectPaymentFlowResponse;

    #[allow(clippy::too_many_arguments)]
    async fn call_payment_flow(
        &self,
        state: &SessionState,
        req_state: ReqState,
        merchant_context: domain::MerchantContext,
        req: PaymentsRedirectResponseData,
        connector_action: CallConnectorAction,
        _connector: String,
        _payment_id: id_type::PaymentId,
    ) -> RouterResult<Self::PaymentFlowResponse> {
        let key_manager_state = &state.into();
        let payment_confirm_req = api::PaymentsRequest {
            payment_id: Some(req.resource_id.clone()),
            merchant_id: req.merchant_id.clone(),
            merchant_connector_details: req.creds_identifier.map(|creds_id| {
                api::MerchantConnectorDetailsWrap {
                    creds_identifier: creds_id,
                    encoded_data: None,
                }
            }),
            feature_metadata: Some(api_models::payments::FeatureMetadata {
                redirect_response: Some(api_models::payments::RedirectResponse {
                    param: req.param.map(Secret::new),
                    json_payload: Some(req.json_payload.unwrap_or(serde_json::json!({})).into()),
                }),
                search_tags: None,
                apple_pay_recurring_details: None,
            }),
            ..Default::default()
        };
        let response = Box::pin(payments_core::<
            api::CompleteAuthorize,
            api::PaymentsResponse,
            _,
            _,
            _,
            _,
        >(
            state.clone(),
            req_state,
            merchant_context.clone(),
            None,
            operations::payment_complete_authorize::CompleteAuthorize,
            payment_confirm_req,
            services::api::AuthFlow::Merchant,
            connector_action,
            None,
            HeaderPayload::default(),
        ))
        .await?;
        let payments_response = match response {
            services::ApplicationResponse::Json(response) => Ok(response),
            services::ApplicationResponse::JsonWithHeaders((response, _)) => Ok(response),
            _ => Err(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to get the response in json"),
        }?;
        let profile_id = payments_response
            .profile_id
            .as_ref()
            .get_required_value("profile_id")?;
        let business_profile = state
            .store
            .find_business_profile_by_profile_id(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;
        Ok(router_types::RedirectPaymentFlowResponse {
            payments_response,
            business_profile,
        })
    }

    fn get_payment_action(&self) -> services::PaymentAction {
        services::PaymentAction::CompleteAuthorize
    }

    fn generate_response(
        &self,
        payment_flow_response: &Self::PaymentFlowResponse,
        payment_id: id_type::PaymentId,
        connector: String,
    ) -> RouterResult<services::ApplicationResponse<api::RedirectionResponse>> {
        let payments_response = &payment_flow_response.payments_response;
        // There might be multiple redirections needed for some flows
        // If the status is requires customer action, then send the startpay url again
        // The redirection data must have been provided and updated by the connector
        let redirection_response = match payments_response.status {
            enums::IntentStatus::RequiresCustomerAction => {
                let startpay_url = payments_response
                    .next_action
                    .clone()
                    .and_then(|next_action_data| match next_action_data {
                        api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url),
                        api_models::payments::NextActionData::RedirectInsidePopup{popup_url, ..} => Some(popup_url),
                        api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None,
                        api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None,
                        api_models::payments::NextActionData::QrCodeInformation{..} => None,
                        api_models::payments::NextActionData::FetchQrCodeInformation{..} => None,
                        api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None,
                        api_models::payments::NextActionData::WaitScreenInformation{..} => None,
                        api_models::payments::NextActionData::ThreeDsInvoke{..} => None,
                        api_models::payments::NextActionData::InvokeSdkClient{..} => None,
                        api_models::payments::NextActionData::CollectOtp{ .. } => None,
                        api_models::payments::NextActionData::InvokeHiddenIframe{ .. } => None,
                    })
                    .ok_or(errors::ApiErrorResponse::InternalServerError)

                    .attach_printable(
                        "did not receive redirect to url when status is requires customer action",
                    )?;
                Ok(api::RedirectionResponse {
                    return_url: String::new(),
                    params: vec![],
                    return_url_with_query_params: startpay_url,
                    http_method: "GET".to_string(),
                    headers: vec![],
                })
            }
            // If the status is terminal status, then redirect to merchant return url to provide status
            enums::IntentStatus::Succeeded
            | enums::IntentStatus::Failed
            | enums::IntentStatus::Cancelled | enums::IntentStatus::RequiresCapture| enums::IntentStatus::Processing=> helpers::get_handle_response_url(
                payment_id,
                &payment_flow_response.business_profile,
                payments_response,
                connector,
            ),
            _ => Err(errors::ApiErrorResponse::InternalServerError).attach_printable_lazy(|| format!("Could not proceed with payment as payment status {} cannot be handled during redirection",payments_response.status))?
        }?;
        if payments_response
            .is_iframe_redirection_enabled
            .unwrap_or(false)
        {
            // html script to check if inside iframe, then send post message to parent for redirection else redirect self to return_url
            let html = core_utils::get_html_redirect_response_popup(
                redirection_response.return_url_with_query_params,
            )?;
            Ok(services::ApplicationResponse::Form(Box::new(
                services::RedirectionFormData {
                    redirect_form: services::RedirectForm::Html { html_data: html },
                    payment_method_data: None,
                    amount: payments_response.amount.to_string(),
                    currency: payments_response.currency.clone(),
                },
            )))
        } else {
            Ok(services::ApplicationResponse::JsonForRedirection(
                redirection_response,
            ))
        }
    }
}

impl PaymentRedirectFlow for for PaymentRedirectCompleteAuthorize


pub async fn accept_invite_from_email(
    state: web::Data<AppState>,
    req: HttpRequest,
    payload: web::Json<user_api::AcceptInviteFromEmailRequest>,
) -> HttpResponse {
    let flow = Flow::AcceptInviteFromEmail;
    Box::pin(api::server_wrap(
        flow.clone(),
        state,
        &req,
        payload.into_inner(),
        |state, user, req_payload, _| {
            user_core::accept_invite_from_email_token_only_flow(state, user, req_payload)
        },
        &auth::SinglePurposeJWTAuth(TokenPurpose::AcceptInvitationFromEmail),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


impl api::PaymentCapture for Tokenio {}

impl api::PaymentCapture for for Tokenio


impl ConnectorSpecifications for HyperswitchVault {}

impl ConnectorSpecifications for for HyperswitchVault


pub struct ExternalVaultInsertFlow;


pub fn get_scope(&self) -> RoleScope {
        self.scope
    }


Documentation: README.md
# Type: Doc File

Composable Open-Source Payments Infrastructure

📁 Table of Contents

- [What Can I Do with Hyperswitch?](#-what-can-i-do-with-hyperswitch)
- [Quickstart (Local Setup)](#-quickstart-local-setup)
- [Cloud Deployment](#cloud-deployment)
- [Hosted Sandbox (No Setup Required)](#hosted-sandbox-no-setup-required)
- [Why Hyperswitch?](#-why-hyperswitch)
- [Architectural Overview](#architectural-overview)
- [Our Vision](#our-vision)
- [Community & Contributions](#community--contributions)
- [Feature Requests & Bugs](#feature-requests--bugs)
- [Versioning](#versioning)
- [License](#copyright-and-license)
- [Team Behind Hyperswitch](#team-behind-hyperswitch)

 What Can I Do with Hyperswitch?

Hyperswitch offers a modular, open-source payments infrastructure designed for flexibility and control. Apart from our Payment Suite offering, this solution allows businesses to pick and integrate only the modules they need on top of their existing payment stack — without unnecessary complexity or vendor lock-in.

Each module is independent and purpose-built to optimize different aspects of payment processing.

 Learn More About The Payment Modules

- **Cost Observability**
  Advanced observability tools to audit, monitor, and optimize your payment costs. Detect hidden fees, downgrades, and penalties with self-serve dashboards and actionable insights.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/ai-powered-cost-observability)_

- **Revenue Recovery**
  Combat passive churn with intelligent retry strategies tuned by card bin, region, method, and more. Offers fine-grained control over retry algorithms, penalty budgets, and recovery transparency.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/revenue-recovery)_

- **Vault**
  A PCI-compliant vault service to store cards, tokens, wallets, and bank credentials. Provides a unified, secure, and reusable store of customer-linked payment methods.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/vault)_

- **Intelligent Routing**
  Route each transaction to the PSP with the highest predicted auth rate. Reduce retries, avoid downtime, and minimize latency while maximizing first attempt success.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/intelligent-routing)_

- **Reconciliation**
  Automate 2-way and 3-way reconciliation with backdated support, staggered scheduling, and customizable outputs. Reduces manual ops effort and increases audit confidence.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/reconciliation)_

- **Alternate Payment Methods**
  Drop-in widgets for PayPal, Apple Pay, Google Pay, Samsung Pay, Pay by Bank, and BNPL providers like Klarna. Maximizes conversions with seamless one-click checkout.
  _[Read more](https://docs.hyperswitch.io/about-hyperswitch/payments-modules/enable-alternate-payment-method-widgets)_

## Quickstart

 Local Setup via Docker

```bash
# One-click local setup

git clone --depth 1 --branch latest https://github.com/juspay/hyperswitch

cd hyperswitch

scripts/setup.sh
```

  This script:

  - Detects Docker/Podman
  - Offers multiple deployment profiles:
    - **Standard**: App server + Control Center
    - **Full**: Includes monitoring + schedulers
    - **Minimal**: Standalone App server
  - Provides access links when done

  If you need further help, check out our [video tutorial](https://docs.hyperswitch.io/hyperswitch-open-source/overview/unified-local-setup-using-docker).

  👉 After setup, [configure a connector](https://docs.hyperswitch.io/hyperswitch-open-source/account-setup/using-hyperswitch-control-center#add-a-payment-processor) and [test a payment](https://docs.hyperswitch.io/hyperswitch-open-source/account-setup/test-a-payment).

Hosted Sandbox (No Setup Required)

Hyperswitch offers a fully hosted sandbox environment that requires no setup. You can explore the Control Center, configure payment connectors, and test payments directly from the UI.

   What you can do in the Hosted Sandbox

  - Access the full Control Center
  - Configure payment connectors
  - View logs, routing rules, and retry strategies
  - Try payments directly from the UI

Cloud Deployment

You can deploy to AWS, GCP, or Azure using Helm or CDK scripts. Fastest path:

Click to deploy via AWS:

  Cloud Deployment Instructions

  1. Click the AWS deployment button above to launch the stack.
  2. Follow the guided steps in the AWS Console (approx. 30–45 mins).

  ✅ This setup provisions Hyperswitch on your cloud account using CloudFormation.

  📘 For full instructions and Helm-based deployments, check out the
  Cloud Install Guide.

Architectural Overview

## Why Hyperswitch?

Hyperswitch is a commercial open-source payments stack purpose-built for scale, flexibility, and developer experience. Designed with a modular architecture, Hyperswitch lets you pick only the components you need—whether it’s routing, retries, vaulting, or observability—without vendor lock-in or bloated integrations.

Built in Rust for performance and reliability, Hyperswitch supports global payment methods (cards, wallets, BNPL, UPI, Pay by Bank), exposes smart routing and retry logic, and provides a visual workflow builder in the Control Center. Whether you're integrating a full payment suite or augmenting an existing stack with a single module, Hyperswitch meets you where you are.

“Linux for Payments” — Hyperswitch is a well-architected reference for teams who want to own their payments stack.

We believe in:

-  Embracing Payment Diversity: Innovation comes from enabling choice—across payment methods, processors, and flows.

-  Open Source by Default: Transparency drives trust and builds better, reusable software.

-  Community-Driven Development: Our roadmap is shaped by real-world use cases and contributors.

-  Systems-Level Engineering: We hold ourselves to a high bar for reliability, security, and performance.

-  Maximizing Value Creation: For developers, customers, and partners alike.

-  Community-Driven, Enterprise-Tested: Hyperswitch is built in the open with real-world feedback from developers and contributors, and maintained by Juspay, the team powering payment infrastructure for 400+ leading enterprises worldwide.

## Contributing

We welcome contributors from around the world to help build Hyperswitch. Whether you're fixing bugs, improving documentation, or adding new features, your help is appreciated.

Please read our [contributing guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md) to get started.

Join the conversation on [Slack](https://inviter.co/hyperswitch-slack) or explore open issues on [GitHub](https://github.com/juspay/hyperswitch/issues).

Feature requests & Bugs

For new product features, enhancements, roadmap discussions, or to share queries and ideas, visit our [GitHub Discussions](https://github.com/juspay/hyperswitch/discussions)

For reporting a bug, please read the issue guidelines and search for [existing and closed issues](https://github.com/juspay/hyperswitch/issues). If your problem or idea is not addressed yet, please [open a new issue](https://github.com/juspay/hyperswitch/issues/new/choose).

Versioning

Check the [CHANGELOG.md](./CHANGELOG.md) file for details.

Copyright and License

This product is licensed under the [Apache 2.0 License](LICENSE).

Team behind Hyperswitch

The core team of 150+ engineers building Hyperswitch. Keep up the great work! 🥂


impl webhooks::IncomingWebhook for Xendit {
    fn get_webhook_source_verification_signature(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let header_value = utils::get_header_key_value("X-CALLBACK-TOKEN", request.headers)?;
        Ok(header_value.into())
    }
    async fn verify_webhook_source(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
        _connector_account_details: crypto::Encryptable<masking::Secret<serde_json::Value>>,
        connector_label: &str,
    ) -> CustomResult<bool, errors::ConnectorError> {
        let connector_webhook_secrets = self
            .get_webhook_source_verification_merchant_secret(
                merchant_id,
                connector_label,
                connector_webhook_details,
            )
            .await
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
        let signature = self
            .get_webhook_source_verification_signature(request, &connector_webhook_secrets)
            .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

        let secret_key = connector_webhook_secrets.secret;
        Ok(secret_key.as_bytes() == (signature).as_bytes())
    }
    fn get_webhook_source_verification_message(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        Ok(request.body.to_vec())
    }
    fn get_webhook_object_reference_id(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let details: XenditWebhookEvent = request
            .body
            .parse_struct("XenditWebhookEvent")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        match details.event {
            XenditEventType::PaymentSucceeded
            | XenditEventType::PaymentAwaitingCapture
            | XenditEventType::PaymentFailed
            | XenditEventType::CaptureSucceeded
            | XenditEventType::CaptureFailed => {
                Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                    api_models::payments::PaymentIdType::ConnectorTransactionId(
                        details
                            .data
                            .payment_request_id
                            .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
                    ),
                ))
            }
        }
    }
    fn get_webhook_event_type(
        &self,
        request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        let body: XenditWebhookEvent = request
            .body
            .parse_struct("XenditWebhookEvent")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        match body.event {
            XenditEventType::PaymentSucceeded => Ok(IncomingWebhookEvent::PaymentIntentSuccess),
            XenditEventType::CaptureSucceeded => {
                Ok(IncomingWebhookEvent::PaymentIntentCaptureSuccess)
            }
            XenditEventType::PaymentAwaitingCapture => {
                Ok(IncomingWebhookEvent::PaymentIntentAuthorizationSuccess)
            }
            XenditEventType::PaymentFailed | XenditEventType::CaptureFailed => {
                Ok(IncomingWebhookEvent::PaymentIntentFailure)
            }
        }
    }

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

impl webhooks::IncomingWebhook for for Xendit


OpenAPI Block Path: components.schemas.RecurringPaymentIntervalUnit
{
  "type": "string",
  "enum": [
    "year",
    "month",
    "day",
    "hour",
    "minute"
  ]
}


impl api::PaymentSync for Trustpay {}


pub struct FiservemeaPaymentCardResponse {
    expiry_date: Option<FiservemeaExpiryDate>,
    bin: Option<String>,
    last4: Option<String>,
    brand: Option<String>,
}


File: crates/euclid/src/types/transformers.rs


impl api::ConnectorAccessToken for Nexixpay {}

impl api::ConnectorAccessToken for for Nexixpay


pub async fn delete_network_token_from_locker_and_token_service(
    state: &routes::SessionState,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    payment_method_id: String,
    network_token_locker_id: Option<String>,
    network_token_requestor_reference_id: String,
    merchant_context: &domain::MerchantContext,
) -> errors::RouterResult<DeleteCardResp> {
    //deleting network token from locker
    let resp = payment_methods::cards::PmCards {
        state,
        merchant_context,
    }
    .delete_card_from_locker(
        customer_id,
        merchant_id,
        network_token_locker_id
            .as_ref()
            .unwrap_or(&payment_method_id),
    )
    .await?;
    if let Some(tokenization_service) = &state.conf.network_tokenization_service {
        let delete_token_resp = record_operation_time(
            async {
                delete_network_token_from_tokenization_service(
                    state,
                    network_token_requestor_reference_id,
                    customer_id,
                    tokenization_service.get_inner(),
                )
                .await
            },
            &metrics::DELETE_NETWORK_TOKEN_TIME,
            &[],
        )
        .await;
        match delete_token_resp {
            Ok(_) => logger::info!("Token From Tokenization Service deleted Successfully!"),
            Err(e) => {
                logger::error!(error=?e, "Error while deleting Token From Tokenization Service!")
            }
        };
    };

    Ok(resp)
}


pub fn get_recon_acl(&self) -> HashMap<Resource, ReconPermissionScope> {
        let mut acl: HashMap<Resource, ReconPermissionScope> = HashMap::new();
        let mut recon_resources = RECON_OPS.to_vec();
        recon_resources.extend(RECON_REPORTS);
        let recon_internal_resources = [Resource::ReconToken];
        self.get_permission_groups()
            .iter()
            .for_each(|permission_group| {
                permission_group.resources().iter().for_each(|resource| {
                    if recon_resources.contains(resource)
                        && !recon_internal_resources.contains(resource)
                    {
                        let scope = match resource {
                            Resource::ReconAndSettlementAnalytics => ReconPermissionScope::Read,
                            _ => ReconPermissionScope::from(permission_group.scope()),
                        };
                        acl.entry(*resource)
                            .and_modify(|curr_scope| {
                                *curr_scope = if (*curr_scope) < scope {
                                    scope
                                } else {
                                    *curr_scope
                                }
                            })
                            .or_insert(scope);
                    }
                })
            });
        acl
    }


pub fn set_log_event(&mut self, log_event: bool) {
        self.log_event = log_event;
    }


pub struct RefundResponse {
    pub id: String,
    pub amount: i64,
    pub currency: String,
    pub charge: String,
    pub status: Shift4RefundStatus,
}


impl Deref for AlwaysRequestExtendedAuthorization {
        type Target = bool;

        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }

impl Deref for for AlwaysRequestExtendedAuthorization


pub struct DisputeListGetConstraints {
    /// The identifier for dispute
    pub dispute_id: Option<String>,
    /// The payment_id against which dispute is raised
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    /// Limit on the number of objects to return
    pub limit: Option<u32>,
    /// The starting point within a list of object
    pub offset: Option<u32>,
    /// The identifier for business profile
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<common_utils::id_type::ProfileId>,
    /// The comma separated list of status of the disputes
    #[serde(default, deserialize_with = "parse_comma_separated")]
    pub dispute_status: Option<Vec<DisputeStatus>>,
    /// The comma separated list of stages of the disputes
    #[serde(default, deserialize_with = "parse_comma_separated")]
    pub dispute_stage: Option<Vec<DisputeStage>>,
    /// Reason for the dispute
    pub reason: Option<String>,
    /// The comma separated list of connectors linked to disputes
    #[serde(default, deserialize_with = "parse_comma_separated")]
    pub connector: Option<Vec<String>>,
    /// The comma separated list of currencies of the disputes
    #[serde(default, deserialize_with = "parse_comma_separated")]
    pub currency: Option<Vec<Currency>>,
    /// The merchant connector id to filter the disputes list
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
    #[serde(flatten)]
    pub time_range: Option<TimeRange>,
}


pub struct AffirmPaymentsResponse {
    checkout_id: String,
    redirect_url: String,
}


File: crates/router/src/core/locker_migration.rs
 Public functions: 4

use ::payment_methods::controller::PaymentMethodsController;
#[cfg(feature = "v1")]
use api_models::enums as api_enums;
use api_models::locker_migration::MigrateCardResponse;
use common_utils::{errors::CustomResult, id_type};
#[cfg(feature = "v1")]
use diesel_models::enums as storage_enums;
#[cfg(feature = "v1")]
use error_stack::FutureExt;
use error_stack::ResultExt;
#[cfg(feature = "v1")]
use futures::TryFutureExt;

#[cfg(feature = "v1")]
use super::{errors::StorageErrorExt, payment_methods::cards};
use crate::{errors, routes::SessionState, services, types::domain};
#[cfg(feature = "v1")]
use crate::{services::logger, types::api};

#[cfg(feature = "v2")]
pub async fn rust_locker_migration(
    _state: SessionState,
    _merchant_id: &id_type::MerchantId,
) -> CustomResult<services::ApplicationResponse<MigrateCardResponse>, errors::ApiErrorResponse> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn rust_locker_migration(
    state: SessionState,
    merchant_id: &id_type::MerchantId,
) -> CustomResult<services::ApplicationResponse<MigrateCardResponse>, errors::ApiErrorResponse> {
    use crate::db::customers::CustomerListConstraints;

    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();
    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            key_manager_state,
            merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let merchant_account = db
        .find_merchant_account_by_merchant_id(key_manager_state, merchant_id, &key_store)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    // Handle cases where the number of customers is greater than the limit
    let constraints = CustomerListConstraints {
        limit: u16::MAX,
        offset: None,
    };

    let domain_customers = db
        .list_customers_by_merchant_id(key_manager_state, merchant_id, &key_store, constraints)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let mut customers_moved = 0;
    let mut cards_moved = 0;

    let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
        merchant_account.clone(),
        key_store.clone(),
    )));
    for customer in domain_customers {
        let result = db
            .find_payment_method_by_customer_id_merchant_id_list(
                key_manager_state,
                &key_store,
                &customer.customer_id,
                merchant_id,
                None,
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .and_then(|pm| {
                call_to_locker(
                    &state,
                    pm,
                    &customer.customer_id,
                    merchant_id,
                    &merchant_context,
                )
            })
            .await?;

        customers_moved += 1;
        cards_moved += result;
    }

    Ok(services::api::ApplicationResponse::Json(
        MigrateCardResponse {
            status_code: "200".to_string(),
            status_message: "Card migration completed".to_string(),
            customers_moved,
            cards_moved,
        },
    ))
}

#[cfg(feature = "v1")]
pub async fn call_to_locker(
    state: &SessionState,
    payment_methods: Vec<domain::PaymentMethod>,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    merchant_context: &domain::MerchantContext,
) -> CustomResult<usize, errors::ApiErrorResponse> {
    let mut cards_moved = 0;

    for pm in payment_methods.into_iter().filter(|pm| {
        matches!(
            pm.get_payment_method_type(),
            Some(storage_enums::PaymentMethod::Card)
        )
    }) {
        let card = cards::get_card_from_locker(
            state,
            customer_id,
            merchant_id,
            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
        )
        .await;

        let card = match card {
            Ok(card) => card,
            Err(err) => {
                logger::error!("Failed to fetch card from Basilisk HS locker : {:?}", err);
                continue;
            }
        };

        let card_details = api::CardDetail {
            card_number: card.card_number,
            card_exp_month: card.card_exp_month,
            card_exp_year: card.card_exp_year,
            card_holder_name: card.name_on_card,
            nick_name: card.nick_name.map(masking::Secret::new),
            card_issuing_country: None,
            card_network: None,
            card_issuer: None,
            card_type: None,
        };

        let pm_create = api::PaymentMethodCreate {
            payment_method: pm.get_payment_method_type(),
            payment_method_type: pm.get_payment_method_subtype(),
            payment_method_issuer: pm.payment_method_issuer,
            payment_method_issuer_code: pm.payment_method_issuer_code,
            card: Some(card_details.clone()),
            #[cfg(feature = "payouts")]
            wallet: None,
            #[cfg(feature = "payouts")]
            bank_transfer: None,
            metadata: pm.metadata,
            customer_id: Some(pm.customer_id),
            card_network: card.card_brand,
            client_secret: None,
            payment_method_data: None,
            billing: None,
            connector_mandate_details: None,
            network_transaction_id: None,
        };

        let add_card_result = cards::PmCards{
            state,
            merchant_context,
        }.add_card_hs(
                pm_create,
                &card_details,
                customer_id,
                api_enums::LockerChoice::HyperswitchCardVault,
                Some(pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id)),

            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable(format!(
                "Card migration failed for merchant_id: {merchant_id:?}, customer_id: {customer_id:?}, payment_method_id: {} ",
                pm.payment_method_id
            ));

        let (_add_card_rs_resp, _is_duplicate) = match add_card_result {
            Ok(output) => output,
            Err(err) => {
                logger::error!("Failed to add card to Rust locker : {:?}", err);
                continue;
            }
        };

        cards_moved += 1;

        logger::info!(
                "Card migrated for merchant_id: {merchant_id:?}, customer_id: {customer_id:?}, payment_method_id: {} ",
                pm.payment_method_id
            );
    }

    Ok(cards_moved)
}

#[cfg(feature = "v2")]
pub async fn call_to_locker(
    _state: &SessionState,
    _payment_methods: Vec<domain::PaymentMethod>,
    _customer_id: &id_type::CustomerId,
    _merchant_id: &id_type::MerchantId,
    _merchant_context: &domain::MerchantContext,
) -> CustomResult<usize, errors::ApiErrorResponse> {
    todo!()
}


pub async fn make_frm_data_and_fraud_check_operation<F, D>(
    _db: &dyn StorageInterface,
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: D,
    frm_routing_algorithm: FrmRoutingAlgorithm,
    profile_id: common_utils::id_type::ProfileId,
    frm_configs: FrmConfigsObject,
    _customer: &Option<domain::Customer>,
) -> RouterResult<FrmInfo<F, D>>
where
    F: Send + Clone,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
{
    let order_details = payment_data
        .get_payment_intent()
        .order_details
        .clone()
        .or_else(||
            // when the order_details are present within the meta_data, we need to take those to support backward compatibility
            payment_data.get_payment_intent().metadata.clone().and_then(|meta| {
                let order_details = meta.get("order_details").to_owned();
                order_details.map(|order| vec![masking::Secret::new(order.to_owned())])
            }))
        .map(|order_details_value| {
            order_details_value
                .into_iter()
                .map(|data| {
                    data.peek()
                        .to_owned()
                        .parse_value("OrderDetailsWithAmount")
                        .attach_printable("unable to parse OrderDetailsWithAmount")
                })
                .collect::<Result<Vec<_>, _>>()
                .unwrap_or_default()
        });

    let frm_connector_details = ConnectorDetailsCore {
        connector_name: frm_routing_algorithm.data,
        profile_id,
    };

    let payment_to_frm_data = PaymentToFrmData {
        amount: payment_data.get_amount(),
        payment_intent: payment_data.get_payment_intent().to_owned(),
        payment_attempt: payment_data.get_payment_attempt().to_owned(),
        merchant_account: merchant_context.get_merchant_account().to_owned(),
        address: payment_data.get_address().clone(),
        connector_details: frm_connector_details.clone(),
        order_details,
        frm_metadata: payment_data.get_payment_intent().frm_metadata.clone(),
    };

    let fraud_check_operation: operation::BoxedFraudCheckOperation<F, D> =
        fraud_check_operation_by_frm_preferred_flow_type(frm_configs.frm_preferred_flow_type);
    let frm_data = fraud_check_operation
        .to_get_tracker()?
        .get_trackers(state, payment_to_frm_data, frm_connector_details)
        .await?;
    Ok(FrmInfo {
        fraud_check_operation,
        frm_data,
        suggested_action: None,
    })
}


pub struct PayoutBatchHeader {
    sender_batch_id: String,
}


impl api::Payment for Nomupay {}


OpenAPI Block Path: components.schemas.GsmDecision
{
  "type": "string",
  "enum": [
    "retry",
    "do_default"
  ]
}


pub async fn create_de_euclid_routing_algo(
    state: &SessionState,
    routing_request: &RoutingRule,
) -> RoutingResult<String> {
    logger::debug!("decision_engine_euclid: create api call for euclid routing rule creation");

    logger::debug!(decision_engine_euclid_request=?routing_request,"decision_engine_euclid");
    let events_response = EuclidApiClient::send_decision_engine_request(
        state,
        services::Method::Post,
        "routing/create",
        Some(routing_request.clone()),
        Some(EUCLID_API_TIMEOUT),
        None,
    )
    .await?;

    let euclid_response: RoutingDictionaryRecord =
        events_response
            .response
            .ok_or(errors::RoutingError::OpenRouterError(
                "Response from decision engine API is empty".to_string(),
            ))?;

    logger::debug!(decision_engine_euclid_parsed_response=?euclid_response,"decision_engine_euclid");
    Ok(euclid_response.rule_id)
}


pub struct CompleteAuthorizeRedirectResponse {
    pub params: Option<Secret<String>>,
    pub payload: Option<pii::SecretSerdeValue>,
}


pub struct FrmMetricRow {
    pub frm_name: Option<String>,
    pub frm_status: Option<DBEnumWrapper<storage_enums::FraudCheckStatus>>,
    pub frm_transaction_type: Option<DBEnumWrapper<FrmTransactionType>>,
    pub total: Option<bigdecimal::BigDecimal>,
    pub count: Option<i64>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}


File: crates/hyperswitch_interfaces/src/connector_integration_v2.rs

//! definition of the new connector integration trait

use common_utils::{
    errors::CustomResult,
    request::{Method, Request, RequestBuilder, RequestContent},
};
use hyperswitch_domain_models::{router_data::ErrorResponse, router_data_v2::RouterDataV2};
use masking::Maskable;
use serde_json::json;

use crate::{
    api::{self, CaptureSyncMethod},
    errors,
    events::connector_api_logs::ConnectorEvent,
    metrics, types, webhooks,
};

/// ConnectorV2 trait
pub trait ConnectorV2:
    Send
    + api::refunds_v2::RefundV2
    + api::payments_v2::PaymentV2
    + api::ConnectorRedirectResponse
    + webhooks::IncomingWebhook
    + api::ConnectorAuthenticationTokenV2
    + api::ConnectorAccessTokenV2
    + api::disputes_v2::DisputeV2
    + api::files_v2::FileUploadV2
    + api::ConnectorTransactionId
    + api::PayoutsV2
    + api::ConnectorVerifyWebhookSourceV2
    + api::FraudCheckV2
    + api::ConnectorMandateRevokeV2
    + api::authentication_v2::ExternalAuthenticationV2
    + api::UnifiedAuthenticationServiceV2
    + api::revenue_recovery_v2::RevenueRecoveryV2
    + api::ExternalVaultV2
{
}
impl<
        T: api::refunds_v2::RefundV2
            + api::payments_v2::PaymentV2
            + api::ConnectorRedirectResponse
            + Send
            + webhooks::IncomingWebhook
            + api::ConnectorAuthenticationTokenV2
            + api::ConnectorAccessTokenV2
            + api::disputes_v2::DisputeV2
            + api::files_v2::FileUploadV2
            + api::ConnectorTransactionId
            + api::PayoutsV2
            + api::ConnectorVerifyWebhookSourceV2
            + api::FraudCheckV2
            + api::ConnectorMandateRevokeV2
            + api::authentication_v2::ExternalAuthenticationV2
            + api::UnifiedAuthenticationServiceV2
            + api::revenue_recovery_v2::RevenueRecoveryV2
            + api::ExternalVaultV2,
    > ConnectorV2 for T
{
}

/// Alias for Box<&'static (dyn ConnectorV2 + Sync)>
pub type BoxedConnectorV2 = Box<&'static (dyn ConnectorV2 + Sync)>;

/// alias for Box of a type that implements trait ConnectorIntegrationV2
pub type BoxedConnectorIntegrationV2<'a, Flow, ResourceCommonData, Req, Resp> =
    Box<&'a (dyn ConnectorIntegrationV2<Flow, ResourceCommonData, Req, Resp> + Send + Sync)>;

/// trait with a function that returns BoxedConnectorIntegrationV2
pub trait ConnectorIntegrationAnyV2<Flow, ResourceCommonData, Req, Resp>:
    Send + Sync + 'static
{
    /// function what returns BoxedConnectorIntegrationV2
    fn get_connector_integration_v2(
        &self,
    ) -> BoxedConnectorIntegrationV2<'_, Flow, ResourceCommonData, Req, Resp>;
}

impl<S, Flow, ResourceCommonData, Req, Resp>
    ConnectorIntegrationAnyV2<Flow, ResourceCommonData, Req, Resp> for S
where
    S: ConnectorIntegrationV2<Flow, ResourceCommonData, Req, Resp> + Send + Sync,
{
    fn get_connector_integration_v2(
        &self,
    ) -> BoxedConnectorIntegrationV2<'_, Flow, ResourceCommonData, Req, Resp> {
        Box::new(self)
    }
}

/// The new connector integration trait with an additional ResourceCommonData generic parameter
pub trait ConnectorIntegrationV2<Flow, ResourceCommonData, Req, Resp>:
    ConnectorIntegrationAnyV2<Flow, ResourceCommonData, Req, Resp> + Sync + api::ConnectorCommon
{
    /// returns a vec of tuple of header key and value
    fn get_headers(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        Ok(vec![])
    }

    /// returns content type
    fn get_content_type(&self) -> &'static str {
        mime::APPLICATION_JSON.essence_str()
    }

    /// primarily used when creating signature based on request method of payment flow
    fn get_http_method(&self) -> Method {
        Method::Post
    }

    /// returns url
    fn get_url(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<String, errors::ConnectorError> {
        metrics::UNIMPLEMENTED_FLOW
            .add(1, router_env::metric_attributes!(("connector", self.id())));
        Ok(String::new())
    }

    /// returns request body
    fn get_request_body(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Option<RequestContent>, errors::ConnectorError> {
        Ok(None)
    }

    /// returns form data
    fn get_request_form_data(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Option<reqwest::multipart::Form>, errors::ConnectorError> {
        Ok(None)
    }

    /// builds the request and returns it
    fn build_request_v2(
        &self,
        req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(self.get_http_method())
                .url(self.get_url(req)?.as_str())
                .attach_default_headers()
                .headers(self.get_headers(req)?)
                .set_optional_body(self.get_request_body(req)?)
                .add_certificate(self.get_certificate(req)?)
                .add_certificate_key(self.get_certificate_key(req)?)
                .build(),
        ))
    }

    /// accepts the raw api response and decodes it
    fn handle_response_v2(
        &self,
        data: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
        event_builder: Option<&mut ConnectorEvent>,
        _res: types::Response,
    ) -> CustomResult<RouterDataV2<Flow, ResourceCommonData, Req, Resp>, errors::ConnectorError>
    where
        Flow: Clone,
        ResourceCommonData: Clone,
        Req: Clone,
        Resp: Clone,
    {
        event_builder.map(|e| e.set_error(json!({"error": "Not Implemented"})));
        Ok(data.clone())
    }

    /// accepts the raw api error response and decodes it
    fn get_error_response_v2(
        &self,
        res: types::Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
        Ok(ErrorResponse::get_not_implemented())
    }

    /// accepts the raw 5xx error response and decodes it
    fn get_5xx_error_response(
        &self,
        res: types::Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        event_builder.map(|event| event.set_error(json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code})));
        let error_message = match res.status_code {
            500 => "internal_server_error",
            501 => "not_implemented",
            502 => "bad_gateway",
            503 => "service_unavailable",
            504 => "gateway_timeout",
            505 => "http_version_not_supported",
            506 => "variant_also_negotiates",
            507 => "insufficient_storage",
            508 => "loop_detected",
            510 => "not_extended",
            511 => "network_authentication_required",
            _ => "unknown_error",
        };
        Ok(ErrorResponse {
            code: res.status_code.to_string(),
            message: error_message.to_string(),
            reason: String::from_utf8(res.response.to_vec()).ok(),
            status_code: res.status_code,
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }

    // whenever capture sync is implemented at the connector side, this method should be overridden
    /// retunes the capture sync method
    fn get_multiple_capture_sync_method(
        &self,
    ) -> CustomResult<CaptureSyncMethod, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("multiple capture sync".into()).into())
    }

    /// returns certificate string
    fn get_certificate(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Option<masking::Secret<String>>, errors::ConnectorError> {
        Ok(None)
    }

    /// returns private key string
    fn get_certificate_key(
        &self,
        _req: &RouterDataV2<Flow, ResourceCommonData, Req, Resp>,
    ) -> CustomResult<Option<masking::Secret<String>>, errors::ConnectorError> {
        Ok(None)
    }
}


pub struct ThreeDS {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authentication_value: Option<Secret<String>>,
    pub version: ThreeDSVersion,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transaction_id: Option<String>,
    pub eci: String,
    #[serde(rename = "type")]
    pub auth_type: CustomerAuthType,
}


pub struct NordeaPaymentsConfirmErrorObject {
    /// Error message
    pub error: Option<String>,
    /// Description of the error
    pub error_description: Option<String>,
    /// Payment id of the payment, the error is associated with
    pub payment_id: Option<String>,
}