// Struct: UpdateContractRequestEventRequest
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Implementations: 0

pub struct UpdateContractRequestEventRequest


// File: crates/openapi/src/routes/payment_link.rs
// Module: openapi
// Public functions: 1

/// Payments Link - Retrieve
///
/// To retrieve the properties of a Payment Link. This may be used to get the status of a previously initiated payment or next action for an ongoing payment
#[utoipa::path(
    get,
    path = "/payment_link/{payment_link_id}",
    params(
        ("payment_link_id" = String, Path, description = "The identifier for payment link"),
        ("client_secret" = Option<String>, Query, description = "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK"),
    ),
    responses(
        (status = 200, description = "Gets details regarding payment link", body = RetrievePaymentLinkResponse),
        (status = 404, description = "No payment link found")
    ),
    tag = "Payments",
    operation_id = "Retrieve a Payment Link",
    security(("api_key" = []), ("publishable_key" = []))
)]
pub async fn payment_link_retrieve() {}


// Function: delete_conditional_config
// File: crates/router/src/core/conditional_config.rs
// Module: router

pub fn delete_conditional_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
) -> RouterResponse<()>


// Struct: HyperswitchAiInteraction
// File: crates/diesel_models/src/hyperswitch_ai_interaction.rs
// Module: diesel_models
// Implementations: 0

pub struct HyperswitchAiInteraction


// Implementation: impl api::MandateSetup for for Loonio
// File: crates/hyperswitch_connectors/src/connectors/loonio.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::MandateSetup for for Loonio


// Struct: AciWebhookCardDetails
// File: crates/hyperswitch_connectors/src/connectors/aci/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AciWebhookCardDetails


// Function: supports_access_token
// File: crates/common_enums/src/connector_enums.rs
// Module: common_enums

pub fn supports_access_token(self, payment_method: PaymentMethod) -> bool


// Struct: Bluecode
// File: crates/hyperswitch_connectors/src/connectors/bluecode.rs
// Module: hyperswitch_connectors
// Implementations: 17
// Traits: api::Payment, api::PaymentSession, api::ConnectorAccessToken, api::MandateSetup, api::PaymentAuthorize, api::PaymentSync, api::PaymentCapture, api::PaymentVoid, api::Refund, api::RefundExecute, api::RefundSync, api::PaymentToken, ConnectorCommon, ConnectorValidation, webhooks::IncomingWebhook, ConnectorSpecifications

pub struct Bluecode

api::Payment api::PaymentSession api::ConnectorAccessToken api::MandateSetup api::PaymentAuthorize api::PaymentSync api::PaymentCapture api::PaymentVoid api::Refund api::RefundExecute api::RefundSync api::PaymentToken ConnectorCommon ConnectorValidation webhooks::IncomingWebhook ConnectorSpecifications


// Function: new_token
// File: crates/router/src/services/authentication.rs
// Module: router

pub fn new_token(
        user_id: String,
        merchant_id: id_type::MerchantId,
        role_id: String,
        settings: &Settings,
        org_id: id_type::OrganizationId,
        profile_id: id_type::ProfileId,
        tenant_id: Option<id_type::TenantId>,
    ) -> UserResult<String>


// Function: supports_payout_eligibility
// File: crates/common_enums/src/connector_enums.rs
// Module: common_enums

pub fn supports_payout_eligibility(self, payout_method: Option<PayoutType>) -> bool


// Function: get_default_config_key
// File: crates/router/src/core/routing/helpers.rs
// Module: router

pub fn get_default_config_key(
    merchant_id: &str,
    transaction_type: &storage::enums::TransactionType,
) -> String


// Struct: PayoutListFilters
// File: crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct PayoutListFilters


// Implementation: impl api::PaymentVoid for for Bluecode
// File: crates/hyperswitch_connectors/src/connectors/bluecode.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentVoid for for Bluecode


// Function: insert_payment_intents
// File: crates/diesel_models/src/query/user/sample_data.rs
// Module: diesel_models

pub fn insert_payment_intents(
    conn: &PgPooledConn,
    batch: Vec<PaymentIntentNew>,
) -> StorageResult<Vec<PaymentIntent>>


// File: crates/hyperswitch_connectors/src/connectors/bluecode/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 1
// Public structs: 10

use std::collections::HashMap;

use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    pii::{Email, IpAddress},
    request::Method,
    types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self as connector_utils, PaymentsAuthorizeRequestData, RouterData as OtherRouterData},
};

pub struct BluecodeRouterData<T> {
    pub amount: FloatMajorUnit,
    pub router_data: T,
}

impl<T> From<(FloatMajorUnit, T)> for BluecodeRouterData<T> {
    fn from((amount, item): (FloatMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BluecodeMetadataObject {
    pub shop_name: String,
}

impl TryFrom<&Option<common_utils::pii::SecretSerdeValue>> for BluecodeMetadataObject {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        meta_data: &Option<common_utils::pii::SecretSerdeValue>,
    ) -> Result<Self, Self::Error> {
        let metadata = connector_utils::to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "metadata",
            })?;
        Ok(metadata)
    }
}

#[derive(Debug, Serialize, PartialEq)]
pub struct BluecodePaymentsRequest {
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub payment_provider: String,
    pub shop_name: String,
    pub reference: String,
    pub ip_address: Option<Secret<String, IpAddress>>,
    pub first_name: Secret<String>,
    pub last_name: Secret<String>,
    pub billing_address_country_code_iso: enums::CountryAlpha2,
    pub billing_address_city: String,
    pub billing_address_line1: Secret<String>,
    pub billing_address_postal_code: Secret<String>,
    pub webhook_url: String,
    pub success_url: String,
    pub failure_url: String,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct BluecodeCard {
    number: cards::CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    cvc: Secret<String>,
    complete: bool,
}

impl TryFrom<&BluecodeRouterData<&PaymentsAuthorizeRouterData>> for BluecodePaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &BluecodeRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if item.router_data.request.capture_method != Some(enums::CaptureMethod::Automatic) {
            return Err(errors::ConnectorError::FlowNotSupported {
                flow: format!("{:?}", item.router_data.request.capture_method),
                connector: "Bluecode".to_string(),
            }
            .into());
        }
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Wallet(WalletData::BluecodeRedirect {}) => {
                let bluecode_mca_metadata =
                    BluecodeMetadataObject::try_from(&item.router_data.connector_meta_data)?;
                Self::try_from((item, &bluecode_mca_metadata))
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

impl
    TryFrom<(
        &BluecodeRouterData<&PaymentsAuthorizeRouterData>,
        &BluecodeMetadataObject,
    )> for BluecodePaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        value: (
            &BluecodeRouterData<&PaymentsAuthorizeRouterData>,
            &BluecodeMetadataObject,
        ),
    ) -> Result<Self, Self::Error> {
        let item = value.0;

        Ok(Self {
            amount: item.amount,
            currency: item.router_data.request.currency,
            payment_provider: "bluecode_payment".to_string(),
            shop_name: value.1.shop_name.clone(),
            reference: item.router_data.payment_id.clone(),
            ip_address: item.router_data.request.get_ip_address_as_optional(),
            first_name: item.router_data.get_billing_first_name()?,
            last_name: item.router_data.get_billing_last_name()?,
            billing_address_country_code_iso: item.router_data.get_billing_country()?,
            billing_address_city: item.router_data.get_billing_city()?,
            billing_address_line1: item.router_data.get_billing_line1()?,
            billing_address_postal_code: item.router_data.get_billing_zip()?,
            webhook_url: item.router_data.request.get_webhook_url()?,
            success_url: item.router_data.request.get_router_return_url()?,
            failure_url: item.router_data.request.get_router_return_url()?,
        })
    }
}

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

impl TryFrom<&ConnectorAuthType> for BluecodeAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

