            Ok(Some(api::MandateTransactionType::NewMandateTransaction))
        }
        Some(api::MandateTransactionType::RecurringMandateTransaction) => {
            validate_recurring_mandate(req)?;
            Ok(Some(
                api::MandateTransactionType::RecurringMandateTransaction,
            ))
        }
        None => Ok(None),
    }
}

pub fn validate_recurring_details_and_token(
    recurring_details: &Option<RecurringDetails>,
    payment_token: &Option<String>,
    mandate_id: &Option<String>,
) -> CustomResult<(), errors::ApiErrorResponse> {
    utils::when(
        recurring_details.is_some() && payment_token.is_some(),
        || {
            Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                message: "Expected one out of recurring_details and payment_token but got both"
                    .into()
            }))
        },
    )?;

    utils::when(recurring_details.is_some() && mandate_id.is_some(), || {
        Err(report!(errors::ApiErrorResponse::PreconditionFailed {
            message: "Expected one out of recurring_details and mandate_id but got both".into()
        }))
    })?;

    Ok(())
}

fn validate_new_mandate_request(
    req: api::MandateValidationFields,
    is_confirm_operation: bool,
) -> RouterResult<()> {
    // We need not check for customer_id in the confirm request if it is already passed
    // in create request

    fp_utils::when(!is_confirm_operation && req.customer_id.is_none(), || {
        Err(report!(errors::ApiErrorResponse::PreconditionFailed {
            message: "`customer_id` is mandatory for mandates".into()
        }))
    })?;

    let mandate_data = req
        .mandate_data
        .clone()
        .get_required_value("mandate_data")?;

    // Only use this validation if the customer_acceptance is present
    if mandate_data
        .customer_acceptance
        .map(|inner| inner.acceptance_type == api::AcceptanceType::Online && inner.online.is_none())
        .unwrap_or(false)
    {
        Err(report!(errors::ApiErrorResponse::PreconditionFailed {
            message: "`mandate_data.customer_acceptance.online` is required when \
                      `mandate_data.customer_acceptance.acceptance_type` is `online`"
                .into()
        }))?
    }

    let mandate_details = match mandate_data.mandate_type {
        Some(api_models::payments::MandateType::SingleUse(details)) => Some(details),
        Some(api_models::payments::MandateType::MultiUse(details)) => details,
        _ => None,
    };
    mandate_details.and_then(|md| md.start_date.zip(md.end_date)).map(|(start_date, end_date)|
        utils::when (start_date >= end_date, || {
        Err(report!(errors::ApiErrorResponse::PreconditionFailed {
            message: "`mandate_data.mandate_type.{multi_use|single_use}.start_date` should be greater than  \
            `mandate_data.mandate_type.{multi_use|single_use}.end_date`"
                .into()
        }))
    })).transpose()?;

    Ok(())
}

pub fn validate_customer_id_mandatory_cases(
    has_setup_future_usage: bool,
    customer_id: Option<&id_type::CustomerId>,
) -> RouterResult<()> {
    match (has_setup_future_usage, customer_id) {
        (true, None) => Err(errors::ApiErrorResponse::PreconditionFailed {
            message: "customer_id is mandatory when setup_future_usage is given".to_string(),
        }
        .into()),
        _ => Ok(()),
    }
}

#[cfg(feature = "v1")]
pub fn create_startpay_url(
    base_url: &str,
    payment_attempt: &PaymentAttempt,
    payment_intent: &PaymentIntent,
) -> String {
    format!(
        "{}/payments/redirect/{}/{}/{}",
        base_url,
        payment_intent.get_id().get_string_repr(),
        payment_intent.merchant_id.get_string_repr(),
        payment_attempt.attempt_id
    )
}

pub fn create_redirect_url(
    router_base_url: &String,
    payment_attempt: &PaymentAttempt,
    connector_name: impl std::fmt::Display,
    creds_identifier: Option<&str>,
) -> String {
    let creds_identifier_path = creds_identifier.map_or_else(String::new, |cd| format!("/{cd}"));
    format!(
        "{}/payments/{}/{}/redirect/response/{}",
        router_base_url,
        payment_attempt.payment_id.get_string_repr(),
        payment_attempt.merchant_id.get_string_repr(),
        connector_name,
    ) + creds_identifier_path.as_ref()
}

pub fn create_authentication_url(
    router_base_url: &str,
    payment_attempt: &PaymentAttempt,
) -> String {
    format!(
        "{router_base_url}/payments/{}/3ds/authentication",
        payment_attempt.payment_id.get_string_repr()
    )
}

pub fn create_authorize_url(
    router_base_url: &str,
    payment_attempt: &PaymentAttempt,
    connector_name: impl std::fmt::Display,
) -> String {
    format!(
        "{}/payments/{}/{}/authorize/{}",
        router_base_url,
        payment_attempt.payment_id.get_string_repr(),
        payment_attempt.merchant_id.get_string_repr(),
        connector_name
    )
}

pub fn create_webhook_url(
    router_base_url: &str,
    merchant_id: &id_type::MerchantId,
    merchant_connector_id_or_connector_name: &str,
) -> String {
    format!(
        "{}/webhooks/{}/{}",
        router_base_url,
        merchant_id.get_string_repr(),
        merchant_connector_id_or_connector_name,
    )
}

pub fn create_complete_authorize_url(
    router_base_url: &String,
    payment_attempt: &PaymentAttempt,
    connector_name: impl std::fmt::Display,
    creds_identifier: Option<&str>,
) -> String {
    let creds_identifier = creds_identifier.map_or_else(String::new, |creds_identifier| {
        format!("/{creds_identifier}")
    });
    format!(
        "{}/payments/{}/{}/redirect/complete/{}{}",
        router_base_url,
        payment_attempt.payment_id.get_string_repr(),
        payment_attempt.merchant_id.get_string_repr(),
        connector_name,
        creds_identifier
    )
}

fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult<()> {
    let recurring_details = req
        .recurring_details
        .get_required_value("recurring_details")?;

    match recurring_details {
        RecurringDetails::ProcessorPaymentToken(_)
        | RecurringDetails::NetworkTransactionIdAndCardDetails(_) => Ok(()),
        _ => {
            req.customer_id.check_value_present("customer_id")?;

            let confirm = req.confirm.get_required_value("confirm")?;
            if !confirm {
                Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                    message: "`confirm` must be `true` for mandates".into()
                }))?
            }

            let off_session = req.off_session.get_required_value("off_session")?;
            if !off_session {
                Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                    message: "`off_session` should be `true` for mandates".into()
                }))?
            }
            Ok(())
        }
    }
}

pub fn verify_mandate_details(
    request_amount: MinorUnit,
    request_currency: api_enums::Currency,
    mandate: storage::Mandate,
) -> RouterResult<()> {
    match mandate.mandate_type {
        storage_enums::MandateType::SingleUse => utils::when(
            mandate
                .mandate_amount
                .map(|mandate_amount| request_amount.get_amount_as_i64() > mandate_amount)
                .unwrap_or(true),
            || {
                Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
                    reason: "request amount is greater than mandate amount".into()
                }))
            },
        ),
        storage::enums::MandateType::MultiUse => utils::when(
            mandate
                .mandate_amount
                .map(|mandate_amount| {
                    (mandate.amount_captured.unwrap_or(0) + request_amount.get_amount_as_i64())
                        > mandate_amount
                })
                .unwrap_or(false),
            || {
                Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
                    reason: "request amount is greater than mandate amount".into()
                }))
            },
        ),
    }?;
    utils::when(
        mandate
            .mandate_currency
            .map(|mandate_currency| mandate_currency != request_currency)
            .unwrap_or(false),
        || {
            Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
                reason: "cross currency mandates not supported".into()
            }))
        },
    )
}

pub fn verify_mandate_details_for_recurring_payments(
    mandate_merchant_id: &id_type::MerchantId,
    merchant_id: &id_type::MerchantId,
    mandate_customer_id: &id_type::CustomerId,
    customer_id: &id_type::CustomerId,
) -> RouterResult<()> {
    if mandate_merchant_id != merchant_id {
        Err(report!(errors::ApiErrorResponse::MandateNotFound))?
    }
    if mandate_customer_id != customer_id {
        Err(report!(errors::ApiErrorResponse::PreconditionFailed {
            message: "customer_id must match mandate customer_id".into()
        }))?
    }

    Ok(())
}

#[instrument(skip_all)]
pub fn payment_attempt_status_fsm(
    payment_method_data: Option<&api::payments::PaymentMethodData>,
    confirm: Option<bool>,
) -> storage_enums::AttemptStatus {
    match payment_method_data {
        Some(_) => match confirm {
            Some(true) => storage_enums::AttemptStatus::PaymentMethodAwaited,
            _ => storage_enums::AttemptStatus::ConfirmationAwaited,
        },
        None => storage_enums::AttemptStatus::PaymentMethodAwaited,
    }
}