impl From<BluecodePaymentStatus> for common_enums::AttemptStatus {
    fn from(item: BluecodePaymentStatus) -> Self {
        match item {
            BluecodePaymentStatus::ManualProcessing => Self::Pending,
            BluecodePaymentStatus::Pending | BluecodePaymentStatus::PaymentInitiated => {
                Self::AuthenticationPending
            }
            BluecodePaymentStatus::Failed => Self::Failure,
            BluecodePaymentStatus::Completed => Self::Charged,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BluecodePaymentsResponse {
    pub id: i64,
    pub order_id: String,
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub charged_amount: FloatMajorUnit,
    pub charged_currency: enums::Currency,
    pub status: BluecodePaymentStatus,
    pub payment_link: url::Url,
    pub etoken: Secret<String>,
    pub payment_request_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BluecodeSyncResponse {
    pub id: Option<i64>,
    pub order_id: String,
    pub user_id: Option<i64>,
    pub customer_id: Option<String>,
    pub customer_email: Option<Email>,
    pub customer_phone: Option<String>,
    pub status: BluecodePaymentStatus,
    pub payment_provider: Option<String>,
    pub payment_connector: Option<String>,
    pub payment_method: Option<String>,
    pub payment_method_type: Option<String>,
    pub shop_name: Option<String>,
    pub sender_name: Option<String>,
    pub sender_email: Option<String>,
    pub description: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub charged_amount: Option<FloatMajorUnit>,
    pub charged_amount_currency: Option<String>,
    pub charged_fx_amount: Option<FloatMajorUnit>,
    pub charged_fx_amount_currency: Option<enums::Currency>,
    pub is_underpaid: Option<bool>,
    pub billing_amount: Option<FloatMajorUnit>,
    pub billing_currency: Option<String>,
    pub language: Option<String>,
    pub ip_address: Option<Secret<String, IpAddress>>,
    pub first_name: Option<Secret<String>>,
    pub last_name: Option<Secret<String>>,
    pub billing_address_line1: Option<Secret<String>>,
    pub billing_address_city: Option<Secret<String>>,
    pub billing_address_postal_code: Option<Secret<String>>,
    pub billing_address_country: Option<String>,
    pub billing_address_country_code_iso: Option<enums::CountryAlpha2>,
    pub shipping_address_country_code_iso: Option<enums::CountryAlpha2>,
    pub success_url: Option<String>,
    pub failure_url: Option<String>,
    pub source: Option<String>,
    pub bonus_code: Option<String>,
    pub dob: Option<String>,
    pub fees_amount: Option<f64>,
    pub fx_margin_amount: Option<f64>,
    pub fx_margin_percent: Option<f64>,
    pub fees_percent: Option<f64>,
    pub reseller_id: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BluecodePaymentStatus {
    Pending,
    PaymentInitiated,
    ManualProcessing,
    Failed,
    Completed,
}

impl<F, T> TryFrom<ResponseRouterData<F, BluecodePaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BluecodePaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let url = item.response.payment_link.clone();
        let redirection_data = Some(RedirectForm::from((url, Method::Get)));
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.payment_request_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, BluecodeSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BluecodeSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: item.data.response,
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
pub struct BluecodeRefundRequest {
    pub amount: FloatMajorUnit,
}

impl<F> TryFrom<&BluecodeRouterData<&RefundsRouterData<F>>> for BluecodeRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &BluecodeRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount.to_owned(),
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Processing => Self::Pending,
            //TODO: Review mapping
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct BluecodeErrorResponse {
    pub message: String,
    pub context_data: HashMap<String, Value>,
}

pub(crate) fn get_bluecode_webhook_event(
    status: BluecodePaymentStatus,
) -> api_models::webhooks::IncomingWebhookEvent {
    match status {
        BluecodePaymentStatus::Completed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess
        }
        BluecodePaymentStatus::PaymentInitiated
        | BluecodePaymentStatus::ManualProcessing
        | BluecodePaymentStatus::Pending => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentProcessing
        }
        BluecodePaymentStatus::Failed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure
        }
    }
}

pub(crate) fn get_webhook_object_from_body(
    body: &[u8],
) -> CustomResult<BluecodeSyncResponse, common_utils::errors::ParsingError> {
    let webhook: BluecodeSyncResponse = body.parse_struct("BluecodeIncomingWebhook")?;

    Ok(webhook)
}

pub fn sort_and_minify_json(value: &Value) -> Result<String, errors::ConnectorError> {
    fn sort_value(val: &Value) -> Value {
        match val {
            Value::Object(map) => {
                let mut entries: Vec<_> = map.iter().collect();
                entries.sort_by_key(|(k, _)| k.to_owned());

                let sorted_map: Map<String, Value> = entries
                    .into_iter()
                    .map(|(k, v)| (k.clone(), sort_value(v)))
                    .collect();

                Value::Object(sorted_map)
            }
            Value::Array(arr) => Value::Array(arr.iter().map(sort_value).collect()),
            _ => val.clone(),
        }
    }

    let sorted_value = sort_value(value);
    serde_json::to_string(&sorted_value)
        .map_err(|_| errors::ConnectorError::WebhookBodyDecodingFailed)
}


// File: crates/scheduler/src/utils.rs
// Module: scheduler
// Public functions: 15

use std::sync;

use common_utils::errors::CustomResult;
use diesel_models::enums::{self, ProcessTrackerStatus};
pub use diesel_models::process_tracker as storage;
use error_stack::{report, ResultExt};
use redis_interface::{RedisConnectionPool, RedisEntryId};
use router_env::{instrument, tracing};
use uuid::Uuid;

use super::{
    consumer::{self, types::process_data, workflows},
    env::logger,
};
use crate::{
    configs::settings::SchedulerSettings, consumer::types::ProcessTrackerBatch, errors,
    flow::SchedulerFlow, metrics, SchedulerInterface, SchedulerSessionState,
};

pub async fn divide_and_append_tasks<T>(
    state: &T,
    flow: SchedulerFlow,
    tasks: Vec<storage::ProcessTracker>,
    settings: &SchedulerSettings,
) -> CustomResult<(), errors::ProcessTrackerError>
where
    T: SchedulerInterface + Send + Sync + ?Sized,
{
    let batches = divide(tasks, settings);
    // Safety: Assuming we won't deal with more than `u64::MAX` batches at once
    #[allow(clippy::as_conversions)]
    metrics::BATCHES_CREATED.add(batches.len() as u64, &[]); // Metrics
    for batch in batches {
        let result = update_status_and_append(state, flow, batch).await;
        match result {
            Ok(_) => (),
            Err(error) => logger::error!(?error),
        }
    }
    Ok(())
}

pub async fn update_status_and_append<T>(
    state: &T,
    flow: SchedulerFlow,
    pt_batch: ProcessTrackerBatch,
) -> CustomResult<(), errors::ProcessTrackerError>
where
    T: SchedulerInterface + Send + Sync + ?Sized,
{
    let process_ids: Vec<String> = pt_batch
        .trackers
        .iter()
        .map(|process| process.id.to_owned())
        .collect();
    match flow {
        SchedulerFlow::Producer => state
            .process_tracker_update_process_status_by_ids(
                process_ids,
                storage::ProcessTrackerUpdate::StatusUpdate {
                    status: ProcessTrackerStatus::Processing,
                    business_status: None,
                },
            )
            .await
            .map_or_else(
                |error| {
                    logger::error!(?error, "Error while updating process status");
                    Err(error.change_context(errors::ProcessTrackerError::ProcessUpdateFailed))
                },
                |count| {
                    logger::debug!("Updated status of {count} processes");
                    Ok(())
                },
            ),
        SchedulerFlow::Cleaner => {
            let res = state
                .reinitialize_limbo_processes(process_ids, common_utils::date_time::now())
                .await;
            match res {
                Ok(count) => {
                    logger::debug!("Reinitialized {count} processes");
                    Ok(())
                }
                Err(error) => {
                    logger::error!(?error, "Error while reinitializing processes");
                    Err(error.change_context(errors::ProcessTrackerError::ProcessUpdateFailed))
                }
            }
        }
        _ => {
            let error = format!("Unexpected scheduler flow {flow:?}");
            logger::error!(%error);
            Err(report!(errors::ProcessTrackerError::UnexpectedFlow).attach_printable(error))
        }
    }?;

    let field_value_pairs = pt_batch.to_redis_field_value_pairs()?;

    match state
        .stream_append_entry(
            &pt_batch.stream_name,
            &RedisEntryId::AutoGeneratedID,
            field_value_pairs,
        )
        .await
        .change_context(errors::ProcessTrackerError::BatchInsertionFailed)
    {
        Ok(x) => Ok(x),
        Err(mut err) => {
            let update_res = state
                .process_tracker_update_process_status_by_ids(
                    pt_batch
                        .trackers
                        .iter()
                        .map(|process| process.id.clone())
                        .collect(),
                    storage::ProcessTrackerUpdate::StatusUpdate {
                        status: ProcessTrackerStatus::Processing,
                        business_status: None,
                    },
                )
                .await
                .map_or_else(
                    |error| {
                        logger::error!(?error, "Error while updating process status");
                        Err(error.change_context(errors::ProcessTrackerError::ProcessUpdateFailed))
                    },
                    |count| {
                        logger::debug!("Updated status of {count} processes");
                        Ok(())
                    },
                );

            match update_res {
                Ok(_) => (),
                Err(inner_err) => {
                    err.extend_one(inner_err);
                }
            };

            Err(err)
        }
    }
}

pub fn divide(
    tasks: Vec<storage::ProcessTracker>,
    conf: &SchedulerSettings,
) -> Vec<ProcessTrackerBatch> {
    let now = common_utils::date_time::now();
    let batch_size = conf.producer.batch_size;
    divide_into_batches(batch_size, tasks, now, conf)
}

pub fn divide_into_batches(
    batch_size: usize,
    tasks: Vec<storage::ProcessTracker>,
    batch_creation_time: time::PrimitiveDateTime,
    conf: &SchedulerSettings,
) -> Vec<ProcessTrackerBatch> {
    let batch_id = Uuid::new_v4().to_string();

    tasks
        .chunks(batch_size)
        .fold(Vec::new(), |mut batches, item| {
            let batch = ProcessTrackerBatch {
                id: batch_id.clone(),
                group_name: conf.consumer.consumer_group.clone(),
                stream_name: conf.stream.clone(),
                connection_name: String::new(),
                created_time: batch_creation_time,
                rule: String::new(), // is it required?
                trackers: item.to_vec(),
            };
            batches.push(batch);

            batches
        })
}

pub async fn get_batches(
    conn: &RedisConnectionPool,
    stream_name: &str,
    group_name: &str,
    consumer_name: &str,
) -> CustomResult<Vec<ProcessTrackerBatch>, errors::ProcessTrackerError> {
    let response = match conn
        .stream_read_with_options(
            stream_name,
            RedisEntryId::UndeliveredEntryID,
            // Update logic for collecting to Vec and flattening, if count > 1 is provided
            Some(1),
            None,
            Some((group_name, consumer_name)),
        )
        .await
    {
        Ok(response) => response,
        Err(error) => {
            if let redis_interface::errors::RedisError::StreamEmptyOrNotAvailable =
                error.current_context()
            {
                logger::debug!("No batches processed as stream is empty");
                return Ok(Vec::new());
            } else {
                return Err(error.change_context(errors::ProcessTrackerError::BatchNotFound));
            }
        }
    };

    metrics::BATCHES_CONSUMED.add(1, &[]);

    let (batches, entry_ids): (Vec<Vec<ProcessTrackerBatch>>, Vec<Vec<String>>) = response.into_values().map(|entries| {
        entries.into_iter().try_fold(
            (Vec::new(), Vec::new()),
            |(mut batches, mut entry_ids), entry| {
                // Redis entry ID
                entry_ids.push(entry.0);
                // Value HashMap
                batches.push(ProcessTrackerBatch::from_redis_stream_entry(entry.1)?);

                Ok((batches, entry_ids))
            },
        )
    }).collect::<CustomResult<Vec<(Vec<ProcessTrackerBatch>, Vec<String>)>, errors::ProcessTrackerError>>()?
    .into_iter()
    .unzip();
    // Flattening the Vec's since the count provided above is 1. This needs to be updated if a
    // count greater than 1 is provided.
    let batches = batches.into_iter().flatten().collect::<Vec<_>>();
    let entry_ids = entry_ids.into_iter().flatten().collect::<Vec<_>>();

    conn.stream_acknowledge_entries(&stream_name.into(), group_name, entry_ids.clone())
        .await
        .map_err(|error| {
            logger::error!(?error, "Error acknowledging batch in stream");
            error.change_context(errors::ProcessTrackerError::BatchUpdateFailed)
        })?;
    conn.stream_delete_entries(&stream_name.into(), entry_ids.clone())
        .await
        .map_err(|error| {
            logger::error!(?error, "Error deleting batch from stream");
            error.change_context(errors::ProcessTrackerError::BatchDeleteFailed)
        })?;

    Ok(batches)
}

pub fn get_process_tracker_id<'a>(
    runner: storage::ProcessTrackerRunner,
    task_name: &'a str,
    txn_id: &'a str,
    merchant_id: &'a common_utils::id_type::MerchantId,
) -> String {
    format!(
        "{runner}_{task_name}_{txn_id}_{}",
        merchant_id.get_string_repr()
    )
}

pub fn get_process_tracker_id_for_dispute_list<'a>(
    runner: storage::ProcessTrackerRunner,
    merchant_connector_account_id: &'a common_utils::id_type::MerchantConnectorAccountId,
    created_from: time::PrimitiveDateTime,
    merchant_id: &'a common_utils::id_type::MerchantId,
) -> String {
    format!(
        "{runner}_{:04}{}{:02}{:02}_{}_{}",
        created_from.year(),
        created_from.month(),
        created_from.day(),
        created_from.hour(),
        merchant_connector_account_id.get_string_repr(),
        merchant_id.get_string_repr()
    )
}