pub fn payment_intent_status_fsm(
    payment_method_data: Option<&api::PaymentMethodData>,
    confirm: Option<bool>,
) -> storage_enums::IntentStatus {
    match payment_method_data {
        Some(_) => match confirm {
            Some(true) => storage_enums::IntentStatus::RequiresPaymentMethod,
            _ => storage_enums::IntentStatus::RequiresConfirmation,
        },
        None => storage_enums::IntentStatus::RequiresPaymentMethod,
    }
}

#[cfg(feature = "v1")]
pub async fn add_domain_task_to_pt<Op>(
    operation: &Op,
    state: &SessionState,
    payment_attempt: &PaymentAttempt,
    requeue: bool,
    schedule_time: Option<time::PrimitiveDateTime>,
) -> CustomResult<(), errors::ApiErrorResponse>
where
    Op: std::fmt::Debug,
{
    if check_if_operation_confirm(operation) {
        match schedule_time {
            Some(stime) => {
                if !requeue {
                    // Here, increment the count of added tasks every time a payment has been confirmed or PSync has been called
                    metrics::TASKS_ADDED_COUNT.add(
                        1,
                        router_env::metric_attributes!(("flow", format!("{:#?}", operation))),
                    );
                    super::add_process_sync_task(&*state.store, payment_attempt, stime)
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed while adding task to process tracker")
                } else {
                    // When the requeue is true, we reset the tasks count as we reset the task every time it is requeued
                    metrics::TASKS_RESET_COUNT.add(
                        1,
                        router_env::metric_attributes!(("flow", format!("{:#?}", operation))),
                    );
                    super::reset_process_sync_task(&*state.store, payment_attempt, stime)
                        .await
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("Failed while updating task in process tracker")
                }
            }
            None => Ok(()),
        }
    } else {
        Ok(())
    }
}

pub fn response_operation<'a, F, R, D>() -> BoxedOperation<'a, F, R, D>
where
    F: Send + Clone,
    PaymentResponse: Operation<F, R, Data = D>,
{
    Box::new(PaymentResponse)
}

pub fn validate_max_amount(
    amount: api_models::payments::Amount,
) -> CustomResult<(), errors::ApiErrorResponse> {
    match amount {
        api_models::payments::Amount::Value(value) => {
            utils::when(value.get() > consts::MAX_ALLOWED_AMOUNT, || {
                Err(report!(errors::ApiErrorResponse::PreconditionFailed {
                    message: format!(
                        "amount should not be more than {}",
                        consts::MAX_ALLOWED_AMOUNT
                    )
                }))
            })
        }
        api_models::payments::Amount::Zero => Ok(()),
    }
}

#[cfg(feature = "v1")]
/// Check whether the customer information that is sent in the root of payments request
/// and in the customer object are same, if the values mismatch return an error
pub fn validate_customer_information(
    request: &api_models::payments::PaymentsRequest,
) -> RouterResult<()> {
    if let Some(mismatched_fields) = request.validate_customer_details_in_request() {
        let mismatched_fields = mismatched_fields.join(", ");
        Err(errors::ApiErrorResponse::PreconditionFailed {
            message: format!(
                "The field names `{mismatched_fields}` sent in both places is ambiguous"
            ),
        })?
    } else {
        Ok(())
    }
}

pub async fn validate_card_ip_blocking_for_business_profile(
    state: &SessionState,
    ip: IpAddr,
    fingerprnt: masking::Secret<String>,
    card_testing_guard_config: &diesel_models::business_profile::CardTestingGuardConfig,
) -> RouterResult<String> {
    let cache_key = format!(
        "{}_{}_{}",
        consts::CARD_IP_BLOCKING_CACHE_KEY_PREFIX,
        fingerprnt.peek(),
        ip
    );

    let unsuccessful_payment_threshold = card_testing_guard_config.card_ip_blocking_threshold;

    validate_blocking_threshold(state, unsuccessful_payment_threshold, cache_key).await
}

pub async fn validate_guest_user_card_blocking_for_business_profile(
    state: &SessionState,
    fingerprnt: masking::Secret<String>,
    customer_id: Option<id_type::CustomerId>,
    card_testing_guard_config: &diesel_models::business_profile::CardTestingGuardConfig,
) -> RouterResult<String> {
    let cache_key = format!(
        "{}_{}",
        consts::GUEST_USER_CARD_BLOCKING_CACHE_KEY_PREFIX,
        fingerprnt.peek()
    );

    let unsuccessful_payment_threshold =
        card_testing_guard_config.guest_user_card_blocking_threshold;

    if customer_id.is_none() {
        Ok(validate_blocking_threshold(state, unsuccessful_payment_threshold, cache_key).await?)
    } else {
        Ok(cache_key)
    }
}

pub async fn validate_customer_id_blocking_for_business_profile(
    state: &SessionState,
    customer_id: id_type::CustomerId,
    profile_id: &id_type::ProfileId,
    card_testing_guard_config: &diesel_models::business_profile::CardTestingGuardConfig,
) -> RouterResult<String> {
    let cache_key = format!(
        "{}_{}_{}",
        consts::CUSTOMER_ID_BLOCKING_PREFIX,
        profile_id.get_string_repr(),
        customer_id.get_string_repr(),
    );

    let unsuccessful_payment_threshold = card_testing_guard_config.customer_id_blocking_threshold;

    validate_blocking_threshold(state, unsuccessful_payment_threshold, cache_key).await
}

pub async fn validate_blocking_threshold(
    state: &SessionState,
    unsuccessful_payment_threshold: i32,
    cache_key: String,
) -> RouterResult<String> {
    match services::card_testing_guard::get_blocked_count_from_cache(state, &cache_key).await {
        Ok(Some(unsuccessful_payment_count)) => {
            if unsuccessful_payment_count >= unsuccessful_payment_threshold {
                Err(errors::ApiErrorResponse::PreconditionFailed {
                    message: "Blocked due to suspicious activity".to_string(),
                })?
            } else {
                Ok(cache_key)
            }
        }
        Ok(None) => Ok(cache_key),
        Err(error) => Err(errors::ApiErrorResponse::InternalServerError).attach_printable(error)?,
    }
}

#[cfg(feature = "v1")]
/// Get the customer details from customer field if present
/// or from the individual fields in `PaymentsRequest`
#[instrument(skip_all)]
pub fn get_customer_details_from_request(
    request: &api_models::payments::PaymentsRequest,
) -> CustomerDetails {
    let customer_id = request.get_customer_id().map(ToOwned::to_owned);

    let customer_name = request
        .customer
        .as_ref()
        .and_then(|customer_details| customer_details.name.clone())
        .or(request.name.clone());

    let customer_email = request
        .customer
        .as_ref()
        .and_then(|customer_details| customer_details.email.clone())
        .or(request.email.clone());

    let customer_phone = request
        .customer
        .as_ref()
        .and_then(|customer_details| customer_details.phone.clone())
        .or(request.phone.clone());

    let customer_phone_code = request
        .customer
        .as_ref()
        .and_then(|customer_details| customer_details.phone_country_code.clone())
        .or(request.phone_country_code.clone());

    let tax_registration_id = request
        .customer
        .as_ref()
        .and_then(|customer_details| customer_details.tax_registration_id.clone());

    CustomerDetails {
        customer_id,
        name: customer_name,
        email: customer_email,
        phone: customer_phone,
        phone_country_code: customer_phone_code,
        tax_registration_id,
    }
}

pub async fn get_connector_default(
    _state: &SessionState,
    request_connector: Option<serde_json::Value>,
) -> CustomResult<api::ConnectorChoice, errors::ApiErrorResponse> {
    Ok(request_connector.map_or(
        api::ConnectorChoice::Decide,
        api::ConnectorChoice::StraightThrough,
    ))
}

#[cfg(feature = "v2")]
pub async fn get_connector_data_from_request(
    state: &SessionState,
    req: Option<common_types::domain::MerchantConnectorAuthDetails>,
) -> CustomResult<api::ConnectorData, errors::ApiErrorResponse> {
    let connector = req
        .as_ref()
        .map(|connector_details| connector_details.connector_name.to_string())
        .ok_or(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "merchant_connector_details",
        })?;
    let connector_data: api::ConnectorData = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &connector,
        api::GetToken::Connector,
        None,
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Invalid connector name received")?;
    Ok(connector_data)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::type_complexity)]
pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>(
    _state: &SessionState,
    _operation: BoxedOperation<'a, F, R, D>,
    _payment_data: &mut PaymentData<F>,
    _req: Option<CustomerDetails>,
    _merchant_id: &id_type::MerchantId,
    _key_store: &domain::MerchantKeyStore,
    _storage_scheme: common_enums::enums::MerchantStorageScheme,
) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option<domain::Customer>), errors::StorageError> {
    todo!()
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::type_complexity)]
pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>(
    state: &SessionState,
    operation: BoxedOperation<'a, F, R, D>,
    payment_data: &mut PaymentData<F>,
    req: Option<CustomerDetails>,
    merchant_id: &id_type::MerchantId,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: common_enums::enums::MerchantStorageScheme,
) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option<domain::Customer>), errors::StorageError> {
    let request_customer_details = req
        .get_required_value("customer")
        .change_context(errors::StorageError::ValueNotFound("customer".to_owned()))?;

    let temp_customer_data = if request_customer_details.name.is_some()
        || request_customer_details.email.is_some()
        || request_customer_details.phone.is_some()
        || request_customer_details.phone_country_code.is_some()
        || request_customer_details.tax_registration_id.is_some()
    {
        Some(CustomerData {
            name: request_customer_details.name.clone(),
            email: request_customer_details.email.clone(),
            phone: request_customer_details.phone.clone(),
            phone_country_code: request_customer_details.phone_country_code.clone(),
            tax_registration_id: request_customer_details.tax_registration_id.clone(),
        })
    } else {
        None
    };

    // Updation of Customer Details for the cases where both customer_id and specific customer
    // details are provided in Payment Update Request
    let raw_customer_details = payment_data
        .payment_intent
        .customer_details
        .clone()
        .map(|customer_details_encrypted| {
            customer_details_encrypted
                .into_inner()
                .expose()
                .parse_value::<CustomerData>("CustomerData")
        })
        .transpose()
        .change_context(errors::StorageError::DeserializationFailed)
        .attach_printable("Failed to parse customer data from payment intent")?
        .map(|parsed_customer_data| CustomerData {
            name: request_customer_details
                .name
                .clone()
                .or(parsed_customer_data.name.clone()),
            email: request_customer_details
                .email
                .clone()
                .or(parsed_customer_data.email.clone()),
            phone: request_customer_details
                .phone
                .clone()
                .or(parsed_customer_data.phone.clone()),
            phone_country_code: request_customer_details
                .phone_country_code
                .clone()
                .or(parsed_customer_data.phone_country_code.clone()),
            tax_registration_id: request_customer_details
                .tax_registration_id
                .clone()
                .or(parsed_customer_data.tax_registration_id.clone()),
        })
        .or(temp_customer_data);
    let key_manager_state = state.into();
    payment_data.payment_intent.customer_details = raw_customer_details
        .clone()
        .async_map(|customer_details| {
            create_encrypted_data(&key_manager_state, key_store, customer_details)
        })
        .await
        .transpose()
        .change_context(errors::StorageError::EncryptionError)
        .attach_printable("Unable to encrypt customer details")?;

    let customer_id = request_customer_details
        .customer_id
        .or(payment_data.payment_intent.customer_id.clone());
    let db = &*state.store;
    let key_manager_state = &state.into();
    let optional_customer = match customer_id {
        Some(customer_id) => {
            let customer_data = db
                .find_customer_optional_by_customer_id_merchant_id(
                    key_manager_state,
                    &customer_id,
                    merchant_id,
                    key_store,
                    storage_scheme,
                )
                .await?;
            let key = key_store.key.get_inner().peek();
            let encrypted_data = types::crypto_operation(
                key_manager_state,
                type_name!(domain::Customer),
                types::CryptoOperation::BatchEncrypt(
                    domain::FromRequestEncryptableCustomer::to_encryptable(
                        domain::FromRequestEncryptableCustomer {
                            name: request_customer_details.name.clone(),
                            email: request_customer_details
                                .email
                                .as_ref()
                                .map(|e| e.clone().expose().switch_strategy()),
                            phone: request_customer_details.phone.clone(),
                            tax_registration_id: None,
                        },
                    ),
                ),
                Identifier::Merchant(key_store.merchant_id.clone()),
                key,
            )
            .await
            .and_then(|val| val.try_into_batchoperation())
            .change_context(errors::StorageError::SerializationFailed)
            .attach_printable("Failed while encrypting Customer while Update")?;
            let encryptable_customer =
                domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                    .change_context(errors::StorageError::SerializationFailed)
                    .attach_printable("Failed while encrypting Customer while Update")?;
            Some(match customer_data {
                Some(c) => {
                    // Update the customer data if new data is passed in the request
                    if request_customer_details.email.is_some()
                        | request_customer_details.name.is_some()
                        | request_customer_details.phone.is_some()
                        | request_customer_details.phone_country_code.is_some()
                        | request_customer_details.tax_registration_id.is_some()
                    {
                        let customer_update = Update {
                            name: encryptable_customer.name,
                            email: encryptable_customer.email.map(|email| {
                                let encryptable: Encryptable<
                                    masking::Secret<String, pii::EmailStrategy>,
                                > = Encryptable::new(
                                    email.clone().into_inner().switch_strategy(),
                                    email.into_encrypted(),
                                );
                                encryptable
                            }),
                            phone: Box::new(encryptable_customer.phone),
                            phone_country_code: request_customer_details.phone_country_code,
                            description: None,
                            connector_customer: Box::new(None),
                            metadata: Box::new(None),
                            address_id: None,
                            tax_registration_id: encryptable_customer.tax_registration_id,
                        };

                        db.update_customer_by_customer_id_merchant_id(
                            key_manager_state,
                            customer_id,
                            merchant_id.to_owned(),
                            c,
                            customer_update,
                            key_store,
                            storage_scheme,
                        )
                        .await
                    } else {
                        Ok(c)
                    }
                }
                None => {
                    let new_customer = domain::Customer {
                        customer_id,
                        merchant_id: merchant_id.to_owned(),
                        name: encryptable_customer.name,
                        email: encryptable_customer.email.map(|email| {
                            let encryptable: Encryptable<
                                masking::Secret<String, pii::EmailStrategy>,
                            > = Encryptable::new(
                                email.clone().into_inner().switch_strategy(),
                                email.into_encrypted(),
                            );
                            encryptable
                        }),
                        phone: encryptable_customer.phone,
                        phone_country_code: request_customer_details.phone_country_code.clone(),
                        description: None,
                        created_at: common_utils::date_time::now(),
                        metadata: None,
                        modified_at: common_utils::date_time::now(),
                        connector_customer: None,
                        address_id: None,
                        default_payment_method_id: None,
                        updated_by: None,
                        version: common_types::consts::API_VERSION,
                        tax_registration_id: encryptable_customer.tax_registration_id,
                    };
                    metrics::CUSTOMER_CREATED.add(1, &[]);
                    db.insert_customer(new_customer, key_manager_state, key_store, storage_scheme)
                        .await
                }
            })
        }
        None => match &payment_data.payment_intent.customer_id {
            None => None,
            Some(customer_id) => db
                .find_customer_optional_by_customer_id_merchant_id(
                    key_manager_state,
                    customer_id,
                    merchant_id,
                    key_store,
                    storage_scheme,
                )
                .await?
                .map(Ok),
        },
    };
    Ok((
        operation,
        match optional_customer {
            Some(customer) => {
                let customer = customer?;

                payment_data.payment_intent.customer_id = Some(customer.customer_id.clone());
                payment_data.email = payment_data.email.clone().or_else(|| {
                    customer
                        .email
                        .clone()
                        .map(|encrypted_value| encrypted_value.into())
                });

                Some(customer)
            }
            None => None,
        },
    ))
}