pub fn get_time_from_delta(delta: Option<i32>) -> Option<time::PrimitiveDateTime> {
    delta.map(|t| common_utils::date_time::now().saturating_add(time::Duration::seconds(t.into())))
}

#[instrument(skip_all)]
pub async fn consumer_operation_handler<E, T>(
    state: T,
    settings: sync::Arc<SchedulerSettings>,
    error_handler_fun: E,
    workflow_selector: impl workflows::ProcessTrackerWorkflows<T> + 'static + Copy + std::fmt::Debug,
) where
    // Error handler function
    E: FnOnce(error_stack::Report<errors::ProcessTrackerError>),
    T: SchedulerSessionState + Send + Sync + 'static,
{
    match consumer::consumer_operations(&state, &settings, workflow_selector).await {
        Ok(_) => (),
        Err(err) => error_handler_fun(err),
    }
}

pub fn add_histogram_metrics(
    pickup_time: &time::PrimitiveDateTime,
    task: &mut storage::ProcessTracker,
    stream_name: &str,
) {
    #[warn(clippy::option_map_unit_fn)]
    if let Some((schedule_time, runner)) = task.schedule_time.as_ref().zip(task.runner.as_ref()) {
        let pickup_schedule_delta = (*pickup_time - *schedule_time).as_seconds_f64();
        logger::info!("Time delta for scheduled tasks: {pickup_schedule_delta} seconds");
        let runner_name = runner.clone();
        metrics::CONSUMER_OPS.record(
            pickup_schedule_delta,
            router_env::metric_attributes!((stream_name.to_owned(), runner_name)),
        );
    };
}

pub fn get_schedule_time(
    mapping: process_data::ConnectorPTMapping,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_count: i32,
) -> Option<i32> {
    let mapping = match mapping.custom_merchant_mapping.get(merchant_id) {
        Some(map) => map.clone(),
        None => mapping.default_mapping,
    };

    // For first try, get the `start_after` time
    if retry_count == 0 {
        Some(mapping.start_after)
    } else {
        get_delay(retry_count, &mapping.frequencies)
    }
}

pub fn get_pm_schedule_time(
    mapping: process_data::PaymentMethodsPTMapping,
    pm: enums::PaymentMethod,
    retry_count: i32,
) -> Option<i32> {
    let mapping = match mapping.custom_pm_mapping.get(&pm) {
        Some(map) => map.clone(),
        None => mapping.default_mapping,
    };

    if retry_count == 0 {
        Some(mapping.start_after)
    } else {
        get_delay(retry_count, &mapping.frequencies)
    }
}

pub fn get_outgoing_webhook_retry_schedule_time(
    mapping: process_data::OutgoingWebhookRetryProcessTrackerMapping,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_count: i32,
) -> Option<i32> {
    let retry_mapping = match mapping.custom_merchant_mapping.get(merchant_id) {
        Some(map) => map.clone(),
        None => mapping.default_mapping,
    };

    // For first try, get the `start_after` time
    if retry_count == 0 {
        Some(retry_mapping.start_after)
    } else {
        get_delay(retry_count, &retry_mapping.frequencies)
    }
}