#[cfg(feature = "v1")]
pub async fn retrieve_payment_method_with_temporary_token(
    state: &SessionState,
    token: &str,
    payment_intent: &PaymentIntent,
    payment_attempt: &PaymentAttempt,
    merchant_key_store: &domain::MerchantKeyStore,
    card_token_data: Option<&domain::CardToken>,
) -> RouterResult<Option<(domain::PaymentMethodData, enums::PaymentMethod)>> {
    let (pm, supplementary_data) =
        vault::Vault::get_payment_method_data_from_locker(state, token, merchant_key_store)
            .await
            .attach_printable(
                "Payment method for given token not found or there was a problem fetching it",
            )?;

    utils::when(
        supplementary_data
            .customer_id
            .ne(&payment_intent.customer_id),
        || {
            Err(errors::ApiErrorResponse::PreconditionFailed { message: "customer associated with payment method and customer passed in payment are not same".into() })
        },
    )?;

    Ok::<_, error_stack::Report<errors::ApiErrorResponse>>(match pm {
        Some(domain::PaymentMethodData::Card(card)) => {
            let mut updated_card = card.clone();
            let mut is_card_updated = false;

            // The card_holder_name from locker retrieved card is considered if it is a non-empty string or else card_holder_name is picked
            // from payment_method_data.card_token object
            let name_on_card =
                card_token_data.and_then(|token_data| token_data.card_holder_name.clone());

            if let Some(name) = name_on_card.clone() {
                if !name.peek().is_empty() {
                    is_card_updated = true;
                    updated_card.nick_name = name_on_card;
                }
            }

            if let Some(token_data) = card_token_data {
                if let Some(cvc) = token_data.card_cvc.clone() {
                    is_card_updated = true;
                    updated_card.card_cvc = cvc;
                }
            }

            // populate additional card details from payment_attempt.payment_method_data (additional_payment_data) if not present in the locker
            if updated_card.card_issuer.is_none()
                || updated_card.card_network.is_none()
                || updated_card.card_type.is_none()
                || updated_card.card_issuing_country.is_none()
            {
                let additional_payment_method_data: Option<
                    api_models::payments::AdditionalPaymentData,
                > = payment_attempt
                    .payment_method_data
                    .clone()
                    .and_then(|data| match data {
                        serde_json::Value::Null => None, // This is to handle the case when the payment_method_data is null
                        _ => Some(data.parse_value("AdditionalPaymentData")),
                    })
                    .transpose()
                    .map_err(|err| logger::error!("Failed to parse AdditionalPaymentData {err:?}"))
                    .ok()
                    .flatten();
                if let Some(api_models::payments::AdditionalPaymentData::Card(card)) =
                    additional_payment_method_data
                {
                    is_card_updated = true;
                    updated_card.card_issuer = updated_card.card_issuer.or(card.card_issuer);
                    updated_card.card_network = updated_card.card_network.or(card.card_network);
                    updated_card.card_type = updated_card.card_type.or(card.card_type);
                    updated_card.card_issuing_country = updated_card
                        .card_issuing_country
                        .or(card.card_issuing_country);
                };
            };

            if is_card_updated {
                let updated_pm = domain::PaymentMethodData::Card(updated_card);
                vault::Vault::store_payment_method_data_in_locker(
                    state,
                    Some(token.to_owned()),
                    &updated_pm,
                    payment_intent.customer_id.to_owned(),
                    enums::PaymentMethod::Card,
                    merchant_key_store,
                )
                .await?;

                Some((updated_pm, enums::PaymentMethod::Card))
            } else {
                Some((
                    domain::PaymentMethodData::Card(card),
                    enums::PaymentMethod::Card,
                ))
            }
        }

        Some(the_pm @ domain::PaymentMethodData::Wallet(_)) => {
            Some((the_pm, enums::PaymentMethod::Wallet))
        }

        Some(the_pm @ domain::PaymentMethodData::BankTransfer(_)) => {
            Some((the_pm, enums::PaymentMethod::BankTransfer))
        }

        Some(the_pm @ domain::PaymentMethodData::BankRedirect(_)) => {
            Some((the_pm, enums::PaymentMethod::BankRedirect))
        }

        Some(the_pm @ domain::PaymentMethodData::BankDebit(_)) => {
            Some((the_pm, enums::PaymentMethod::BankDebit))
        }

        Some(_) => Err(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Payment method received from locker is unsupported by locker")?,

        None => None,
    })
}

#[cfg(feature = "v2")]
pub async fn retrieve_card_with_permanent_token(
    state: &SessionState,
    locker_id: &str,
    _payment_method_id: &id_type::GlobalPaymentMethodId,
    payment_intent: &PaymentIntent,
    card_token_data: Option<&domain::CardToken>,
    _merchant_key_store: &domain::MerchantKeyStore,
    _storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<domain::PaymentMethodData> {
    todo!()
}

pub enum VaultFetchAction {
    FetchCardDetailsFromLocker,
    FetchCardDetailsForNetworkTransactionIdFlowFromLocker,
    FetchNetworkTokenDataFromTokenizationService(String),
    FetchNetworkTokenDetailsFromLocker(api_models::payments::NetworkTokenWithNTIRef),
    NoFetchAction,
}

pub fn decide_payment_method_retrieval_action(
    is_network_tokenization_enabled: bool,
    mandate_id: Option<api_models::payments::MandateIds>,
    connector: Option<api_enums::Connector>,
    network_tokenization_supported_connectors: &HashSet<api_enums::Connector>,
    should_retry_with_pan: bool,
    network_token_requestor_ref_id: Option<String>,
) -> VaultFetchAction {
    let standard_flow = || {
        determine_standard_vault_action(
            is_network_tokenization_enabled,
            mandate_id,
            connector,
            network_tokenization_supported_connectors,
            network_token_requestor_ref_id,
        )
    };

    if should_retry_with_pan {
        VaultFetchAction::FetchCardDetailsFromLocker
    } else {
        standard_flow()
    }
}

pub async fn is_ucs_enabled(state: &SessionState, config_key: &str) -> bool {
    let db = state.store.as_ref();
    db.find_config_by_key_unwrap_or(config_key, Some("false".to_string()))
        .await
        .inspect_err(|error| {
            logger::error!(
                ?error,
                "Failed to fetch `{config_key}` UCS enabled config from DB"
            );
        })
        .ok()
        .and_then(|config| {
            config
                .config
                .parse::<bool>()
                .inspect_err(|error| {
                    logger::error!(?error, "Failed to parse `{config_key}` UCS enabled config");
                })
                .ok()
        })
        .unwrap_or(false)
}

pub async fn should_execute_based_on_rollout(
    state: &SessionState,
    config_key: &str,
) -> RouterResult<bool> {
    let db = state.store.as_ref();

    match db.find_config_by_key(config_key).await {
        Ok(rollout_config) => match rollout_config.config.parse::<f64>() {
            Ok(rollout_percent) => {
                if !(0.0..=1.0).contains(&rollout_percent) {
                    logger::warn!(
                        rollout_percent,
                        "Rollout percent out of bounds. Must be between 0.0 and 1.0"
                    );
                    return Ok(false);
                }

                let sampled_value: f64 = rand::thread_rng().gen_range(0.0..1.0);
                Ok(sampled_value < rollout_percent)
            }
            Err(err) => {
                logger::error!(error = ?err, "Failed to parse rollout percent");
                Ok(false)
            }
        },
        Err(err) => {
            logger::error!(error = ?err, "Failed to fetch rollout config from DB");
            Ok(false)
        }
    }
}

pub fn determine_standard_vault_action(
    is_network_tokenization_enabled: bool,
    mandate_id: Option<api_models::payments::MandateIds>,
    connector: Option<api_enums::Connector>,
    network_tokenization_supported_connectors: &HashSet<api_enums::Connector>,
    network_token_requestor_ref_id: Option<String>,
) -> VaultFetchAction {
    let is_network_transaction_id_flow = mandate_id
        .as_ref()
        .map(|mandate_ids| mandate_ids.is_network_transaction_id_flow())
        .unwrap_or(false);

    if !is_network_tokenization_enabled {
        if is_network_transaction_id_flow {
            VaultFetchAction::FetchCardDetailsForNetworkTransactionIdFlowFromLocker
        } else {
            VaultFetchAction::FetchCardDetailsFromLocker
        }
    } else {
        match mandate_id {
            Some(mandate_ids) => match mandate_ids.mandate_reference_id {
                Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(nt_data)) => {
                    VaultFetchAction::FetchNetworkTokenDetailsFromLocker(nt_data)
                }
                Some(api_models::payments::MandateReferenceId::NetworkMandateId(_)) => {
                    VaultFetchAction::FetchCardDetailsForNetworkTransactionIdFlowFromLocker
                }
                Some(api_models::payments::MandateReferenceId::ConnectorMandateId(_)) | None => {
                    VaultFetchAction::NoFetchAction
                }
            },
            None => {
                //saved card flow
                let is_network_token_supported_connector = connector
                    .map(|conn| network_tokenization_supported_connectors.contains(&conn))
                    .unwrap_or(false);

                match (
                    is_network_token_supported_connector,
                    network_token_requestor_ref_id,
                ) {
                    (true, Some(ref_id)) => {
                        VaultFetchAction::FetchNetworkTokenDataFromTokenizationService(ref_id)
                    }
                    (false, Some(_)) | (true, None) | (false, None) => {
                        VaultFetchAction::FetchCardDetailsFromLocker
                    }
                }
            }
        }
    }
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn retrieve_payment_method_data_with_permanent_token(
    state: &SessionState,
    locker_id: &str,
    _payment_method_id: &str,
    payment_intent: &PaymentIntent,
    card_token_data: Option<&domain::CardToken>,
    _merchant_key_store: &domain::MerchantKeyStore,
    _storage_scheme: enums::MerchantStorageScheme,
    mandate_id: Option<api_models::payments::MandateIds>,
    payment_method_info: domain::PaymentMethod,


impl SecretState for SecuredSecret {}


pub struct Authentication {
    pub authentication_id: common_utils::id_type::AuthenticationId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub authentication_connector: Option<String>,
    pub connector_authentication_id: Option<String>,
    pub authentication_data: Option<serde_json::Value>,
    pub payment_method_id: String,
    pub authentication_type: Option<common_enums::DecoupledAuthenticationType>,
    pub authentication_status: common_enums::AuthenticationStatus,
    pub authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: time::PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub modified_at: time::PrimitiveDateTime,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
    pub connector_metadata: Option<serde_json::Value>,
    pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
    pub threeds_server_transaction_id: Option<String>,
    pub cavv: Option<String>,
    pub authentication_flow_type: Option<String>,
    pub message_version: Option<common_utils::types::SemanticVersion>,
    pub eci: Option<String>,
    pub trans_status: Option<common_enums::TransactionStatus>,
    pub acquirer_bin: Option<String>,
    pub acquirer_merchant_id: Option<String>,
    pub three_ds_method_data: Option<String>,
    pub three_ds_method_url: Option<String>,
    pub acs_url: Option<String>,
    pub challenge_request: Option<String>,
    pub acs_reference_number: Option<String>,
    pub acs_trans_id: Option<String>,
    pub acs_signed_content: Option<String>,
    pub profile_id: common_utils::id_type::ProfileId,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub ds_trans_id: Option<String>,
    pub directory_server_id: Option<String>,
    pub acquirer_country_code: Option<String>,
    pub service_details: Option<serde_json::Value>,
    pub organization_id: common_utils::id_type::OrganizationId,
    pub authentication_client_secret: Option<String>,
    pub force_3ds_challenge: Option<bool>,
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    pub return_url: Option<String>,
    pub amount: Option<common_utils::types::MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    pub billing_address: Option<Encryption>,
    pub shipping_address: Option<Encryption>,
    pub browser_info: Option<serde_json::Value>,
    pub email: Option<Encryption>,
    pub profile_acquirer_id: Option<common_utils::id_type::ProfileAcquirerId>,
    pub challenge_code: Option<String>,
    pub challenge_cancel: Option<String>,
    pub challenge_code_reason: Option<String>,
    pub message_extension: Option<pii::SecretSerdeValue>,
}


impl PaymentsResponseScheme {
    pub fn new(reference: String) -> Self {
        Self { reference }
    }
}


pub struct RefundResponse {
    //A unique number that tags a credit or debit card transaction when it goes from the merchant's bank through to the cardholder's bank.
    acquirer_reference_number: Option<String>,
    amount: f32,
    //Unique identifier for the Refund
    id: String,
    status: RefundStatus,
}


File: crates/hyperswitch_domain_models/src/router_data_v2.rs
 Public structs: 1

pub mod flow_common_types;

use std::{marker::PhantomData, ops::Deref};

use common_utils::id_type;
#[cfg(feature = "frm")]
pub use flow_common_types::FrmFlowData;
#[cfg(feature = "payouts")]
pub use flow_common_types::PayoutFlowData;
pub use flow_common_types::{
    AccessTokenFlowData, AuthenticationTokenFlowData, DisputesFlowData,
    ExternalAuthenticationFlowData, ExternalVaultProxyFlowData, FilesFlowData,
    MandateRevokeFlowData, PaymentFlowData, RefundFlowData, UasFlowData, VaultConnectorFlowData,
    WebhookSourceVerifyData,
};

use crate::router_data::{ConnectorAuthType, ErrorResponse};

#[derive(Debug, Clone)]
pub struct RouterDataV2<Flow, ResourceCommonData, FlowSpecificRequest, FlowSpecificResponse> {
    pub flow: PhantomData<Flow>,
    pub tenant_id: id_type::TenantId,
    pub resource_common_data: ResourceCommonData,
    pub connector_auth_type: ConnectorAuthType,
    /// Contains flow-specific data required to construct a request and send it to the connector.
    pub request: FlowSpecificRequest,
    /// Contains flow-specific data that the connector responds with.
    pub response: Result<FlowSpecificResponse, ErrorResponse>,
}

impl<Flow, ResourceCommonData, FlowSpecificRequest, FlowSpecificResponse> Deref
    for RouterDataV2<Flow, ResourceCommonData, FlowSpecificRequest, FlowSpecificResponse>
{
    type Target = ResourceCommonData;
    fn deref(&self) -> &Self::Target {
        &self.resource_common_data
    }
}


pub fn validate_role_groups(groups: &[PermissionGroup]) -> UserResult<()> {
    if groups.is_empty() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("Role groups cannot be empty");
    }

    let unique_groups: HashSet<_> = groups.iter().copied().collect();

    if unique_groups.contains(&PermissionGroup::OrganizationManage)
        || unique_groups.contains(&PermissionGroup::InternalManage)
    {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("Invalid groups present in the custom role");
    }

    if unique_groups.len() != groups.len() {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("Duplicate permission group found");
    }

    Ok(())
}


File: crates/router/src/types/domain/user/dashboard_metadata.rs
 Public structs: 1

use api_models::user::dashboard_metadata as api;
use diesel_models::enums::DashboardMetadata as DBEnum;
use masking::Secret;
use time::PrimitiveDateTime;

pub enum MetaData {
    ProductionAgreement(ProductionAgreementValue),
    SetupProcessor(api::SetupProcessor),
    ConfigureEndpoint(bool),
    SetupComplete(bool),
    FirstProcessorConnected(api::ProcessorConnected),
    SecondProcessorConnected(api::ProcessorConnected),
    ConfiguredRouting(api::ConfiguredRouting),
    TestPayment(api::TestPayment),
    IntegrationMethod(api::IntegrationMethod),
    ConfigurationType(api::ConfigurationType),
    IntegrationCompleted(bool),
    StripeConnected(api::ProcessorConnected),
    PaypalConnected(api::ProcessorConnected),
    SPRoutingConfigured(api::ConfiguredRouting),
    Feedback(api::Feedback),
    ProdIntent(api::ProdIntent),
    SPTestPayment(bool),
    DownloadWoocom(bool),
    ConfigureWoocom(bool),
    SetupWoocomWebhook(bool),
    IsMultipleConfiguration(bool),
    IsChangePasswordRequired(bool),
    OnboardingSurvey(api::OnboardingSurvey),
    ReconStatus(api::ReconStatus),
}

impl From<&MetaData> for DBEnum {
    fn from(value: &MetaData) -> Self {
        match value {
            MetaData::ProductionAgreement(_) => Self::ProductionAgreement,
            MetaData::SetupProcessor(_) => Self::SetupProcessor,
            MetaData::ConfigureEndpoint(_) => Self::ConfigureEndpoint,
            MetaData::SetupComplete(_) => Self::SetupComplete,
            MetaData::FirstProcessorConnected(_) => Self::FirstProcessorConnected,
            MetaData::SecondProcessorConnected(_) => Self::SecondProcessorConnected,
            MetaData::ConfiguredRouting(_) => Self::ConfiguredRouting,
            MetaData::TestPayment(_) => Self::TestPayment,
            MetaData::IntegrationMethod(_) => Self::IntegrationMethod,
            MetaData::ConfigurationType(_) => Self::ConfigurationType,
            MetaData::IntegrationCompleted(_) => Self::IntegrationCompleted,
            MetaData::StripeConnected(_) => Self::StripeConnected,
            MetaData::PaypalConnected(_) => Self::PaypalConnected,
            MetaData::SPRoutingConfigured(_) => Self::SpRoutingConfigured,
            MetaData::Feedback(_) => Self::Feedback,
            MetaData::ProdIntent(_) => Self::ProdIntent,
            MetaData::SPTestPayment(_) => Self::SpTestPayment,
            MetaData::DownloadWoocom(_) => Self::DownloadWoocom,
            MetaData::ConfigureWoocom(_) => Self::ConfigureWoocom,
            MetaData::SetupWoocomWebhook(_) => Self::SetupWoocomWebhook,
            MetaData::IsMultipleConfiguration(_) => Self::IsMultipleConfiguration,
            MetaData::IsChangePasswordRequired(_) => Self::IsChangePasswordRequired,
            MetaData::OnboardingSurvey(_) => Self::OnboardingSurvey,
            MetaData::ReconStatus(_) => Self::ReconStatus,
        }
    }
}
#[derive(Debug, serde::Serialize)]
pub struct ProductionAgreementValue {
    pub version: String,
    pub ip_address: Secret<String, common_utils::pii::IpAddress>,
    pub timestamp: PrimitiveDateTime,
}


impl ConnectorValidation for Shift4 {}


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

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*COINGATE_SUPPORTED_PAYMENT_METHODS)
    }

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

impl ConnectorSpecifications for for Coingate


impl ProcessTrackerInterface for KafkaStore {
    async fn reinitialize_limbo_processes(
        &self,
        ids: Vec<String>,
        schedule_time: PrimitiveDateTime,
    ) -> CustomResult<usize, errors::StorageError> {
        self.diesel_store
            .reinitialize_limbo_processes(ids, schedule_time)
            .await
    }

    async fn find_process_by_id(
        &self,
        id: &str,
    ) -> CustomResult<Option<storage::ProcessTracker>, errors::StorageError> {
        self.diesel_store.find_process_by_id(id).await
    }

    async fn update_process(
        &self,
        this: storage::ProcessTracker,
        process: storage::ProcessTrackerUpdate,
    ) -> CustomResult<storage::ProcessTracker, errors::StorageError> {
        self.diesel_store.update_process(this, process).await
    }

    async fn process_tracker_update_process_status_by_ids(
        &self,
        task_ids: Vec<String>,
        task_update: storage::ProcessTrackerUpdate,
    ) -> CustomResult<usize, errors::StorageError> {
        self.diesel_store
            .process_tracker_update_process_status_by_ids(task_ids, task_update)
            .await
    }

    async fn insert_process(
        &self,
        new: storage::ProcessTrackerNew,
    ) -> CustomResult<storage::ProcessTracker, errors::StorageError> {
        self.diesel_store.insert_process(new).await
    }