pub fn get_pcr_payments_retry_schedule_time(
    mapping: process_data::RevenueRecoveryPaymentProcessTrackerMapping,
    merchant_id: &common_utils::id_type::MerchantId,
    retry_count: i32,
) -> Option<i32> {
    let mapping = match mapping.custom_merchant_mapping.get(merchant_id) {
        Some(map) => map.clone(),
        None => mapping.default_mapping,
    };
    // TODO: check if the current scheduled time is not more than the configured timerange

    // For first try, get the `start_after` time
    if retry_count == 0 {
        Some(mapping.start_after)
    } else {
        get_delay(retry_count, &mapping.frequencies)
    }
}

/// Get the delay based on the retry count
pub fn get_delay<'a>(
    retry_count: i32,
    frequencies: impl IntoIterator<Item = &'a (i32, i32)>,
) -> Option<i32> {
    // Preferably, fix this by using unsigned ints
    if retry_count <= 0 {
        return None;
    }

    let mut cumulative_count = 0;
    for &(frequency, count) in frequencies.into_iter() {
        cumulative_count += count;
        if cumulative_count >= retry_count {
            return Some(frequency);
        }
    }

    None
}

pub(crate) async fn lock_acquire_release<T, F, Fut>(
    state: &T,
    settings: &SchedulerSettings,
    callback: F,
) -> CustomResult<(), errors::ProcessTrackerError>
where
    F: Fn() -> Fut,
    T: SchedulerInterface + Send + Sync + ?Sized,
    Fut: futures::Future<Output = CustomResult<(), errors::ProcessTrackerError>>,
{
    let tag = "PRODUCER_LOCK";
    let lock_key = &settings.producer.lock_key;
    let lock_val = "LOCKED";
    let ttl = settings.producer.lock_ttl;

    if state
        .acquire_pt_lock(tag, lock_key, lock_val, ttl)
        .await
        .change_context(errors::ProcessTrackerError::ERedisError(
            errors::RedisError::RedisConnectionError.into(),
        ))?
    {
        let result = callback().await;
        state
            .release_pt_lock(tag, lock_key)
            .await
            .map_err(errors::ProcessTrackerError::ERedisError)?;
        result
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_delay() {
        let frequency_count = vec![(300, 10), (600, 5), (1800, 3), (3600, 2)];

        let retry_counts_and_expected_delays = [
            (-4, None),
            (-2, None),
            (0, None),
            (4, Some(300)),
            (7, Some(300)),
            (10, Some(300)),
            (12, Some(600)),
            (16, Some(1800)),
            (18, Some(1800)),
            (20, Some(3600)),
            (24, None),
            (30, None),
        ];

        for (retry_count, expected_delay) in retry_counts_and_expected_delays {
            let delay = get_delay(retry_count, &frequency_count);

            assert_eq!(
                delay, expected_delay,
                "Delay and expected delay differ for `retry_count` = {retry_count}"
            );
        }
    }
}


// Implementation: impl ConnectorCommon for for Trustpayments
// File: crates/hyperswitch_connectors/src/connectors/trustpayments.rs
// Module: hyperswitch_connectors
// Methods: 6 total (0 public)

impl ConnectorCommon for for Trustpayments


// Implementation: impl api::ConnectorAccessToken for for Xendit
// File: crates/hyperswitch_connectors/src/connectors/xendit.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorAccessToken for for Xendit


// Struct: GlobalpayCancelRequest
// File: crates/hyperswitch_connectors/src/connectors/globalpay/requests.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct GlobalpayCancelRequest


// File: crates/router/src/routes/payouts.rs
// Module: router
// Public functions: 13

use actix_web::{
    body::{BoxBody, MessageBody},
    web, HttpRequest, HttpResponse, Responder,
};
use common_utils::id_type;
use router_env::{instrument, tracing, Flow};

use super::app::AppState;
use crate::{
    core::{api_locking, payouts::*},
    services::{
        api,
        authentication::{self as auth},
        authorization::permissions::Permission,
    },
    types::{api::payouts as payout_types, domain},
};

/// Payouts - Create
#[instrument(skip_all, fields(flow = ?Flow::PayoutsCreate))]
pub async fn payouts_create(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<payout_types::PayoutCreateRequest>,
) -> HttpResponse {
    let flow = Flow::PayoutsCreate;

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        json_payload.into_inner(),
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payouts_create_core(state, merchant_context, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[cfg(all(feature = "v1", feature = "payouts"))]
/// Payouts - Retrieve
#[instrument(skip_all, fields(flow = ?Flow::PayoutsRetrieve))]
pub async fn payouts_retrieve(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::PayoutId>,
    query_params: web::Query<payout_types::PayoutRetrieveBody>,
) -> HttpResponse {
    let payout_retrieve_request = payout_types::PayoutRetrieveRequest {
        payout_id: path.into_inner(),
        force_sync: query_params.force_sync.to_owned(),
        merchant_id: query_params.merchant_id.to_owned(),
    };
    let flow = Flow::PayoutsRetrieve;

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payout_retrieve_request,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payouts_retrieve_core(state, merchant_context, auth.profile_id, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfilePayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Payouts - Update
#[instrument(skip_all, fields(flow = ?Flow::PayoutsUpdate))]
pub async fn payouts_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::PayoutId>,
    json_payload: web::Json<payout_types::PayoutCreateRequest>,
) -> HttpResponse {
    let flow = Flow::PayoutsUpdate;
    let payout_id = path.into_inner();
    let mut payout_update_payload = json_payload.into_inner();
    payout_update_payload.payout_id = Some(payout_id);
    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payout_update_payload,
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payouts_update_core(state, merchant_context, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::PayoutsConfirm))]
pub async fn payouts_confirm(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<payout_types::PayoutCreateRequest>,
    path: web::Path<id_type::PayoutId>,
) -> HttpResponse {
    let flow = Flow::PayoutsConfirm;
    let mut payload = json_payload.into_inner();
    let payout_id = path.into_inner();
    tracing::Span::current().record("payout_id", payout_id.get_string_repr());
    payload.payout_id = Some(payout_id);
    payload.confirm = Some(true);
    let api_auth = auth::ApiKeyAuth::default();

    let (auth_type, _auth_flow) =
        match auth::check_client_secret_and_get_auth(req.headers(), &payload, api_auth) {
            Ok(auth) => auth,
            Err(e) => return api::log_and_return_error_response(e),
        };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload,
        |state, auth, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payouts_confirm_core(state, merchant_context, req)
        },
        &*auth_type,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - Cancel
#[instrument(skip_all, fields(flow = ?Flow::PayoutsCancel))]
pub async fn payouts_cancel(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::PayoutId>,
) -> HttpResponse {
    let flow = Flow::PayoutsCancel;
    let payload = payout_types::PayoutActionRequest {
        payout_id: path.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),
            ));
            payouts_cancel_core(state, merchant_context, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}
/// Payouts - Fulfill
#[instrument(skip_all, fields(flow = ?Flow::PayoutsFulfill))]
pub async fn payouts_fulfill(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<id_type::PayoutId>,
) -> HttpResponse {
    let flow = Flow::PayoutsFulfill;
    let payload = payout_types::PayoutActionRequest {
        payout_id: path.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),
            ));
            payouts_fulfill_core(state, merchant_context, req)
        },
        &auth::HeaderAuth(auth::ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        }),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - List
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
pub async fn payouts_list(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Query<payout_types::PayoutListConstraints>,
) -> HttpResponse {
    let flow = Flow::PayoutsList;
    let payload = json_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),
            ));
            payouts_list_core(state, merchant_context, None, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantPayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - List Profile
#[cfg(all(feature = "olap", feature = "payouts", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
pub async fn payouts_list_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Query<payout_types::PayoutListConstraints>,
) -> HttpResponse {
    let flow = Flow::PayoutsList;
    let payload = json_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),
            ));
            payouts_list_core(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfilePayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - Filtered list
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
pub async fn payouts_list_by_filter(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<payout_types::PayoutListFilterConstraints>,
) -> HttpResponse {
    let flow = Flow::PayoutsList;
    let payload = json_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),
            ));
            payouts_filtered_list_core(state, merchant_context, None, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantPayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - Filtered list
#[cfg(all(feature = "olap", feature = "payouts", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsList))]
pub async fn payouts_list_by_filter_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<payout_types::PayoutListFilterConstraints>,
) -> HttpResponse {
    let flow = Flow::PayoutsList;
    let payload = json_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),
            ));
            payouts_filtered_list_core(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfilePayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - Available filters for Merchant
#[cfg(feature = "olap")]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsFilter))]
pub async fn payouts_list_available_filters_for_merchant(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<common_utils::types::TimeRange>,
) -> HttpResponse {
    let flow = Flow::PayoutsFilter;
    let payload = json_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),
            ));
            payouts_list_available_filters_core(state, merchant_context, None, req)
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::MerchantPayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

/// Payouts - Available filters for Profile
#[cfg(all(feature = "olap", feature = "payouts", feature = "v1"))]
#[instrument(skip_all, fields(flow = ?Flow::PayoutsFilter))]
pub async fn payouts_list_available_filters_for_profile(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<common_utils::types::TimeRange>,
) -> HttpResponse {
    let flow = Flow::PayoutsFilter;
    let payload = json_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),
            ));
            payouts_list_available_filters_core(
                state,
                merchant_context,
                auth.profile_id.map(|profile_id| vec![profile_id]),
                req,
            )
        },
        auth::auth_type(
            &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: false,
            }),
            &auth::JWTAuth {
                permission: Permission::ProfilePayoutRead,
            },
            req.headers(),
        ),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}

#[instrument(skip_all, fields(flow = ?Flow::PayoutsAccounts))]
// #[get("/accounts")]
pub async fn payouts_accounts() -> impl Responder {
    let _flow = Flow::PayoutsAccounts;
    http_response("accounts")
}

fn http_response<T: MessageBody + 'static>(response: T) -> HttpResponse<BoxBody> {
    HttpResponse::Ok().body(response)
}


    let connector_integration: services::BoxedPayoutConnectorIntegrationInterface<
        api::PoCancel,
        types::PayoutsData,
        types::PayoutsResponseData,
    > = connector_data.connector.get_connector_integration();

    // 3. Call connector service
    let router_data_resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payout_failed_response()?;

    // 4. Process data returned by the connector
    let db = &*state.store;
    match router_data_resp.response {
        Ok(payout_response_data) => {
            let status = payout_response_data
                .status
                .unwrap_or(payout_data.payout_attempt.status.to_owned());
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: None,
                error_message: None,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
        Err(err) => {
            let status = storage_enums::PayoutStatus::Failed;
            let (error_code, error_message) = (Some(err.code), Some(err.message));
            let (unified_code, unified_message) = helpers::get_gsm_record(
                state,
                error_code.clone(),
                error_message.clone(),
                payout_data.payout_attempt.connector.clone(),
                consts::PAYOUT_FLOW_STR,
            )
            .await
            .map_or(
                (
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_CODE.to_string()),
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_string()),
                ),
                |gsm| (gsm.unified_code, gsm.unified_message),
            );
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                status,
                error_code,
                error_message,
                is_eligible: None,
                unified_code: unified_code
                    .map(UnifiedCode::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_code",
                    })?,
                unified_message: unified_message
                    .map(UnifiedMessage::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_message",
                    })?,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
    };

    Ok(())
}

pub async fn fulfill_payout(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    connector_data: &api::ConnectorData,
    payout_data: &mut PayoutData,
) -> RouterResult<()> {
    // 1. Form Router data
    let mut router_data = core_utils::construct_payout_router_data(
        state,
        connector_data,
        merchant_context,
        payout_data,
    )
    .await?;

    // 2. Get/Create access token
    access_token::create_access_token(
        state,
        connector_data,
        merchant_context,
        &mut router_data,
        payout_data.payouts.payout_type.to_owned(),
    )
    .await?;

    // 3. Fetch connector integration details
    let connector_integration: services::BoxedPayoutConnectorIntegrationInterface<
        api::PoFulfill,
        types::PayoutsData,
        types::PayoutsResponseData,
    > = connector_data.connector.get_connector_integration();

    // 4. Call connector service
    let router_data_resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payout_failed_response()?;

    // 5. Process data returned by the connector
    let db = &*state.store;
    match router_data_resp.response {
        Ok(payout_response_data) => {
            let status = payout_response_data
                .status
                .unwrap_or(payout_data.payout_attempt.status.to_owned());
            payout_data.payouts.status = status;
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_response_data.connector_payout_id,
                status,
                error_code: payout_response_data.error_code,
                error_message: payout_response_data.error_message,
                is_eligible: payout_response_data.payout_eligible,
                unified_code: None,
                unified_message: None,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
            if payout_data.payouts.recurring
                && payout_data.payouts.payout_method_id.clone().is_none()
            {
                let payout_method_data = payout_data
                    .payout_method_data
                    .clone()
                    .get_required_value("payout_method_data")?;
                payout_data
                    .payouts
                    .customer_id
                    .clone()
                    .async_map(|customer_id| async move {
                        helpers::save_payout_data_to_locker(
                            state,
                            payout_data,
                            &customer_id,
                            &payout_method_data,
                            None,
                            merchant_context,
                        )
                        .await
                    })
                    .await
                    .transpose()
                    .attach_printable("Failed to save payout data to locker")?;
            }
        }
        Err(err) => {
            let status = storage_enums::PayoutStatus::Failed;
            let (error_code, error_message) = (Some(err.code), Some(err.message));
            let (unified_code, unified_message) = helpers::get_gsm_record(
                state,
                error_code.clone(),
                error_message.clone(),
                payout_data.payout_attempt.connector.clone(),
                consts::PAYOUT_FLOW_STR,
            )
            .await
            .map_or(
                (
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_CODE.to_string()),
                    Some(crate::consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_string()),
                ),
                |gsm| (gsm.unified_code, gsm.unified_message),
            );
            let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                status,
                error_code,
                error_message,
                is_eligible: None,
                unified_code: unified_code
                    .map(UnifiedCode::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_code",
                    })?,
                unified_message: unified_message
                    .map(UnifiedMessage::try_from)
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "unified_message",
                    })?,
            };
            payout_data.payout_attempt = db
                .update_payout_attempt(
                    &payout_data.payout_attempt,
                    updated_payout_attempt,
                    &payout_data.payouts,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payout_attempt in db")?;
            payout_data.payouts = db
                .update_payout(
                    &payout_data.payouts,
                    storage::PayoutsUpdate::StatusUpdate { status },
                    &payout_data.payout_attempt,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error updating payouts in db")?;
        }
    };

    Ok(())
}

pub async fn trigger_webhook_and_handle_response(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_data: &PayoutData,
) -> RouterResponse<payouts::PayoutCreateResponse> {
    let response = response_handler(state, merchant_context, payout_data).await?;
    utils::trigger_payouts_webhook(state, merchant_context, &response).await?;
    Ok(services::ApplicationResponse::Json(response))
}