    async fn reset_process(
        &self,
        this: storage::ProcessTracker,
        schedule_time: PrimitiveDateTime,
    ) -> CustomResult<(), errors::StorageError> {
        self.diesel_store.reset_process(this, schedule_time).await
    }

    async fn retry_process(
        &self,
        this: storage::ProcessTracker,
        schedule_time: PrimitiveDateTime,
    ) -> CustomResult<(), errors::StorageError> {
        self.diesel_store.retry_process(this, schedule_time).await
    }

    async fn finish_process_with_business_status(
        &self,
        this: storage::ProcessTracker,
        business_status: &'static str,
    ) -> CustomResult<(), errors::StorageError> {
        self.diesel_store
            .finish_process_with_business_status(this, business_status)
            .await
    }
    async fn find_processes_by_time_status(
        &self,
        time_lower_limit: PrimitiveDateTime,
        time_upper_limit: PrimitiveDateTime,
        status: ProcessTrackerStatus,
        limit: Option<i64>,
    ) -> CustomResult<Vec<storage::ProcessTracker>, errors::StorageError> {
        self.diesel_store
            .find_processes_by_time_status(time_lower_limit, time_upper_limit, status, limit)
            .await
    }
}

impl ProcessTrackerInterface for for KafkaStore


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

pub use hyperswitch_domain_models::{
    router_request_types::authentication::{
        AcquirerDetails, AuthNFlowType, ChallengeParams, ConnectorAuthenticationRequestData,
        ConnectorPostAuthenticationRequestData, PreAuthNRequestData, PreAuthenticationData,
    },
    router_response_types::AuthenticationResponseData,
};

use super::{api, RouterData};
use crate::services;

pub type PreAuthNRouterData =
    RouterData<api::PreAuthentication, PreAuthNRequestData, AuthenticationResponseData>;

pub type PreAuthNVersionCallRouterData =
    RouterData<api::PreAuthenticationVersionCall, PreAuthNRequestData, AuthenticationResponseData>;

pub type ConnectorAuthenticationRouterData =
    RouterData<api::Authentication, ConnectorAuthenticationRequestData, AuthenticationResponseData>;

pub type ConnectorPostAuthenticationRouterData = RouterData<
    api::PostAuthentication,
    ConnectorPostAuthenticationRequestData,
    AuthenticationResponseData,
>;

pub type ConnectorAuthenticationType = dyn services::ConnectorIntegration<
    api::Authentication,
    ConnectorAuthenticationRequestData,
    AuthenticationResponseData,
>;

pub type ConnectorPostAuthenticationType = dyn services::ConnectorIntegration<
    api::PostAuthentication,
    ConnectorPostAuthenticationRequestData,
    AuthenticationResponseData,
>;

pub type ConnectorPreAuthenticationType = dyn services::ConnectorIntegration<
    api::PreAuthentication,
    PreAuthNRequestData,
    AuthenticationResponseData,
>;

pub type ConnectorPreAuthenticationVersionCallType = dyn services::ConnectorIntegration<
    api::PreAuthenticationVersionCall,
    PreAuthNRequestData,
    AuthenticationResponseData,
>;


pub async fn merchant_account_toggle_all_kv(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<admin::ToggleAllKVRequest>,
) -> HttpResponse {
    let flow = Flow::MerchantTransferKey;
    let payload = json_payload.into_inner();

    api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, _, payload, _| toggle_kv_for_all_merchants(state, payload.kv_enabled),
        &auth::AdminApiAuth,
        api_locking::LockAction::NotApplicable,
    )
    .await
}


OpenAPI Block Path: components.schemas.GpayMerchantInfo
{
  "type": "object",
  "required": [
    "merchant_name"
  ],
  "properties": {
    "merchant_id": {
      "type": "string",
      "description": "The merchant Identifier that needs to be passed while invoking Gpay SDK",
      "nullable": true
    },
    "merchant_name": {
      "type": "string",
      "description": "The name of the merchant that needs to be displayed on Gpay PopUp"
    }
  }
}


pub struct MerchantAccountListRequest {
    pub organization_id: id_type::OrganizationId,
}


File: crates/hyperswitch_domain_models/src/router_flow_types/mandate_revoke.rs
 Public structs: 1

#[derive(Clone, Debug)]
pub struct MandateRevoke;


pub async fn insert_totp_secret_in_redis(
    state: &SessionState,
    user_id: &str,
    secret: &masking::Secret<String>,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .set_key_with_expiry(
            &get_totp_secret_key(user_id).into(),
            secret.peek(),
            consts::user::REDIS_TOTP_SECRET_TTL_IN_SECS,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}


File: crates/router/src/services/authentication/blacklist.rs
 Public functions: 6

use std::sync::Arc;

#[cfg(feature = "olap")]
use common_utils::date_time;
use error_stack::ResultExt;
use redis_interface::RedisConnectionPool;

use super::AuthToken;
#[cfg(feature = "olap")]
use super::{SinglePurposeOrLoginToken, SinglePurposeToken};
#[cfg(feature = "email")]
use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
use crate::{
    consts::{JWT_TOKEN_TIME_IN_SECS, ROLE_BLACKLIST_PREFIX, USER_BLACKLIST_PREFIX},
    core::errors::{ApiErrorResponse, RouterResult},
    routes::app::SessionStateInfo,
};
#[cfg(feature = "olap")]
use crate::{
    core::errors::{UserErrors, UserResult},
    routes::SessionState,
    services::authorization as authz,
};

#[cfg(feature = "olap")]
pub async fn insert_user_in_blacklist(state: &SessionState, user_id: &str) -> UserResult<()> {
    let user_blacklist_key = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &user_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
pub async fn insert_role_in_blacklist(state: &SessionState, role_id: &str) -> UserResult<()> {
    let role_blacklist_key = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &role_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)?;
    invalidate_role_cache(state, role_id)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
async fn invalidate_role_cache(state: &SessionState, role_id: &str) -> RouterResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&authz::get_cache_key_from_role_id(role_id).as_str().into())
        .await
        .map(|_| ())
        .change_context(ApiErrorResponse::InternalServerError)
}

pub async fn check_user_in_blacklist<A: SessionStateInfo>(
    state: &A,
    user_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

pub async fn check_role_in_blacklist<A: SessionStateInfo>(
    state: &A,
    role_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

#[cfg(feature = "email")]
pub async fn insert_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let expiry =
        expiry_to_i64(EMAIL_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(&blacklist_key.as_str().into(), true, expiry)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "email")]
pub async fn check_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let key_exists = redis_conn
        .exists::<()>(&blacklist_key.as_str().into())
        .await
        .change_context(UserErrors::InternalServerError)?;

    if key_exists {
        return Err(UserErrors::LinkInvalid.into());
    }
    Ok(())
}

fn get_redis_connection_for_global_tenant<A: SessionStateInfo>(
    state: &A,
) -> RouterResult<Arc<RedisConnectionPool>> {
    state
        .global_store()
        .get_redis_conn()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")
}

fn expiry_to_i64(expiry: u64) -> RouterResult<i64> {
    i64::try_from(expiry).change_context(ApiErrorResponse::InternalServerError)
}

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

#[async_trait::async_trait]
impl BlackList for AuthToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        Ok(
            check_user_in_blacklist(state, &self.user_id, self.exp).await?
                || check_role_in_blacklist(state, &self.role_id, self.exp).await?,
        )
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeOrLoginToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}


OpenAPI Block Path: components.schemas.ExtendedCardInfoResponse
{
  "type": "object",
  "required": [
    "payload"
  ],
  "properties": {
    "payload": {
      "type": "string"
    }
  }
}


pub struct PaymentsResponse {
    order_id: String,
    fraud_status: KlarnaFraudStatus,
    authorized_payment_method: Option<AuthorizedPaymentMethod>,
}


pub struct InfoCodes {
    address: Option<Vec<String>>,
    identity_change: Option<Vec<String>>,
}


pub struct AlternativePaymentMethod {
    pub payment_method: AlternativePaymentMethodType,
    #[serde(rename = "BIC")]
    pub bank_id: Option<NuveiBIC>,
}