pub async fn response_handler(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payout_data: &PayoutData,
) -> RouterResult<payouts::PayoutCreateResponse> {
    let payout_attempt = payout_data.payout_attempt.to_owned();
    let payouts = payout_data.payouts.to_owned();

    let payout_method_id: Option<String> = payout_data.payment_method.as_ref().map(|pm| {
        #[cfg(feature = "v1")]
        {
            pm.payment_method_id.clone()
        }

        #[cfg(feature = "v2")]
        {
            pm.id.clone().get_string_repr().to_string()
        }
    });

    let payout_link = payout_data.payout_link.to_owned();
    let billing_address = payout_data.billing_address.to_owned();
    let customer_details = payout_data.customer_details.to_owned();
    let customer_id = payouts.customer_id;
    let billing = billing_address.map(From::from);

    let translated_unified_message = helpers::get_translated_unified_code_and_message(
        state,
        payout_attempt.unified_code.as_ref(),
        payout_attempt.unified_message.as_ref(),
        &payout_data.current_locale,
    )
    .await?;

    let additional_payout_method_data = payout_attempt.additional_payout_method_data.clone();

    let payout_method_data =
        additional_payout_method_data.map(payouts::PayoutMethodDataResponse::from);

    let response = api::PayoutCreateResponse {
        payout_id: payouts.payout_id.to_owned(),
        merchant_id: merchant_context.get_merchant_account().get_id().to_owned(),
        merchant_order_reference_id: payout_attempt.merchant_order_reference_id.clone(),
        amount: payouts.amount,
        currency: payouts.destination_currency.to_owned(),
        connector: payout_attempt.connector,
        payout_type: payouts.payout_type.to_owned(),
        payout_method_data,
        billing,
        auto_fulfill: payouts.auto_fulfill,
        customer_id,
        email: customer_details.as_ref().and_then(|c| c.email.clone()),
        name: customer_details.as_ref().and_then(|c| c.name.clone()),
        phone: customer_details.as_ref().and_then(|c| c.phone.clone()),
        phone_country_code: customer_details
            .as_ref()
            .and_then(|c| c.phone_country_code.clone()),
        customer: customer_details
            .as_ref()
            .map(payment_api_types::CustomerDetailsResponse::foreign_from),
        client_secret: payouts.client_secret.to_owned(),
        return_url: payouts.return_url.to_owned(),
        business_country: payout_attempt.business_country,
        business_label: payout_attempt.business_label,
        description: payouts.description.to_owned(),
        entity_type: payouts.entity_type.to_owned(),
        recurring: payouts.recurring,
        metadata: payouts.metadata,
        merchant_connector_id: payout_attempt.merchant_connector_id.to_owned(),
        status: payout_attempt.status.to_owned(),
        error_message: payout_attempt.error_message.to_owned(),
        error_code: payout_attempt.error_code,
        profile_id: payout_attempt.profile_id,
        created: Some(payouts.created_at),
        connector_transaction_id: payout_attempt.connector_payout_id,
        priority: payouts.priority,
        attempts: None,
        unified_code: payout_attempt.unified_code,
        unified_message: translated_unified_message,
        payout_link: payout_link
            .map(|payout_link| {
                url::Url::parse(payout_link.url.peek()).map(|link| PayoutLinkResponse {
                    payout_link_id: payout_link.link_id,
                    link: link.into(),
                })
            })
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse payout link's URL")?,
        payout_method_id,
    };

    Ok(response)
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn payout_create_db_entries(
    _state: &SessionState,
    _merchant_context: &domain::MerchantContext,
    _req: &payouts::PayoutCreateRequest,
    _payout_id: &str,
    _profile_id: &str,
    _stored_payout_method_data: Option<&payouts::PayoutMethodData>,
    _locale: &str,
    _customer: Option<&domain::Customer>,
    _payment_method: Option<PaymentMethod>,
) -> RouterResult<PayoutData> {
    todo!()
}

// DB entries
#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
pub async fn payout_create_db_entries(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    req: &payouts::PayoutCreateRequest,
    payout_id: &id_type::PayoutId,
    profile_id: &id_type::ProfileId,
    stored_payout_method_data: Option<&payouts::PayoutMethodData>,
    locale: &str,
    customer: Option<&domain::Customer>,
    payment_method: Option<PaymentMethod>,
) -> RouterResult<PayoutData> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let customer_id = customer.map(|cust| cust.customer_id.clone());

    // Validate whether profile_id passed in request is valid and is linked to the merchant
    let business_profile = validate_and_get_business_profile(
        state,
        merchant_context.get_merchant_key_store(),
        profile_id,
        merchant_id,
    )
    .await?;

    let payout_link = match req.payout_link {
        Some(true) => Some(
            create_payout_link(
                state,
                &business_profile,
                &customer_id
                    .clone()
                    .get_required_value("customer.id when payout_link is true")?,
                merchant_id,
                req,
                payout_id,
                locale,
            )
            .await?,
        ),
        _ => None,
    };

    // Get or create billing address
    let (billing_address, address_id) = helpers::resolve_billing_address_for_payout(
        state,
        req.billing.as_ref(),
        None,
        payment_method.as_ref(),
        merchant_context,
        customer_id.as_ref(),
        payout_id,
    )
    .await?;

    // Make payouts entry
    let currency = req.currency.to_owned().get_required_value("currency")?;

    let (payout_method_id, payout_type) = match stored_payout_method_data {
        Some(payout_method_data) => (
            payment_method
                .as_ref()
                .map(|pm| pm.payment_method_id.clone()),
            Some(api_enums::PayoutType::foreign_from(payout_method_data)),
        ),
        None => {
            (
                payment_method
                    .as_ref()
                    .map(|pm| pm.payment_method_id.clone()),
                match req.payout_type {
                    None => payment_method
                        .as_ref()
                        .and_then(|pm| pm.payment_method)
                        .map(|payment_method_enum| {
                            api_enums::PayoutType::foreign_try_from(payment_method_enum)
                                .change_context(errors::ApiErrorResponse::InvalidRequestData {
                                    message: format!(
                                        "PaymentMethod {payment_method_enum:?} is not supported for payouts"
                                    ),
                                })
                                .attach_printable("Failed to convert PaymentMethod to PayoutType")
                        })
                        .transpose()?,
                    payout_type => payout_type,
                },
            )
        }
    };

    let client_secret = utils::generate_id(
        consts::ID_LENGTH,
        format!("payout_{}_secret", payout_id.get_string_repr()).as_str(),
    );
    let amount = MinorUnit::from(req.amount.unwrap_or(api::Amount::Zero));
    let status = if req.payout_method_data.is_some()
        || req.payout_token.is_some()
        || stored_payout_method_data.is_some()
    {
        match req.confirm {
            Some(true) => storage_enums::PayoutStatus::RequiresCreation,
            _ => storage_enums::PayoutStatus::RequiresConfirmation,
        }
    } else {
        storage_enums::PayoutStatus::RequiresPayoutMethodData
    };

    let payouts_req = storage::PayoutsNew {
        payout_id: payout_id.clone(),
        merchant_id: merchant_id.to_owned(),
        customer_id: customer_id.to_owned(),
        address_id: address_id.to_owned(),
        payout_type,
        amount,
        destination_currency: currency,
        source_currency: currency,
        description: req.description.to_owned(),
        recurring: req.recurring.unwrap_or(false),
        auto_fulfill: req.auto_fulfill.unwrap_or(false),
        return_url: req.return_url.to_owned(),
        entity_type: req.entity_type.unwrap_or_default(),
        payout_method_id,
        profile_id: profile_id.to_owned(),
        attempt_count: 1,
        metadata: req.metadata.clone(),
        confirm: req.confirm,
        payout_link_id: payout_link
            .clone()
            .map(|link_data| link_data.link_id.clone()),
        client_secret: Some(client_secret),
        priority: req.priority,
        status,
        created_at: common_utils::date_time::now(),
        last_modified_at: common_utils::date_time::now(),
    };
    let payouts = db
        .insert_payout(
            payouts_req,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayout {
            payout_id: payout_id.clone(),
        })
        .attach_printable("Error inserting payouts in db")?;
    // Make payout_attempt entry
    let payout_attempt_id = utils::get_payout_attempt_id(payout_id.get_string_repr(), 1);

    let additional_pm_data_value = req
        .payout_method_data
        .clone()
        .or(stored_payout_method_data.cloned())
        .async_and_then(|payout_method_data| async move {
            helpers::get_additional_payout_data(&payout_method_data, &*state.store, profile_id)
                .await
        })
        .await
        // If no payout method data in request but we have a stored payment method, populate from it
        .or_else(|| {
            payment_method.as_ref().and_then(|payment_method| {
                payment_method
                    .get_payment_methods_data()
                    .and_then(|pmd| pmd.get_additional_payout_method_data())
            })
        });

    let payout_attempt_req = storage::PayoutAttemptNew {
        payout_attempt_id: payout_attempt_id.to_string(),
        payout_id: payout_id.clone(),
        additional_payout_method_data: additional_pm_data_value,
        merchant_id: merchant_id.to_owned(),
        merchant_order_reference_id: req.merchant_order_reference_id.clone(),
        status,
        business_country: req.business_country.to_owned(),
        business_label: req.business_label.to_owned(),
        payout_token: req.payout_token.to_owned(),
        profile_id: profile_id.to_owned(),
        customer_id,
        address_id,
        connector: None,
        connector_payout_id: None,
        is_eligible: None,
        error_message: None,
        error_code: None,
        created_at: common_utils::date_time::now(),
        last_modified_at: common_utils::date_time::now(),
        merchant_connector_id: None,
        routing_info: None,
        unified_code: None,
        unified_message: None,
    };
    let payout_attempt = db
        .insert_payout_attempt(
            payout_attempt_req,
            &payouts,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayout {
            payout_id: payout_id.clone(),
        })
        .attach_printable("Error inserting payout_attempt in db")?;

    // Make PayoutData
    Ok(PayoutData {
        billing_address,
        business_profile,
        customer_details: customer.map(ToOwned::to_owned),
        merchant_connector_account: None,
        payouts,
        payout_attempt,
        payout_method_data: req
            .payout_method_data
            .as_ref()
            .cloned()
            .or(stored_payout_method_data.cloned()),
        should_terminate: false,
        profile_id: profile_id.to_owned(),
        payout_link,
        current_locale: locale.to_string(),
        payment_method,
        connector_transfer_method_id: None,
    })
}

#[cfg(feature = "v2")]
pub async fn make_payout_data(
    _state: &SessionState,
    _merchant_context: &domain::MerchantContext,
    _auth_profile_id: Option<id_type::ProfileId>,
    _req: &payouts::PayoutRequest,
    locale: &str,
) -> RouterResult<PayoutData> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn make_payout_data(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    auth_profile_id: Option<id_type::ProfileId>,
    req: &payouts::PayoutRequest,
    locale: &str,
) -> RouterResult<PayoutData> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    let payout_id = match req {
        payouts::PayoutRequest::PayoutActionRequest(r) => r.payout_id.clone(),
        payouts::PayoutRequest::PayoutCreateRequest(r) => {
            r.payout_id.clone().unwrap_or(id_type::PayoutId::generate())
        }
        payouts::PayoutRequest::PayoutRetrieveRequest(r) => r.payout_id.clone(),
    };

    let payouts = db
        .find_payout_by_merchant_id_payout_id(
            merchant_id,
            &payout_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
    core_utils::validate_profile_id_from_auth_layer(auth_profile_id, &payouts)?;

    let payout_attempt_id =
        utils::get_payout_attempt_id(payouts.payout_id.get_string_repr(), payouts.attempt_count);

    let mut payout_attempt = db
        .find_payout_attempt_by_merchant_id_payout_attempt_id(
            merchant_id,
            &payout_attempt_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;

    let customer_id = payouts.customer_id.as_ref();

    // We have to do this because the function that is being used to create / get address is from payments
    // which expects a payment_id
    let payout_id_as_payment_id_type = id_type::PaymentId::try_from(std::borrow::Cow::Owned(
        payouts.payout_id.get_string_repr().to_string(),
    ))
    .change_context(errors::ApiErrorResponse::InvalidRequestData {
        message: "payout_id contains invalid data".to_string(),
    })
    .attach_printable("Error converting payout_id to PaymentId type")?;

    let billing_address = payment_helpers::create_or_find_address_for_payment_by_request(
        state,
        None,
        payouts.address_id.as_deref(),
        merchant_id,
        customer_id,
        merchant_context.get_merchant_key_store(),
        &payout_id_as_payment_id_type,
        merchant_context.get_merchant_account().storage_scheme,
    )
    .await?
    .map(|addr| domain_models::address::Address::from(&addr));

    let payout_id = &payouts.payout_id;

    let customer_details = customer_id
        .async_map(|customer_id| async move {
            db.find_customer_optional_by_customer_id_merchant_id(
                &state.into(),
                customer_id,
                merchant_id,
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .map_err(|err| err.change_context(errors::ApiErrorResponse::InternalServerError))
            .attach_printable_lazy(|| {
                format!(
                    "Failed while fetching optional customer [id - {customer_id:?}] for payout [id - {}]", payout_id.get_string_repr()
                )
            })
        })
        .await
        .transpose()?
        .and_then(|c| c);
    let profile_id = payout_attempt.profile_id.clone();

    // Validate whether profile_id passed in request is valid and is linked to the merchant
    let business_profile = validate_and_get_business_profile(
        state,
        merchant_context.get_merchant_key_store(),
        &profile_id,
        merchant_id,
    )
    .await?;
    let payout_method_data_req = match req {
        payouts::PayoutRequest::PayoutCreateRequest(r) => r.payout_method_data.to_owned(),
        payouts::PayoutRequest::PayoutActionRequest(_) => {
            match payout_attempt.payout_token.to_owned() {
                Some(payout_token) => {
                    let customer_id = customer_details
                        .as_ref()
                        .map(|cd| cd.customer_id.to_owned())
                        .get_required_value("customer_id when payout_token is sent")?;
                    helpers::make_payout_method_data(
                        state,
                        None,
                        Some(&payout_token),
                        &customer_id,
                        merchant_context.get_merchant_account().get_id(),
                        payouts.payout_type,
                        merchant_context.get_merchant_key_store(),
                        None,
                        merchant_context.get_merchant_account().storage_scheme,
                    )
                    .await?
                }
                None => None,
            }
        }
        payouts::PayoutRequest::PayoutRetrieveRequest(_) => None,
    };

    if let Some(payout_method_data) = payout_method_data_req.clone() {
        let additional_payout_method_data =
            helpers::get_additional_payout_data(&payout_method_data, &*state.store, &profile_id)
                .await;

        let update_additional_payout_method_data =
            storage::PayoutAttemptUpdate::AdditionalPayoutMethodDataUpdate {
                additional_payout_method_data,
            };

        payout_attempt = db
            .update_payout_attempt(
                &payout_attempt,
                update_additional_payout_method_data,
                &payouts,
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error updating additional payout method data in payout_attempt")?;
    };

    let merchant_connector_account =
        if payout_attempt.connector.is_some() && payout_attempt.merchant_connector_id.is_some() {
            let connector_name = payout_attempt
                .connector
                .clone()
                .get_required_value("connector_name")?;
            Some(
                payment_helpers::get_merchant_connector_account(
                    state,
                    merchant_context.get_merchant_account().get_id(),
                    None,
                    merchant_context.get_merchant_key_store(),
                    &profile_id,
                    connector_name.as_str(),
                    payout_attempt.merchant_connector_id.as_ref(),
                )
                .await?,
            )
        } else {
            None
        };

    let payout_link = payouts
        .payout_link_id
        .clone()
        .async_map(|link_id| async move {
            db.find_payout_link_by_link_id(&link_id)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error fetching payout links from db")
        })
        .await
        .transpose()?;

    let payment_method = payouts
        .payout_method_id
        .clone()
        .async_map(|pm_id| async move {
            db.find_payment_method(
                &(state.into()),
                merchant_context.get_merchant_key_store(),
                &pm_id,
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::PaymentMethodNotFound)
            .attach_printable("Unable to find payment method")
        })
        .await
        .transpose()?;

    Ok(PayoutData {
        billing_address,
        business_profile,
        customer_details,
        payouts,
        payout_attempt,
        payout_method_data: payout_method_data_req.to_owned(),
        merchant_connector_account,
        should_terminate: false,
        profile_id,
        payout_link,
        current_locale: locale.to_string(),
        payment_method,
        connector_transfer_method_id: None,
    })
}

pub async fn add_external_account_addition_task(
    db: &dyn StorageInterface,
    payout_data: &PayoutData,
    schedule_time: time::PrimitiveDateTime,
) -> CustomResult<(), errors::StorageError> {
    let runner = storage::ProcessTrackerRunner::AttachPayoutAccountWorkflow;
    let task = "STRPE_ATTACH_EXTERNAL_ACCOUNT";
    let tag = ["PAYOUTS", "STRIPE", "ACCOUNT", "CREATE"];
    let process_tracker_id = pt_utils::get_process_tracker_id(
        runner,
        task,
        &payout_data.payout_attempt.payout_attempt_id,
        &payout_data.payout_attempt.merchant_id,
    );
    let tracking_data = api::PayoutRetrieveRequest {
        payout_id: payout_data.payouts.payout_id.to_owned(),
        force_sync: None,
        merchant_id: Some(payout_data.payouts.merchant_id.to_owned()),
    };
    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        task,
        runner,
        tag,
        tracking_data,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
    )
    .map_err(errors::StorageError::from)?;

    db.insert_process(process_tracker_entry).await?;
    Ok(())
}

async fn validate_and_get_business_profile(
    state: &SessionState,
    merchant_key_store: &domain::MerchantKeyStore,
    profile_id: &id_type::ProfileId,
    merchant_id: &id_type::MerchantId,
) -> RouterResult<domain::Profile> {
    let db = &*state.store;
    let key_manager_state = &state.into();

    if let Some(business_profile) = core_utils::validate_and_get_business_profile(
        db,
        key_manager_state,
        merchant_key_store,
        Some(profile_id),
        merchant_id,
    )
    .await?
    {
        Ok(business_profile)
    } else {
        db.find_business_profile_by_profile_id(key_manager_state, merchant_key_store, profile_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })
    }
}

#[allow(clippy::too_many_arguments)]
pub async fn create_payout_link(
    state: &SessionState,
    business_profile: &domain::Profile,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    req: &payouts::PayoutCreateRequest,
    payout_id: &id_type::PayoutId,
    locale: &str,
) -> RouterResult<PayoutLink> {
    let payout_link_config_req = req.payout_link_config.to_owned();

    // Fetch all configs
    let default_config = &state.conf.generic_link.payout_link;
    let profile_config = &business_profile.payout_link_config;
    let profile_ui_config = profile_config.as_ref().map(|c| c.config.ui_config.clone());
    let ui_config = payout_link_config_req
        .as_ref()
        .and_then(|config| config.ui_config.clone())
        .or(profile_ui_config);

    let test_mode_in_config = payout_link_config_req
        .as_ref()
        .and_then(|config| config.test_mode)
        .or_else(|| profile_config.as_ref().and_then(|c| c.payout_test_mode));
    let is_test_mode_enabled = test_mode_in_config.unwrap_or(false);

    let allowed_domains = match (router_env::which(), is_test_mode_enabled) {
        // Throw error in case test_mode was enabled in production
        (Env::Production, true) => Err(report!(errors::ApiErrorResponse::LinkConfigurationError {
            message: "test_mode cannot be true for creating payout_links in production".to_string()
        })),
        // Send empty set of whitelisted domains
        (_, true) => {
            Ok(HashSet::new())
        },
        // Otherwise, fetch and use allowed domains from profile config
        (_, false) => {
            profile_config
            .as_ref()
            .map(|config| config.config.allowed_domains.to_owned())
            .get_required_value("allowed_domains")
            .change_context(errors::ApiErrorResponse::LinkConfigurationError {
                message:
                    "Payout links cannot be used without setting allowed_domains in profile. If you're using a non-production environment, you can set test_mode to true while in payout_link_config"
                        .to_string(),
            })
        }
    }?;

    // Form data to be injected in the link
    let (logo, merchant_name, theme) = match ui_config {
        Some(config) => (config.logo, config.merchant_name, config.theme),
        _ => (None, None, None),
    };
    let payout_link_config = GenericLinkUiConfig {
        logo,
        merchant_name,
        theme,
    };
    let client_secret = utils::generate_id(consts::ID_LENGTH, "payout_link_secret");
    let base_url = profile_config
        .as_ref()
        .and_then(|c| c.config.domain_name.as_ref())
        .map(|domain| format!("https://{domain}"))
        .unwrap_or(state.base_url.clone());
    let session_expiry = req
        .session_expiry
        .as_ref()
        .map_or(default_config.expiry, |expiry| *expiry);
    let url = format!(
        "{base_url}/payout_link/{}/{}?locale={}",
        merchant_id.get_string_repr(),
        payout_id.get_string_repr(),
        locale
    );
    let link = url::Url::parse(&url)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| format!("Failed to form payout link URL - {url}"))?;
    let req_enabled_payment_methods = payout_link_config_req
        .as_ref()
        .and_then(|req| req.enabled_payment_methods.to_owned());
    let amount = req
        .amount
        .as_ref()
        .get_required_value("amount")
        .attach_printable("amount is a required value when creating payout links")?;
    let currency = req
        .currency
        .as_ref()
        .get_required_value("currency")
        .attach_printable("currency is a required value when creating payout links")?;
    let payout_link_id = core_utils::get_or_generate_id(
        "payout_link_id",
        &payout_link_config_req
            .as_ref()
            .and_then(|config| config.payout_link_id.clone()),
        "payout_link",
    )?;
    let form_layout = payout_link_config_req
        .as_ref()
        .and_then(|config| config.form_layout.to_owned())
        .or_else(|| {
            profile_config
                .as_ref()
                .and_then(|config| config.form_layout.to_owned())
        });

    let data = PayoutLinkData {
        payout_link_id: payout_link_id.clone(),
        customer_id: customer_id.clone(),
        payout_id: payout_id.clone(),
        link,
        client_secret: Secret::new(client_secret),
        session_expiry,
        ui_config: payout_link_config,
        enabled_payment_methods: req_enabled_payment_methods,
        amount: MinorUnit::from(*amount),
        currency: *currency,
        allowed_domains,
        form_layout,
        test_mode: test_mode_in_config,
    };

    create_payout_link_db_entry(state, merchant_id, &data, req.return_url.clone()).await
}