impl Responder {
    let flow = Flow::PaymentsList;
    let payload = payload.into_inner();
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payments::apply_filters_on_payments(state, merchant_context, None, req)
        },
        &auth::JWTAuth {
            permission: Permission::MerchantPaymentRead,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub trait Tokenizable {
    fn set_session_token(&mut self, token: Option<String>);
}


pub fn string_str(input: &str) -> ParseResult<&str, String> {
    error::context(
        "String",
        combinator::map(
            sequence::delimited(
                complete::tag("\""),
                complete::take_while1(|c: char| c != '"'),
                complete::tag("\""),
            ),
            |val: &str| val.to_string(),
        ),
    )(input)
}


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

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

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

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

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, Maskable<String>)>, errors::ConnectorError> {
        let auth = paypal::PaypalAuthType::try_from(auth_type)?;
        let credentials = auth.get_credentials()?;

        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            credentials.get_client_secret().into_masked(),
        )])
    }

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

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

        let error_reason = response
            .details
            .clone()
            .map(|error_details| {
                error_details
                    .iter()
                    .try_fold(String::new(), |mut acc, error| {
                        if let Some(description) = &error.description {
                            write!(acc, "description - {description} ;")
                                .change_context(
                                    errors::ConnectorError::ResponseDeserializationFailed,
                                )
                                .attach_printable("Failed to concatenate error details")
                                .map(|_| acc)
                        } else {
                            Ok(acc)
                        }
                    })
            })
            .transpose()?;
        let reason = match error_reason {
            Some(err_reason) => err_reason
                .is_empty()
                .then(|| response.message.to_owned())
                .or(Some(err_reason)),
            None => Some(response.message.to_owned()),
        };
        let errors_list = response.details.unwrap_or_default();
        let option_error_code_message =
            connector_utils::get_error_code_error_message_based_on_priority(
                self.clone(),
                errors_list
                    .into_iter()
                    .map(|errors| errors.into())
                    .collect(),
            );

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: option_error_code_message
                .clone()
                .map(|error_code_message| error_code_message.error_code)
                .unwrap_or(NO_ERROR_CODE.to_string()),
            message: option_error_code_message
                .map(|error_code_message| error_code_message.error_message)
                .unwrap_or(NO_ERROR_MESSAGE.to_string()),
            reason,
            attempt_status: None,
            connector_transaction_id: response.debug_id,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}


pub struct FiuuFPXData {
    #[serde(rename = "non_3DS")]
    non_3ds: i32,
    txn_channel: FPXTxnChannel,
}


OpenAPI Block Path: components.schemas.WeChatPay
{
  "type": "object"
}


pub struct RedirectRequest {
    name: Secret<String>,
    country_code: enums::CountryAlpha2,
    experience_context: ContextStruct,
}


pub struct DlocalRefundRequest {
    pub amount: String,
    pub payment_id: String,
    pub currency: enums::Currency,
    pub id: String,
}


pub async fn insert_recovery_code_attempts_in_redis(
    state: &SessionState,
    user_id: &str,
    user_recovery_code_attempts: u8,
) -> UserResult<()> {
    let redis_conn = super::get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .set_key_with_expiry(
            &get_recovery_code_attempts_key(user_id).into(),
            user_recovery_code_attempts,
            consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_TTL_IN_SECS,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}


/// Executes a capture request by building a connector-specific request and deciding
/// the appropriate flow to send it to the payment connector.
pub async fn call_capture_request(
    mut capture_router_data: types::RouterData<
        api::Capture,
        PaymentsCaptureData,
        types::PaymentsResponseData,
    >,
    state: &SessionState,
    connector: &api::ConnectorData,
    call_connector_action: payments::CallConnectorAction,
    business_profile: &domain::Profile,
    header_payload: hyperswitch_domain_models::payments::HeaderPayload,
) -> RouterResult<types::RouterData<api::Capture, PaymentsCaptureData, types::PaymentsResponseData>>
{
    // Build capture-specific connector request
    let (connector_request, _should_continue_further) = capture_router_data
        .build_flow_specific_connector_request(state, connector, call_connector_action.clone())
        .await?;

    // Execute capture flow
    capture_router_data
        .decide_flows(
            state,
            connector,
            call_connector_action,
            connector_request,
            business_profile,
            header_payload.clone(),
            None,
        )
        .await
}


impl super::sdk_events::metrics::SdkEventMetricAnalytics for ClickhouseClient {}

impl super::sdk_events::metrics::SdkEventMetricAnalytics for for ClickhouseClient


File: crates/cards/src/lib.rs
 Public functions: 6
 Public structs: 4

pub mod validate;
use std::ops::Deref;

use common_utils::{date_time, errors};
use error_stack::report;
use masking::{PeekInterface, StrongSecret};
use serde::{de, Deserialize, Serialize};
use time::{Date, Duration, PrimitiveDateTime, Time};

pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr, NetworkToken};

#[derive(Serialize)]
pub struct CardSecurityCode(StrongSecret<u16>);

impl TryFrom<u16> for CardSecurityCode {
    type Error = error_stack::Report<errors::ValidationError>;
    fn try_from(csc: u16) -> Result<Self, Self::Error> {
        if (0..=9999).contains(&csc) {
            Ok(Self(StrongSecret::new(csc)))
        } else {
            Err(report!(errors::ValidationError::InvalidValue {
                message: "invalid card security code".to_string()
            }))
        }
    }
}

impl<'de> Deserialize<'de> for CardSecurityCode {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let csc = u16::deserialize(deserializer)?;
        csc.try_into().map_err(de::Error::custom)
    }
}

#[derive(Clone, Debug, Serialize)]
pub struct CardExpirationMonth(StrongSecret<u8>);

impl CardExpirationMonth {
    pub fn two_digits(&self) -> String {
        format!("{:02}", self.peek())
    }
}

impl TryFrom<u8> for CardExpirationMonth {
    type Error = error_stack::Report<errors::ValidationError>;
    fn try_from(month: u8) -> Result<Self, Self::Error> {
        if (1..=12).contains(&month) {
            Ok(Self(StrongSecret::new(month)))
        } else {
            Err(report!(errors::ValidationError::InvalidValue {
                message: "invalid card expiration month".to_string()
            }))
        }
    }
}

impl<'de> Deserialize<'de> for CardExpirationMonth {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let month = u8::deserialize(deserializer)?;
        month.try_into().map_err(de::Error::custom)
    }
}

#[derive(Clone, Debug, Serialize)]
pub struct CardExpirationYear(StrongSecret<u16>);

impl CardExpirationYear {
    pub fn four_digits(&self) -> String {
        self.peek().to_string()
    }

    pub fn two_digits(&self) -> String {
        let year = self.peek() % 100;
        year.to_string()
    }
}

impl TryFrom<u16> for CardExpirationYear {
    type Error = error_stack::Report<errors::ValidationError>;
    fn try_from(year: u16) -> Result<Self, Self::Error> {
        let curr_year = u16::try_from(date_time::now().year()).map_err(|_| {
            report!(errors::ValidationError::InvalidValue {
                message: "invalid year".to_string()
            })
        })?;

        if year >= curr_year {
            Ok(Self(StrongSecret::<u16>::new(year)))
        } else {
            Err(report!(errors::ValidationError::InvalidValue {
                message: "invalid card expiration year".to_string()
            }))
        }
    }
}

impl<'de> Deserialize<'de> for CardExpirationYear {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let year = u16::deserialize(deserializer)?;
        year.try_into().map_err(de::Error::custom)
    }
}

#[derive(Serialize, Deserialize)]
pub struct CardExpiration {
    pub month: CardExpirationMonth,
    pub year: CardExpirationYear,
}

impl CardExpiration {
    pub fn is_expired(&self) -> Result<bool, error_stack::Report<errors::ValidationError>> {
        let current_datetime_utc = date_time::now();

        let expiration_month = time::Month::try_from(*self.month.peek()).map_err(|_| {
            report!(errors::ValidationError::InvalidValue {
                message: "invalid month".to_string()
            })
        })?;

        let expiration_year = *self.year.peek();

        let expiration_day = expiration_month.length(i32::from(expiration_year));

        let expiration_date =
            Date::from_calendar_date(i32::from(expiration_year), expiration_month, expiration_day)
                .map_err(|_| {
                    report!(errors::ValidationError::InvalidValue {
                        message: "error while constructing calendar date".to_string()
                    })
                })?;

        let expiration_time = Time::MIDNIGHT;

        // actual expiry date specified on card w.r.t. local timezone
        // max diff b/w utc and other timezones is 14 hours
        let mut expiration_datetime_utc = PrimitiveDateTime::new(expiration_date, expiration_time);

        // compensating time difference b/w local and utc timezone by adding a day
        expiration_datetime_utc = expiration_datetime_utc.saturating_add(Duration::days(1));

        Ok(current_datetime_utc > expiration_datetime_utc)
    }

    pub fn get_month(&self) -> &CardExpirationMonth {
        &self.month
    }

    pub fn get_year(&self) -> &CardExpirationYear {
        &self.year
    }
}

impl TryFrom<(u8, u16)> for CardExpiration {
    type Error = error_stack::Report<errors::ValidationError>;
    fn try_from(items: (u8, u16)) -> errors::CustomResult<Self, errors::ValidationError> {
        let month = CardExpirationMonth::try_from(items.0)?;
        let year = CardExpirationYear::try_from(items.1)?;
        Ok(Self { month, year })
    }
}

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

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

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


impl api::Payment for Aci {}


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

    fn get_webhook_event_type(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        Ok(IncomingWebhookEvent::EventNotSupported)
    }

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

impl IncomingWebhook for for Multisafepay


pub struct ConfirmIntentResponseUpdate {
    pub status: storage_enums::AttemptStatus,
    pub connector_payment_id: Option<String>,
    pub updated_by: String,
    pub redirection_data: Option<router_response_types::RedirectForm>,
    pub connector_metadata: Option<pii::SecretSerdeValue>,
    pub amount_capturable: Option<MinorUnit>,
    pub connector_token_details: Option<diesel_models::ConnectorTokenDetails>,
    pub connector_response_reference_id: Option<String>,
}


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


impl api::PaymentToken for Authipay {}


pub struct BankOfAmericaAuthenticationErrorResponse {
    pub response: AuthenticationErrorInformation,
}


File: crates/router/src/core/payments/operations/payment_approve.rs
 Public structs: 1

use std::marker::PhantomData;

use api_models::enums::{AttemptStatus, FrmSuggestion, IntentStatus};
use async_trait::async_trait;
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payments::{helpers, operations, PaymentData},
    },
    events::audit_events::{AuditEvent, AuditEventType},
    routes::{app::ReqState, SessionState},
    services,
    types::{
        api::{self, PaymentIdTypeExt},
        domain,
        storage::{self, enums as storage_enums},
        PaymentAddress,
    },
    utils::OptionExt,
};

#[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(operations = "all", flow = "capture")]
pub struct PaymentApprove;

type PaymentApproveOperation<'a, F> =
    BoxedOperation<'a, F, api::PaymentsCaptureRequest, PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentData<F>, api::PaymentsCaptureRequest>
    for PaymentApprove
{
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        _request: &api::PaymentsCaptureRequest,
        merchant_context: &domain::MerchantContext,
        _auth_flow: services::AuthFlow,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<
        operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData<F>>,
    > {
        let db = &*state.store;
        let key_manager_state = &state.into();
        let merchant_id = merchant_context.get_merchant_account().get_id();
        let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
        let (mut payment_intent, payment_attempt, currency, amount);

        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;

        payment_intent = db
            .find_payment_intent_by_payment_id_merchant_id(
                key_manager_state,
                &payment_id,
                merchant_id,
                merchant_context.get_merchant_key_store(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        helpers::validate_payment_status_against_not_allowed_statuses(
            payment_intent.status,
            &[IntentStatus::Failed, IntentStatus::Succeeded],
            "approve",
        )?;

        let profile_id = payment_intent
            .profile_id
            .as_ref()
            .get_required_value("profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("'profile_id' not set in payment intent")?;

        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(),
            })?;

        let attempt_id = payment_intent.active_attempt.get_id().clone();
        payment_attempt = db
            .find_payment_attempt_by_payment_id_merchant_id_attempt_id(
                &payment_intent.payment_id,
                merchant_id,
                &attempt_id.clone(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        currency = payment_attempt.currency.get_required_value("currency")?;
        amount = payment_attempt.get_total_amount().into();

        let shipping_address = helpers::get_address_by_id(
            state,
            payment_intent.shipping_address_id.clone(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        let billing_address = helpers::get_address_by_id(
            state,
            payment_intent.billing_address_id.clone(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        let payment_method_billing = helpers::get_address_by_id(
            state,
            payment_attempt.payment_method_billing_address_id.clone(),
            merchant_context.get_merchant_key_store(),
            &payment_intent.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await?;

        payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id);
        payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id);

        let frm_response = if cfg!(feature = "frm") {
            db.find_fraud_check_by_payment_id(payment_intent.payment_id.clone(), merchant_context.get_merchant_account().get_id().clone())
                .await
                .change_context(errors::ApiErrorResponse::PaymentNotFound)
                .attach_printable_lazy(|| {
                    format!("Error while retrieving frm_response, merchant_id: {}, payment_id: {attempt_id}", merchant_context.get_merchant_account().get_id().get_string_repr())
                })
                .ok()
        } else {
            None
        };

        let payment_data = PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount,
            email: None,
            mandate_id: None,
            mandate_connector: None,
            setup_mandate: None,
            customer_acceptance: None,
            token: None,
            token_data: None,
            address: PaymentAddress::new(
                shipping_address.as_ref().map(From::from),
                billing_address.as_ref().map(From::from),
                payment_method_billing.as_ref().map(From::from),
                business_profile.use_billing_as_payment_method_billing,
            ),
            confirm: None,
            payment_method_data: None,
            payment_method_token: None,
            payment_method_info: None,
            force_sync: None,
            all_keys_required: None,
            refunds: vec![],
            disputes: vec![],
            attempts: None,
            sessions_token: vec![],
            card_cvc: None,
            creds_identifier: None,
            pm_token: None,
            connector_customer_id: None,
            recurring_mandate_payment_data: None,
            ephemeral_key: None,
            multiple_capture_data: None,
            redirect_response: None,
            surcharge_details: None,
            frm_message: frm_response,
            payment_link_data: None,
            incremental_authorization_details: None,
            authorizations: vec![],
            authentication: None,
            recurring_details: None,
            poll_config: None,
            tax_data: None,
            session_id: None,
            service_details: None,
            card_testing_guard_data: None,
            vault_operation: None,
            threeds_method_comp_ind: None,
            whole_connector_response: None,
        };

        let get_trackers_response = operations::GetTrackerResponse {
            operation: Box::new(self),
            customer_details: None,
            payment_data,
            business_profile,
            mandate_type: None,
        };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsCaptureRequest>
    for PaymentApprove
{
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        req_state: ReqState,
        mut payment_data: PaymentData<F>,
        _customer: Option<domain::Customer>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        _updated_customer: Option<storage::CustomerUpdate>,
        key_store: &domain::MerchantKeyStore,
        frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    ) -> RouterResult<(PaymentApproveOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        if matches!(frm_suggestion, Some(FrmSuggestion::FrmAuthorizeTransaction)) {
            payment_data.payment_intent.status = IntentStatus::RequiresCapture; // In Approve flow, payment which has payment_capture_method "manual" and attempt status as "Unresolved",
            payment_data.payment_attempt.status = AttemptStatus::Authorized; // We shouldn't call the connector instead we need to update the payment attempt and payment intent.
        }
        let intent_status_update = storage::PaymentIntentUpdate::ApproveUpdate {
            status: payment_data.payment_intent.status,
            merchant_decision: Some(api_models::enums::MerchantDecision::Approved.to_string()),
            updated_by: storage_scheme.to_string(),
        };
        payment_data.payment_intent = state
            .store
            .update_payment_intent(
                &state.into(),
                payment_data.payment_intent,
                intent_status_update,
                key_store,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        state
            .store
            .update_payment_attempt_with_attempt_id(
                payment_data.payment_attempt.clone(),
                storage::PaymentAttemptUpdate::StatusUpdate {
                    status: payment_data.payment_attempt.status,
                    updated_by: storage_scheme.to_string(),
                },
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentApprove))
            .with(payment_data.to_event())
            .emit();

        Ok((Box::new(self), payment_data))
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, api::PaymentsCaptureRequest, PaymentData<F>>
    for PaymentApprove
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &api::PaymentsCaptureRequest,
        merchant_context: &'a domain::MerchantContext,
    ) -> RouterResult<(PaymentApproveOperation<'b, F>, operations::ValidateResult)> {
        let request_merchant_id = request.merchant_id.as_ref();
        helpers::validate_merchant_id(
            merchant_context.get_merchant_account().get_id(),
            request_merchant_id,
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "merchant_id".to_string(),
            expected_format: "merchant_id from merchant account".to_string(),
        })?;

        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
                payment_id: api::PaymentIdType::PaymentIntentId(request.payment_id.clone()),
                storage_scheme: merchant_context.get_merchant_account().storage_scheme,
                requeue: false,
            },
        ))
    }
}


impl Default for AnalyticsConfig {
    fn default() -> Self {
        Self::Sqlx {
            sqlx: Database::default(),
            forex_enabled: false,
        }
    }
}


File: crates/router/src/types/domain/merchant_connector_account.rs

pub use hyperswitch_domain_models::merchant_connector_account::*;


pub struct DecideConnector;


pub fn get_wallet_data(&self) -> Option<&WalletData> {
        if let Self::Wallet(wallet_data) = self {
            Some(wallet_data)
        } else {
            None
        }
    }


impl RefundMetricsBucketIdentifier {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        currency: Option<Currency>,
        refund_status: Option<String>,
        connector: Option<String>,
        refund_type: Option<String>,
        profile_id: Option<String>,
        refund_reason: Option<String>,
        refund_error_message: Option<String>,
        normalized_time_range: TimeRange,
    ) -> Self {
        Self {
            currency,
            refund_status,
            connector,
            refund_type,
            profile_id,
            refund_reason,
            refund_error_message,
            time_bucket: normalized_time_range,
            start_time: normalized_time_range.start_time,
        }
    }
}