pub async fn create_payout_link_db_entry(
    state: &SessionState,
    merchant_id: &id_type::MerchantId,
    payout_link_data: &PayoutLinkData,
    return_url: Option<String>,
) -> RouterResult<PayoutLink> {
    let db: &dyn StorageInterface = &*state.store;

    let link_data = serde_json::to_value(payout_link_data)
        .map_err(|_| report!(errors::ApiErrorResponse::InternalServerError))
        .attach_printable("Failed to convert PayoutLinkData to Value")?;

    let payout_link = GenericLinkNew {
        link_id: payout_link_data.payout_link_id.to_string(),


// Implementation: impl PaymentMethodNew
// File: crates/diesel_models/src/payment_method.rs
// Module: diesel_models
// Methods: 3 total (3 public)

impl PaymentMethodNew


// Implementation: impl IncomingWebhook for for Zen
// File: crates/hyperswitch_connectors/src/connectors/zen.rs
// Module: hyperswitch_connectors
// Methods: 8 total (0 public)

impl IncomingWebhook for for Zen


// Function: construct_profile_id_and_get_mca
// File: crates/router/src/core/payments.rs
// Module: router

pub fn construct_profile_id_and_get_mca<'a, F, D>(
    state: &'a SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: &D,
    connector_name: &str,
    merchant_connector_id: Option<&id_type::MerchantConnectorAccountId>,
    _should_validate: bool,
) -> RouterResult<helpers::MerchantConnectorAccountType>
where
    F: Clone,
    D: OperationSessionGetters<F> + Send + Sync + Clone,


// Function: store_payment_method_data_in_locker
// File: crates/router/src/core/payment_methods/vault.rs
// Module: router

pub fn store_payment_method_data_in_locker(
        state: &routes::SessionState,
        token_id: Option<String>,
        payment_method: &domain::PaymentMethodData,
        customer_id: Option<id_type::CustomerId>,
        pm: enums::PaymentMethod,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<String>


// Struct: BacsBankDebitAdditionalData
// File: crates/api_models/src/payments/additional_info.rs
// Module: api_models
// Implementations: 0

pub struct BacsBankDebitAdditionalData


// Implementation: impl api::PaymentToken for for Custombilling
// File: crates/hyperswitch_connectors/src/connectors/custombilling.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentToken for for Custombilling


// File: crates/euclid_macros/src/inner/enum_nums.rs
// Module: euclid_macros
// Public functions: 1

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;

fn error() -> TokenStream2 {
    syn::Error::new(
        Span::call_site(),
        "'EnumNums' can only be derived on enums with unit variants".to_string(),
    )
    .to_compile_error()
}

pub(crate) fn enum_nums_inner(ts: TokenStream) -> TokenStream {
    let derive_input = syn::parse_macro_input!(ts as syn::DeriveInput);

    let enum_obj = match derive_input.data {
        syn::Data::Enum(e) => e,
        _ => return error().into(),
    };

    let enum_name = derive_input.ident;

    let mut match_arms = Vec::<TokenStream2>::with_capacity(enum_obj.variants.len());

    for (i, variant) in enum_obj.variants.iter().enumerate() {
        match variant.fields {
            syn::Fields::Unit => {}
            _ => return error().into(),
        }

        let var_ident = &variant.ident;

        match_arms.push(quote! { Self::#var_ident => #i });
    }

    let impl_block = quote! {
        impl #enum_name {
            pub fn to_num(&self) -> usize {
                match self {
                    #(#match_arms),*
                }
            }
        }
    };

    impl_block.into()
}


// Function: get_two_digit_expiry_year
// File: crates/common_types/src/payments.rs
// Module: common_types

// Documentation: Get the four-digit expiration year from the Apple Pay pre-decrypt data

pub fn get_two_digit_expiry_year(&self) -> Result<Secret<String>, errors::ValidationError>


// Struct: SilverflowWebhookEventData
// File: crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct SilverflowWebhookEventData