File: crates/scheduler/src/consumer.rs
 Public functions: 7

// TODO: Figure out what to log

use std::{
    sync::{self, atomic},
    time as std_time,
};
pub mod types;
pub mod workflows;

use common_utils::{errors::CustomResult, id_type, signals::get_allowed_signals};
use diesel_models::enums;
pub use diesel_models::{self, process_tracker as storage};
use error_stack::ResultExt;
use futures::future;
use redis_interface::{RedisConnectionPool, RedisEntryId};
use router_env::{
    instrument,
    tracing::{self, Instrument},
};
use time::PrimitiveDateTime;
use tokio::sync::mpsc;
use uuid::Uuid;

use super::env::logger;
pub use super::workflows::ProcessTrackerWorkflow;
use crate::{
    configs::settings::SchedulerSettings, db::process_tracker::ProcessTrackerInterface, errors,
    metrics, utils as pt_utils, SchedulerAppState, SchedulerInterface, SchedulerSessionState,
};

// Valid consumer business statuses
pub fn valid_business_statuses() -> Vec<&'static str> {
    vec![storage::business_status::PENDING]
}

#[instrument(skip_all)]
pub async fn start_consumer<T: SchedulerAppState + 'static, U: SchedulerSessionState + 'static, F>(
    state: &T,
    settings: sync::Arc<SchedulerSettings>,
    workflow_selector: impl workflows::ProcessTrackerWorkflows<U> + 'static + Copy + std::fmt::Debug,
    (tx, mut rx): (mpsc::Sender<()>, mpsc::Receiver<()>),
    app_state_to_session_state: F,
) -> CustomResult<(), errors::ProcessTrackerError>
where
    F: Fn(&T, &id_type::TenantId) -> CustomResult<U, errors::ProcessTrackerError>,
{
    use std::time::Duration;

    use rand::distributions::{Distribution, Uniform};

    let mut rng = rand::thread_rng();

    // TODO: this can be removed once rand-0.9 is released
    // reference - https://github.com/rust-random/rand/issues/1326#issuecomment-1635331942
    #[allow(unknown_lints)]
    #[allow(clippy::unnecessary_fallible_conversions)]
    let timeout = Uniform::try_from(0..=settings.loop_interval)
        .change_context(errors::ProcessTrackerError::ConfigurationError)?;

    tokio::time::sleep(Duration::from_millis(timeout.sample(&mut rng))).await;

    let mut interval = tokio::time::interval(Duration::from_millis(settings.loop_interval));

    let mut shutdown_interval =
        tokio::time::interval(Duration::from_millis(settings.graceful_shutdown_interval));

    let consumer_operation_counter = sync::Arc::new(atomic::AtomicU64::new(0));
    let signal = get_allowed_signals()
        .map_err(|error| {
            logger::error!(?error, "Signal Handler Error");
            errors::ProcessTrackerError::ConfigurationError
        })
        .attach_printable("Failed while creating a signals handler")?;
    let handle = signal.handle();
    let task_handle =
        tokio::spawn(common_utils::signals::signal_handler(signal, tx).in_current_span());

    'consumer: loop {
        match rx.try_recv() {
            Err(mpsc::error::TryRecvError::Empty) => {
                interval.tick().await;

                // A guard from env to disable the consumer
                if settings.consumer.disabled {
                    continue;
                }
                consumer_operation_counter.fetch_add(1, atomic::Ordering::SeqCst);
                let start_time = std_time::Instant::now();
                let tenants = state.get_tenants();
                for tenant in tenants {
                    let session_state = app_state_to_session_state(state, &tenant)?;
                    pt_utils::consumer_operation_handler(
                        session_state.clone(),
                        settings.clone(),
                        |error| {
                            logger::error!(?error, "Failed to perform consumer operation");
                        },
                        workflow_selector,
                    )
                    .await;
                }

                let end_time = std_time::Instant::now();
                let duration = end_time.saturating_duration_since(start_time).as_secs_f64();
                logger::debug!("Time taken to execute consumer_operation: {}s", duration);

                let current_count =
                    consumer_operation_counter.fetch_sub(1, atomic::Ordering::SeqCst);
                logger::info!("Current tasks being executed: {}", current_count);
            }
            Ok(()) | Err(mpsc::error::TryRecvError::Disconnected) => {
                logger::debug!("Awaiting shutdown!");
                rx.close();
                loop {
                    shutdown_interval.tick().await;
                    let active_tasks = consumer_operation_counter.load(atomic::Ordering::Acquire);
                    logger::info!("Active tasks: {active_tasks}");
                    match active_tasks {
                        0 => {
                            logger::info!("Terminating consumer");
                            break 'consumer;
                        }
                        _ => continue,
                    }
                }
            }
        }
    }
    handle.close();
    task_handle
        .await
        .change_context(errors::ProcessTrackerError::UnexpectedFlow)?;

    Ok(())
}

#[instrument(skip_all)]
pub async fn consumer_operations<T: SchedulerSessionState + 'static>(
    state: &T,
    settings: &SchedulerSettings,
    workflow_selector: impl workflows::ProcessTrackerWorkflows<T> + 'static + Copy + std::fmt::Debug,
) -> CustomResult<(), errors::ProcessTrackerError> {
    let stream_name = settings.stream.clone();
    let group_name = settings.consumer.consumer_group.clone();
    let consumer_name = format!("consumer_{}", Uuid::new_v4());

    let _group_created = &mut state
        .get_db()
        .consumer_group_create(&stream_name, &group_name, &RedisEntryId::AfterLastID)
        .await;

    let mut tasks = state
        .get_db()
        .as_scheduler()
        .fetch_consumer_tasks(&stream_name, &group_name, &consumer_name)
        .await?;

    if !tasks.is_empty() {
        logger::info!("{} picked {} tasks", consumer_name, tasks.len());
    }
    let mut handler = vec![];

    for task in tasks.iter_mut() {
        let pickup_time = common_utils::date_time::now();

        pt_utils::add_histogram_metrics(&pickup_time, task, &stream_name);

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

        handler.push(tokio::task::spawn(start_workflow(
            state.clone(),
            task.clone(),
            pickup_time,
            workflow_selector,
        )))
    }
    future::join_all(handler).await;

    Ok(())
}

#[instrument(skip(db, redis_conn))]
pub async fn fetch_consumer_tasks(
    db: &dyn ProcessTrackerInterface,
    redis_conn: &RedisConnectionPool,
    stream_name: &str,
    group_name: &str,
    consumer_name: &str,
) -> CustomResult<Vec<storage::ProcessTracker>, errors::ProcessTrackerError> {
    let batches = pt_utils::get_batches(redis_conn, stream_name, group_name, consumer_name).await?;

    // Returning early to avoid execution of database queries when `batches` is empty
    if batches.is_empty() {
        return Ok(Vec::new());
    }

    let mut tasks = batches.into_iter().fold(Vec::new(), |mut acc, batch| {
        acc.extend_from_slice(
            batch
                .trackers
                .into_iter()
                .filter(|task| task.is_valid_business_status(&valid_business_statuses()))
                .collect::<Vec<_>>()
                .as_slice(),
        );
        acc
    });
    let task_ids = tasks
        .iter()
        .map(|task| task.id.to_owned())
        .collect::<Vec<_>>();

    db.process_tracker_update_process_status_by_ids(
        task_ids,
        storage::ProcessTrackerUpdate::StatusUpdate {
            status: enums::ProcessTrackerStatus::ProcessStarted,
            business_status: None,
        },
    )
    .await
    .change_context(errors::ProcessTrackerError::ProcessFetchingFailed)?;
    tasks
        .iter_mut()
        .for_each(|x| x.status = enums::ProcessTrackerStatus::ProcessStarted);
    Ok(tasks)
}

// Accept flow_options if required
#[instrument(skip(state), fields(workflow_id))]
pub async fn start_workflow<T>(
    state: T,
    process: storage::ProcessTracker,
    _pickup_time: PrimitiveDateTime,
    workflow_selector: impl workflows::ProcessTrackerWorkflows<T> + 'static + std::fmt::Debug,
) -> CustomResult<(), errors::ProcessTrackerError>
where
    T: SchedulerSessionState,
{
    tracing::Span::current().record("workflow_id", Uuid::new_v4().to_string());
    logger::info!(pt.name=?process.name, pt.id=%process.id);
    let res = workflow_selector
        .trigger_workflow(&state.clone(), process.clone())
        .await
        .inspect_err(|error| {
            logger::error!(?error, "Failed to trigger workflow");
        });
    metrics::TASK_PROCESSED.add(1, &[]);
    res
}

#[instrument(skip_all)]
pub async fn consumer_error_handler(
    state: &(dyn SchedulerInterface + 'static),
    process: storage::ProcessTracker,
    error: errors::ProcessTrackerError,
) -> CustomResult<(), errors::ProcessTrackerError> {
    logger::error!(pt.name=?process.name, pt.id=%process.id, ?error, "Failed to execute workflow");

    state
        .process_tracker_update_process_status_by_ids(
            vec![process.id],
            storage::ProcessTrackerUpdate::StatusUpdate {
                status: enums::ProcessTrackerStatus::Finish,
                business_status: Some(String::from(storage::business_status::GLOBAL_ERROR)),
            },
        )
        .await
        .change_context(errors::ProcessTrackerError::ProcessUpdateFailed)?;
    Ok(())
}

pub async fn create_task(
    db: &dyn ProcessTrackerInterface,
    process_tracker_entry: storage::ProcessTrackerNew,
) -> CustomResult<(), storage_impl::errors::StorageError> {
    db.insert_process(process_tracker_entry).await?;
    Ok(())
}


pub struct PixTransactionRequest {
    pub subject_id: Secret<String>,              // Customer ID (UUID)
    pub from_bank_account_id: MaskedBankAccount, // UUID
    pub to_bank_account_id: MaskedBankAccount,   // UUID
    pub currency: api_models::enums::Currency,
    pub exchange_currency: api_models::enums::Currency,
    pub value: StringMajorUnit,
    pub use_dynamic_pix: bool,
    #[serde(default, with = "common_utils::custom_serde::iso8601")]
    pub dynamic_pix_expires_at: PrimitiveDateTime,
}


pub trait PaymentDistribution<T>
where
    T: AnalyticsDataSource + PaymentDistributionAnalytics,
{
    #[allow(clippy::too_many_arguments)]
    async fn load_distribution(
        &self,
        distribution: &PaymentDistributionBody,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<Vec<(PaymentMetricsBucketIdentifier, PaymentDistributionRow)>>;
}


pub async fn fetch_forex_rates_from_fallback_api(
    state: &SessionState,
) -> CustomResult<FxExchangeRatesCacheEntry, ForexError> {
    let fallback_forex_api_key = state.conf.forex_api.get_inner().fallback_api_key.peek();

    let fallback_forex_url: String = format!("{FALLBACK_FOREX_BASE_URL}{fallback_forex_api_key}");
    let fallback_forex_request = services::RequestBuilder::new()
        .method(services::Method::Get)
        .url(&fallback_forex_url)
        .build();

    logger::info!(fallback_forex_request=?fallback_forex_request,"forex_log: Fallback api call for forex fetch");
    let response = state
        .api_client
        .send_request(
            &state.clone(),
            fallback_forex_request,
            Some(FOREX_API_TIMEOUT),
            false,
        )
        .await
        .change_context(ForexError::ApiUnresponsive)
        .attach_printable("Fallback forex fetch api unresponsive")?;

    let fallback_forex_response = response
        .json::<FallbackForexResponse>()
        .await
        .change_context(ForexError::ParsingError)
        .attach_printable(
            "Unable to parse response received from fallback api into ForexResponse",
        )?;

    logger::info!(fallback_forex_response=?fallback_forex_response,"forex_log");

    let mut conversions: HashMap<enums::Currency, CurrencyFactors> = HashMap::new();
    for enum_curr in enums::Currency::iter() {
        match fallback_forex_response.quotes.get(
            format!(
                "{}{}",
                FALLBACK_FOREX_API_CURRENCY_PREFIX,
                &enum_curr.to_string()
            )
            .as_str(),
        ) {
            Some(rate) => {
                let from_factor = match Decimal::new(1, 0).checked_div(**rate) {
                    Some(rate) => rate,
                    None => {
                        logger::error!(
                            "forex_error: Rates for {} not received from API",
                            &enum_curr
                        );
                        continue;
                    }
                };
                let currency_factors = CurrencyFactors::new(**rate, from_factor);
                conversions.insert(enum_curr, currency_factors);
            }
            None => {
                if enum_curr == enums::Currency::USD {
                    let currency_factors =
                        CurrencyFactors::new(Decimal::new(1, 0), Decimal::new(1, 0));
                    conversions.insert(enum_curr, currency_factors);
                } else {
                    logger::error!(
                        "forex_error: Rates for {} not received from API",
                        &enum_curr
                    );
                }
            }
        };
    }

    let rates =
        FxExchangeRatesCacheEntry::new(ExchangeRates::new(enums::Currency::USD, conversions));
    match acquire_redis_lock(state).await {
        Ok(_) => {
            save_forex_data_to_cache_and_redis(state, rates.clone()).await?;
            Ok(rates)
        }
        Err(e) => Err(e),
    }
}


pub fn new(email: Secret<String, pii::EmailStrategy>) -> UserResult<Self> {
        use validator::ValidateEmail;

        let email_string = email.expose().to_lowercase();
        let email =
            pii::Email::from_str(&email_string).change_context(UserErrors::EmailParsingError)?;

        if email_string.validate_email() {
            let (_username, domain) = match email_string.as_str().split_once('@') {
                Some((u, d)) => (u, d),
                None => return Err(UserErrors::EmailParsingError.into()),
            };

            if BLOCKED_EMAIL.contains(domain) {
                return Err(UserErrors::InvalidEmailError.into());
            }
            Ok(Self(email))
        } else {
            Err(UserErrors::EmailParsingError.into())
        }
    }


impl RefundSync for Cybersource {}


pub struct AuthReversal {
    #[serde(rename = "@id")]
    pub id: String,
    #[serde(rename = "@reportGroup")]
    pub report_group: String,
    pub cnp_txn_id: String,
}


pub fn format_emv_field(id: &str, value: &str) -> String {
    format!("{id}{:02}{value}", value.len())
}


pub struct RazorpayRefundRequest {
    pub amount: MinorUnit,
}


pub struct RefundManualUpdateRequest {
    #[serde(skip)]
    pub refund_id: String,
    /// Merchant ID
    #[schema(value_type = String)]
    pub merchant_id: common_utils::id_type::MerchantId,
    /// The status for refund
    pub status: Option<RefundStatus>,
    /// The code for the error
    pub error_code: Option<String>,
    /// The error message
    pub error_message: Option<String>,
}


impl DummyConnectors {
    pub fn get_dummy_connector_id(self) -> &'static str {
        match self {
            Self::PhonyPay => "phonypay",
            Self::FauxPay => "fauxpay",
            Self::PretendPay => "pretendpay",
            Self::StripeTest => "stripe_test",
            Self::AdyenTest => "adyen_test",
            Self::CheckoutTest => "checkout_test",
            Self::PaypalTest => "paypal_test",
        }
    }
}


pub const fn new() -> &'static Self {
        &Self {
            #[cfg(feature = "payouts")]
            amount_converter: &MinorUnitForConnector,
        }
    }


pub async fn find_by_connector_transaction_id_payment_id_merchant_id(
        conn: &PgPooledConn,
        connector_transaction_id: &common_utils::types::ConnectorTransactionId,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::connector_transaction_id
                .eq(connector_transaction_id.get_id().to_owned())
                .and(dsl::payment_id.eq(payment_id.to_owned()))
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }


pub struct Upload;


File: crates/router/tests/connectors/chargebee.rs

use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData};
use masking::Secret;
use router::types::{self, api, storage::enums};
use test_utils::connector_auth;

use crate::utils::{self, ConnectorActions};

#[derive(Clone, Copy)]
struct ChargebeeTest;
impl ConnectorActions for ChargebeeTest {}
impl utils::Connector for ChargebeeTest {
    fn get_data(&self) -> api::ConnectorData {
        use router::connector::Chargebee;
        utils::construct_connector_data_old(
            Box::new(Chargebee::new()),
            types::Connector::Plaid,
            api::GetToken::Connector,
            None,
        )
    }

    fn get_auth_token(&self) -> types::ConnectorAuthType {
        utils::to_connector_auth_type(
            connector_auth::ConnectorAuthentication::new()
                .chargebee
                .expect("Missing connector authentication configuration")
                .into(),
        )
    }

    fn get_name(&self) -> String {
        "chargebee".to_string()
    }
}

static CONNECTOR: ChargebeeTest = ChargebeeTest {};

fn get_default_payment_info() -> Option<utils::PaymentInfo> {
    None
}

fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    None
}

// Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_only_authorize_payment() {
    let response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized);
}

// Captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Partially captures a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(
            payment_method_details(),
            Some(types::PaymentsCaptureData {
                amount_to_capture: 50,
                ..utils::PaymentCaptureType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let authorize_response = CONNECTOR
        .authorize_payment(payment_method_details(), get_default_payment_info())
        .await
        .expect("Authorize payment response");
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}

// Voids a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_void_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_void_payment(
            payment_method_details(),
            Some(types::PaymentsCancelData {
                connector_transaction_id: String::from(""),
                cancellation_reason: Some("requested_by_customer".to_string()),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .expect("Void payment response");
    assert_eq!(response.status, enums::AttemptStatus::Voided);
}

// Refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Synchronizes a refund using the manual capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_manually_captured_refund() {
    let refund_response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_make_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_auto_captured_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Charged,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                capture_method: Some(enums::CaptureMethod::Automatic),
                ..Default::default()
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(response.status, enums::AttemptStatus::Charged,);
}

// Refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_auto_captured_payment() {
    let response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_partially_refund_succeeded_payment() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        refund_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
    CONNECTOR
        .make_payment_and_multiple_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await;
}

// Synchronizes a refund using the automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_sync_refund() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Cards Negative scenarios
// Creates a payment with incorrect CVC.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_cvc: Secret::new("12345".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's security code is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry month.
#[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_exp_month: Secret::new("20".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration month is invalid.".to_string(),
    );
}

// Creates a payment with incorrect expiry year.
#[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                payment_method_data: PaymentMethodData::Card(Card {
                    card_exp_year: Secret::new("2000".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Your card's expiration year is invalid.".to_string(),
    );
}

// Voids a payment using automatic capture flow (Non 3DS).
#[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let void_response = CONNECTOR
        .void_payment(txn_id.unwrap(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        void_response.response.unwrap_err().message,
        "You cannot cancel this PaymentIntent because it has a status of succeeded."
    );
}

// Captures a payment using invalid connector payment id.
#[actix_web::test]
async fn should_fail_capture_for_invalid_payment() {
    let capture_response = CONNECTOR
        .capture_payment("123456789".to_string(), None, get_default_payment_info())
        .await
        .unwrap();
    assert_eq!(
        capture_response.response.unwrap_err().message,
        String::from("No such payment_intent: '123456789'")
    );
}

// Refunds a payment with refund amount higher than payment amount.
#[actix_web::test]
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
    let response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 150,
                ..utils::PaymentRefundType::default().0
            }),
            get_default_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "Refund amount (₹1.50) is greater than charge amount (₹1.00)",
    );
}

// Connector dependent test cases goes here

// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests


pub struct HipayRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}


File: crates/test_utils/tests/connectors/adyen_uk_wh_ui.rs

use thirtyfour::{prelude::*, WebDriver};

use crate::{selenium::*, tester};

struct AdyenSeleniumTest;

impl SeleniumTest for AdyenSeleniumTest {
    fn get_connector_name(&self) -> String {
        "adyen_uk".to_string()
    }
}

async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = AdyenSeleniumTest {};
    conn.make_webhook_test(
        web_driver,
        &format!("{CHECKOUT_BASE_URL}/saved/104"),
        vec![
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Assert(Assert::IsPresent("succeeded")),
        ],
        10,
        "succeeded",
    )
    .await?;
    Ok(())
}

#[test]
fn should_make_webhook_test() {
    tester!(should_make_webhook);
}


pub async fn find(
        conn: &PgPooledConn,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::connector
                .eq(connector)
                .and(dsl::flow.eq(flow))
                .and(dsl::sub_flow.eq(sub_flow))
                .and(dsl::code.eq(code))
                .and(dsl::message.eq(message)),
        )
        .await
    }


File: crates/euclid/src/frontend/ast/parser.rs
 Public functions: 24

use common_utils::types::MinorUnit;
use nom::{
    branch, bytes::complete, character::complete as pchar, combinator, error, multi, sequence,
};

use crate::{frontend::ast, types::DummyOutput};
pub type ParseResult<T, U> = nom::IResult<T, U, error::VerboseError<T>>;

pub enum EuclidError {
    InvalidPercentage(String),
    InvalidConnector(String),
    InvalidOperator(String),
    InvalidNumber(String),
}

pub trait EuclidParsable: Sized {
    fn parse_output(input: &str) -> ParseResult<&str, Self>;
}

impl EuclidParsable for DummyOutput {
    fn parse_output(input: &str) -> ParseResult<&str, Self> {
        let string_w = sequence::delimited(
            skip_ws(complete::tag("\"")),
            complete::take_while(|c| c != '"'),
            skip_ws(complete::tag("\"")),
        );
        let full_sequence = multi::many0(sequence::preceded(
            skip_ws(complete::tag(",")),
            sequence::delimited(
                skip_ws(complete::tag("\"")),
                complete::take_while(|c| c != '"'),
                skip_ws(complete::tag("\"")),
            ),
        ));
        let sequence = sequence::pair(string_w, full_sequence);
        error::context(
            "dummy_strings",
            combinator::map(
                sequence::delimited(
                    skip_ws(complete::tag("[")),
                    sequence,
                    skip_ws(complete::tag("]")),
                ),
                |out: (&str, Vec<&str>)| {
                    let mut first = out.1;
                    first.insert(0, out.0);
                    let v = first.iter().map(|s| s.to_string()).collect();
                    Self { outputs: v }
                },
            ),
        )(input)
    }
}
pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&'a str, O>
where
    F: FnMut(&'a str) -> ParseResult<&'a str, O> + 'a,
{
    sequence::preceded(pchar::multispace0, inner)
}

pub fn num_i64(input: &str) -> ParseResult<&str, i64> {
    error::context(
        "num_i32",
        combinator::map_res(
            complete::take_while1(|c: char| c.is_ascii_digit()),
            |o: &str| {
                o.parse::<i64>()
                    .map_err(|_| EuclidError::InvalidNumber(o.to_string()))
            },
        ),
    )(input)
}

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

pub fn identifier(input: &str) -> ParseResult<&str, String> {
    error::context(
        "identifier",
        combinator::map(
            sequence::pair(
                complete::take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
                complete::take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
            ),
            |out: (&str, &str)| out.0.to_string() + out.1,
        ),
    )(input)
}
pub fn percentage(input: &str) -> ParseResult<&str, u8> {
    error::context(
        "volume_split_percentage",
        combinator::map_res(
            sequence::terminated(
                complete::take_while_m_n(1, 2, |c: char| c.is_ascii_digit()),
                complete::tag("%"),
            ),
            |o: &str| {
                o.parse::<u8>()
                    .map_err(|_| EuclidError::InvalidPercentage(o.to_string()))
            },
        ),
    )(input)
}

pub fn number_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    error::context(
        "number_value",
        combinator::map(num_i64, |n| ast::ValueType::Number(MinorUnit::new(n))),
    )(input)
}

pub fn str_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    error::context(
        "str_value",
        combinator::map(string_str, ast::ValueType::StrValue),
    )(input)
}
pub fn enum_value_string(input: &str) -> ParseResult<&str, String> {
    combinator::map(
        sequence::pair(
            complete::take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
            complete::take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
        ),
        |out: (&str, &str)| out.0.to_string() + out.1,
    )(input)
}

pub fn enum_variant_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    error::context(
        "enum_variant_value",
        combinator::map(enum_value_string, ast::ValueType::EnumVariant),
    )(input)
}

pub fn number_array_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    fn num_minor_unit(input: &str) -> ParseResult<&str, MinorUnit> {
        combinator::map(num_i64, MinorUnit::new)(input)
    }
    let many_with_comma = multi::many0(sequence::preceded(
        skip_ws(complete::tag(",")),
        skip_ws(num_minor_unit),
    ));

    let full_sequence = sequence::pair(skip_ws(num_minor_unit), many_with_comma);

    error::context(
        "number_array_value",
        combinator::map(
            sequence::delimited(
                skip_ws(complete::tag("(")),
                full_sequence,
                skip_ws(complete::tag(")")),
            ),
            |tup: (MinorUnit, Vec<MinorUnit>)| {
                let mut rest = tup.1;
                rest.insert(0, tup.0);
                ast::ValueType::NumberArray(rest)
            },
        ),
    )(input)
}

pub fn enum_variant_array_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    let many_with_comma = multi::many0(sequence::preceded(
        skip_ws(complete::tag(",")),
        skip_ws(enum_value_string),
    ));

    let full_sequence = sequence::pair(skip_ws(enum_value_string), many_with_comma);

    error::context(
        "enum_variant_array_value",
        combinator::map(
            sequence::delimited(
                skip_ws(complete::tag("(")),
                full_sequence,
                skip_ws(complete::tag(")")),
            ),
            |tup: (String, Vec<String>)| {
                let mut rest = tup.1;
                rest.insert(0, tup.0);
                ast::ValueType::EnumVariantArray(rest)
            },
        ),
    )(input)
}

pub fn number_comparison(input: &str) -> ParseResult<&str, ast::NumberComparison> {
    let operator = combinator::map_res(
        branch::alt((
            complete::tag(">="),
            complete::tag("<="),
            complete::tag(">"),
            complete::tag("<"),
        )),
        |s: &str| match s {
            ">=" => Ok(ast::ComparisonType::GreaterThanEqual),
            "<=" => Ok(ast::ComparisonType::LessThanEqual),
            ">" => Ok(ast::ComparisonType::GreaterThan),
            "<" => Ok(ast::ComparisonType::LessThan),
            _ => Err(EuclidError::InvalidOperator(s.to_string())),
        },
    );

    error::context(
        "number_comparison",
        combinator::map(
            sequence::pair(operator, num_i64),
            |tup: (ast::ComparisonType, i64)| ast::NumberComparison {
                comparison_type: tup.0,
                number: MinorUnit::new(tup.1),
            },
        ),
    )(input)
}

pub fn number_comparison_array_value(input: &str) -> ParseResult<&str, ast::ValueType> {
    let many_with_comma = multi::many0(sequence::preceded(
        skip_ws(complete::tag(",")),
        skip_ws(number_comparison),
    ));

    let full_sequence = sequence::pair(skip_ws(number_comparison), many_with_comma);

    error::context(
        "number_comparison_array_value",
        combinator::map(
            sequence::delimited(
                skip_ws(complete::tag("(")),
                full_sequence,
                skip_ws(complete::tag(")")),
            ),
            |tup: (ast::NumberComparison, Vec<ast::NumberComparison>)| {
                let mut rest = tup.1;
                rest.insert(0, tup.0);
                ast::ValueType::NumberComparisonArray(rest)
            },
        ),
    )(input)
}

pub fn value_type(input: &str) -> ParseResult<&str, ast::ValueType> {
    error::context(
        "value_type",
        branch::alt((
            number_value,
            enum_variant_value,
            enum_variant_array_value,
            number_array_value,
            number_comparison_array_value,
            str_value,
        )),
    )(input)
}

pub fn comparison_type(input: &str) -> ParseResult<&str, ast::ComparisonType> {
    error::context(
        "comparison_operator",
        combinator::map_res(
            branch::alt((
                complete::tag("/="),
                complete::tag(">="),
                complete::tag("<="),
                complete::tag("="),
                complete::tag(">"),
                complete::tag("<"),
            )),
            |s: &str| match s {
                "/=" => Ok(ast::ComparisonType::NotEqual),
                ">=" => Ok(ast::ComparisonType::GreaterThanEqual),
                "<=" => Ok(ast::ComparisonType::LessThanEqual),
                "=" => Ok(ast::ComparisonType::Equal),
                ">" => Ok(ast::ComparisonType::GreaterThan),
                "<" => Ok(ast::ComparisonType::LessThan),
                _ => Err(EuclidError::InvalidOperator(s.to_string())),
            },
        ),
    )(input)
}

pub fn comparison(input: &str) -> ParseResult<&str, ast::Comparison> {
    error::context(
        "condition",
        combinator::map(
            sequence::tuple((
                skip_ws(complete::take_while1(|c: char| {
                    c.is_ascii_alphabetic() || c == '.' || c == '_'
                })),
                skip_ws(comparison_type),
                skip_ws(value_type),
            )),
            |tup: (&str, ast::ComparisonType, ast::ValueType)| ast::Comparison {
                lhs: tup.0.to_string(),
                comparison: tup.1,
                value: tup.2,
                metadata: std::collections::HashMap::new(),
            },
        ),
    )(input)
}

pub fn arbitrary_comparison(input: &str) -> ParseResult<&str, ast::Comparison> {
    error::context(
        "condition",
        combinator::map(
            sequence::tuple((
                skip_ws(string_str),
                skip_ws(comparison_type),
                skip_ws(string_str),
            )),
            |tup: (String, ast::ComparisonType, String)| ast::Comparison {
                lhs: "metadata".to_string(),
                comparison: tup.1,
                value: ast::ValueType::MetadataVariant(ast::MetadataValue {
                    key: tup.0,
                    value: tup.2,
                }),
                metadata: std::collections::HashMap::new(),
            },
        ),
    )(input)
}

pub fn comparison_array(input: &str) -> ParseResult<&str, Vec<ast::Comparison>> {
    let many_with_ampersand = error::context(
        "many_with_amp",
        multi::many0(sequence::preceded(skip_ws(complete::tag("&")), comparison)),
    );

    let full_sequence = sequence::pair(
        skip_ws(branch::alt((comparison, arbitrary_comparison))),
        many_with_ampersand,
    );

    error::context(
        "comparison_array",
        combinator::map(
            full_sequence,
            |tup: (ast::Comparison, Vec<ast::Comparison>)| {
                let mut rest = tup.1;
                rest.insert(0, tup.0);
                rest
            },
        ),
    )(input)
}

pub fn if_statement(input: &str) -> ParseResult<&str, ast::IfStatement> {
    let nested_block = sequence::delimited(
        skip_ws(complete::tag("{")),
        multi::many0(if_statement),
        skip_ws(complete::tag("}")),
    );

    error::context(
        "if_statement",
        combinator::map(
            sequence::pair(comparison_array, combinator::opt(nested_block)),
            |tup: (ast::IfCondition, Option<Vec<ast::IfStatement>>)| ast::IfStatement {
                condition: tup.0,
                nested: tup.1,
            },
        ),
    )(input)
}

pub fn rule_conditions_array(input: &str) -> ParseResult<&str, Vec<ast::IfStatement>> {
    error::context(
        "rules_array",
        sequence::delimited(
            skip_ws(complete::tag("{")),
            multi::many1(if_statement),
            skip_ws(complete::tag("}")),
        ),
    )(input)
}

pub fn rule<O: EuclidParsable>(input: &str) -> ParseResult<&str, ast::Rule<O>> {
    let rule_name = error::context(
        "rule_name",
        combinator::map(
            skip_ws(sequence::pair(
                complete::take_while1(|c: char| c.is_ascii_alphabetic() || c == '_'),
                complete::take_while(|c: char| c.is_ascii_alphanumeric() || c == '_'),
            )),
            |out: (&str, &str)| out.0.to_string() + out.1,
        ),
    );

    let connector_selection = error::context(
        "parse_output",
        sequence::preceded(skip_ws(complete::tag(":")), output),
    );

    error::context(
        "rule",
        combinator::map(
            sequence::tuple((rule_name, connector_selection, rule_conditions_array)),
            |tup: (String, O, Vec<ast::IfStatement>)| ast::Rule {
                name: tup.0,
                connector_selection: tup.1,
                statements: tup.2,
            },
        ),
    )(input)
}

pub fn output<O: EuclidParsable>(input: &str) -> ParseResult<&str, O> {
    O::parse_output(input)
}

pub fn default_output<O: EuclidParsable + 'static>(input: &str) -> ParseResult<&str, O> {
    error::context(
        "default_output",
        sequence::preceded(
            sequence::pair(skip_ws(complete::tag("default")), skip_ws(pchar::char(':'))),
            skip_ws(output),
        ),
    )(input)
}

pub fn program<O: EuclidParsable + 'static>(input: &str) -> ParseResult<&str, ast::Program<O>> {
    error::context(
        "program",
        combinator::map(
            sequence::pair(default_output, multi::many1(skip_ws(rule::<O>))),
            |tup: (O, Vec<ast::Rule<O>>)| ast::Program {
                default_selection: tup.0,
                rules: tup.1,
                metadata: std::collections::HashMap::new(),
            },
        ),
    )(input)
}


impl api::Payment for Stripe {}


Documentation: api-reference/intelligent-router-api-reference/static-routing/create-a-routing-rule.mdx
# Type: Doc File

---
openapi: post /euclid.EuclidService/Create
---


pub async fn refunds_metadata_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<refunds::RefundMetadataUpdateRequest>,
    path: web::Path<common_utils::id_type::GlobalRefundId>,
) -> HttpResponse {
    let flow = Flow::RefundsUpdate;

    let global_refund_id = path.into_inner();
    let internal_payload = internal_payload_types::RefundsGenericRequestWithResourceId {
        global_refund_id: global_refund_id.clone(),
        payment_id: None,
        payload: json_payload.into_inner(),
    };

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        internal_payload,
        |state, auth: auth::AuthenticationData, req, _| {
            refund_metadata_update_core(
                state,
                auth.merchant_account,
                req.payload,
                global_refund_id.clone(),
            )
        },
        &auth::V2ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct AffirmCancelRequest {
    pub reference_id: Option<String>,
    pub amount: i64,
    pub merchant_transaction_id: Option<String>,
}


pub async fn upsert_conditional_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    request: DecisionManager,
) -> RouterResponse<DecisionManagerRecord> {
    use common_utils::ext_traits::{Encode, OptionExt, ValueExt};
    use diesel_models::configs;
    use storage_impl::redis::cache;

    use super::routing::helpers::update_merchant_active_algorithm_ref;

    let db = state.store.as_ref();
    let (name, prog) = match request {
        DecisionManager::DecisionManagerv0(ccr) => {
            let name = ccr.name;

            let prog = ccr
                .algorithm
                .get_required_value("algorithm")
                .change_context(errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "algorithm",
                })
                .attach_printable("Algorithm for config not given")?;
            (name, prog)
        }
        DecisionManager::DecisionManagerv1(dmr) => {
            let name = dmr.name;

            let prog = dmr
                .program
                .get_required_value("program")
                .change_context(errors::ApiErrorResponse::MissingRequiredField {
                    field_name: "program",
                })
                .attach_printable("Program for config not given")?;
            (name, prog)
        }
    };
    let timestamp = common_utils::date_time::now_unix_timestamp();
    let mut algo_id: api_models::routing::RoutingAlgorithmRef = merchant_context
        .get_merchant_account()
        .routing_algorithm
        .clone()
        .map(|val| val.parse_value("routing algorithm"))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Could not decode the routing algorithm")?
        .unwrap_or_default();

    let key = merchant_context
        .get_merchant_account()
        .get_id()
        .get_payment_config_routing_id();
    let read_config_key = db.find_config_by_key(&key).await;

    euclid::frontend::ast::lowering::lower_program(prog.clone())
        .change_context(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid Request Data".to_string(),
        })
        .attach_printable("The Request has an Invalid Comparison")?;

    match read_config_key {
        Ok(config) => {
            let previous_record: DecisionManagerRecord = config
                .config
                .parse_struct("DecisionManagerRecord")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("The Payment Config Key Not Found")?;

            let new_algo = DecisionManagerRecord {
                name: previous_record.name,
                program: prog,
                modified_at: timestamp,
                created_at: previous_record.created_at,
            };

            let serialize_updated_str = new_algo
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to serialize config to string")?;

            let updated_config = configs::ConfigUpdate::Update {
                config: Some(serialize_updated_str),
            };

            db.update_config_by_key(&key, updated_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error serializing the config")?;

            algo_id.update_conditional_config_id(key.clone());
            let config_key = cache::CacheKind::DecisionManager(key.into());
            update_merchant_active_algorithm_ref(
                &state,
                merchant_context.get_merchant_key_store(),
                config_key,
                algo_id,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update routing algorithm ref")?;

            Ok(service_api::ApplicationResponse::Json(new_algo))
        }
        Err(e) if e.current_context().is_db_not_found() => {
            let new_rec = DecisionManagerRecord {
                name: name
                    .get_required_value("name")
                    .change_context(errors::ApiErrorResponse::MissingRequiredField {
                        field_name: "name",
                    })
                    .attach_printable("name of the config not found")?,
                program: prog,
                modified_at: timestamp,
                created_at: timestamp,
            };

            let serialized_str = new_rec
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error serializing the config")?;
            let new_config = configs::ConfigNew {
                key: key.clone(),
                config: serialized_str,
            };

            db.insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error fetching the config")?;

            algo_id.update_conditional_config_id(key.clone());
            let config_key = cache::CacheKind::DecisionManager(key.into());
            update_merchant_active_algorithm_ref(
                &state,
                merchant_context.get_merchant_key_store(),
                config_key,
                algo_id,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update routing algorithm ref")?;

            Ok(service_api::ApplicationResponse::Json(new_rec))
        }
        Err(e) => Err(e)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error fetching payment config"),
    }
}


Documentation: api-reference/v2/revenue-recovery/revenue-recovery--retrieve.mdx
# Type: Doc File

---
openapi: get /v2/process-trackers/revenue-recovery-workflow/{revenue_recovery_id}
---


pub struct StripePaymentIntentRequest {
    pub id: Option<id_type::PaymentId>,
    pub amount: Option<i64>, // amount in cents, hence passed as integer
    pub connector: Option<Vec<api_enums::RoutableConnectors>>,
    pub currency: Option<String>,
    #[serde(rename = "amount_to_capture")]
    pub amount_capturable: Option<i64>,
    pub confirm: Option<bool>,
    pub capture_method: Option<api_enums::CaptureMethod>,
    pub customer: Option<id_type::CustomerId>,
    pub description: Option<String>,
    pub payment_method_data: Option<StripePaymentMethodData>,
    pub receipt_email: Option<Email>,
    pub return_url: Option<url::Url>,
    pub setup_future_usage: Option<api_enums::FutureUsage>,
    pub shipping: Option<Shipping>,
    pub statement_descriptor: Option<String>,
    pub statement_descriptor_suffix: Option<String>,
    pub metadata: Option<serde_json::Value>,
    pub client_secret: Option<masking::Secret<String>>,
    pub payment_method_options: Option<StripePaymentMethodOptions>,
    pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
    pub mandate: Option<String>,
    pub off_session: Option<bool>,
    pub payment_method_types: Option<api_enums::PaymentMethodType>,
    pub receipt_ipaddress: Option<String>,
    pub user_agent: Option<String>,
    pub mandate_data: Option<MandateData>,
    pub automatic_payment_methods: Option<SecretSerdeValue>, // not used
    pub payment_method: Option<String>,                      // not used
    pub confirmation_method: Option<String>,                 // not used
    pub error_on_requires_action: Option<String>,            // not used
    pub radar_options: Option<SecretSerdeValue>,             // not used
    pub connector_metadata: Option<payments::ConnectorMetadata>,
}


pub async fn upsert_surcharge_decision_config(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    request: SurchargeDecisionConfigReq,
) -> RouterResponse<SurchargeDecisionManagerRecord> {
    use common_utils::ext_traits::{Encode, OptionExt, ValueExt};
    use diesel_models::configs;
    use storage_impl::redis::cache;

    use super::routing::helpers::update_merchant_active_algorithm_ref;

    let db = state.store.as_ref();
    let name = request.name;

    let program = request
        .algorithm
        .get_required_value("algorithm")
        .change_context(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "algorithm",
        })
        .attach_printable("Program for config not given")?;
    let merchant_surcharge_configs = request.merchant_surcharge_configs;

    let timestamp = common_utils::date_time::now_unix_timestamp();
    let mut algo_id: api_models::routing::RoutingAlgorithmRef = merchant_context
        .get_merchant_account()
        .routing_algorithm
        .clone()
        .map(|val| val.parse_value("routing algorithm"))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Could not decode the routing algorithm")?
        .unwrap_or_default();

    let key = merchant_context
        .get_merchant_account()
        .get_id()
        .get_payment_method_surcharge_routing_id();
    let read_config_key = db.find_config_by_key(&key).await;

    euclid::frontend::ast::lowering::lower_program(program.clone())
        .change_context(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid Request Data".to_string(),
        })
        .attach_printable("The Request has an Invalid Comparison")?;
    let surcharge_cache_key = merchant_context
        .get_merchant_account()
        .get_id()
        .get_surcharge_dsk_key();
    match read_config_key {
        Ok(config) => {
            let previous_record: SurchargeDecisionManagerRecord = config
                .config
                .parse_struct("SurchargeDecisionManagerRecord")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("The Payment Config Key Not Found")?;

            let new_algo = SurchargeDecisionManagerRecord {
                name: name.unwrap_or(previous_record.name),
                algorithm: program,
                modified_at: timestamp,
                created_at: previous_record.created_at,
                merchant_surcharge_configs,
            };

            let serialize_updated_str = new_algo
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Unable to serialize config to string")?;

            let updated_config = configs::ConfigUpdate::Update {
                config: Some(serialize_updated_str),
            };

            db.update_config_by_key(&key, updated_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error serializing the config")?;

            algo_id.update_surcharge_config_id(key.clone());
            let config_key = cache::CacheKind::Surcharge(surcharge_cache_key.into());
            update_merchant_active_algorithm_ref(
                &state,
                merchant_context.get_merchant_key_store(),
                config_key,
                algo_id,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update routing algorithm ref")?;

            Ok(service_api::ApplicationResponse::Json(new_algo))
        }
        Err(e) if e.current_context().is_db_not_found() => {
            let new_rec = SurchargeDecisionManagerRecord {
                name: name
                    .get_required_value("name")
                    .change_context(errors::ApiErrorResponse::MissingRequiredField {
                        field_name: "name",
                    })
                    .attach_printable("name of the config not found")?,
                algorithm: program,
                merchant_surcharge_configs,
                modified_at: timestamp,
                created_at: timestamp,
            };

            let serialized_str = new_rec
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error serializing the config")?;
            let new_config = configs::ConfigNew {
                key: key.clone(),
                config: serialized_str,
            };

            db.insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error fetching the config")?;

            algo_id.update_surcharge_config_id(key.clone());
            let config_key = cache::CacheKind::Surcharge(surcharge_cache_key.into());
            update_merchant_active_algorithm_ref(
                &state,
                merchant_context.get_merchant_key_store(),
                config_key,
                algo_id,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to update routing algorithm ref")?;

            Ok(service_api::ApplicationResponse::Json(new_rec))
        }
        Err(e) => Err(e)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error fetching payment config"),
    }
}


pub fn option_to_result<T>(opt: Option<T>) -> Result<T, errors::ConnectorError> {
    opt.ok_or(errors::ConnectorError::WebhookBodyDecodingFailed)
}


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

use super::{MockDb, Store};
use crate::{
    errors::{self, CustomResult},
    types::storage::{
        enums,
        reverse_lookup::{ReverseLookup, ReverseLookupNew},
    },
};

#[async_trait::async_trait]
pub trait ReverseLookupInterface {
    async fn insert_reverse_lookup(
        &self,
        _new: ReverseLookupNew,
        _storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<ReverseLookup, errors::StorageError>;
    async fn get_lookup_by_lookup_id(
        &self,
        _id: &str,
        _storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<ReverseLookup, errors::StorageError>;
}

#[cfg(not(feature = "kv_store"))]
mod storage {
    use error_stack::report;
    use router_env::{instrument, tracing};

    use super::{ReverseLookupInterface, Store};
    use crate::{
        connection,
        errors::{self, CustomResult},
        types::storage::{
            enums,
            reverse_lookup::{ReverseLookup, ReverseLookupNew},
        },
    };

    #[async_trait::async_trait]
    impl ReverseLookupInterface for Store {
        #[instrument(skip_all)]
        async fn insert_reverse_lookup(
            &self,
            new: ReverseLookupNew,
            _storage_scheme: enums::MerchantStorageScheme,
        ) -> CustomResult<ReverseLookup, errors::StorageError> {
            let conn = connection::pg_connection_write(self).await?;
            new.insert(&conn)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        }

        #[instrument(skip_all)]
        async fn get_lookup_by_lookup_id(
            &self,
            id: &str,
            _storage_scheme: enums::MerchantStorageScheme,
        ) -> CustomResult<ReverseLookup, errors::StorageError> {
            let conn = connection::pg_connection_read(self).await?;
            ReverseLookup::find_by_lookup_id(id, &conn)
                .await
                .map_err(|error| report!(errors::StorageError::from(error)))
        }
    }
}

#[cfg(feature = "kv_store")]
mod storage {
    use error_stack::{report, ResultExt};
    use redis_interface::SetnxReply;
    use router_env::{instrument, tracing};
    use storage_impl::redis::kv_store::{
        decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey,
    };

    use super::{ReverseLookupInterface, Store};
    use crate::{
        connection,
        core::errors::utils::RedisErrorExt,
        errors::{self, CustomResult},
        types::storage::{
            enums, kv,
            reverse_lookup::{ReverseLookup, ReverseLookupNew},
        },
        utils::db_utils,
    };

    #[async_trait::async_trait]
    impl ReverseLookupInterface for Store {
        #[instrument(skip_all)]
        async fn insert_reverse_lookup(
            &self,
            new: ReverseLookupNew,
            storage_scheme: enums::MerchantStorageScheme,
        ) -> CustomResult<ReverseLookup, errors::StorageError> {
            let storage_scheme = Box::pin(decide_storage_scheme::<_, ReverseLookup>(
                self,
                storage_scheme,
                Op::Insert,
            ))
            .await;
            match storage_scheme {
                enums::MerchantStorageScheme::PostgresOnly => {
                    let conn = connection::pg_connection_write(self).await?;
                    new.insert(&conn)
                        .await
                        .map_err(|error| report!(errors::StorageError::from(error)))
                }
                enums::MerchantStorageScheme::RedisKv => {
                    let created_rev_lookup = ReverseLookup {
                        lookup_id: new.lookup_id.clone(),
                        sk_id: new.sk_id.clone(),
                        pk_id: new.pk_id.clone(),
                        source: new.source.clone(),
                        updated_by: storage_scheme.to_string(),
                    };
                    let redis_entry = kv::TypedSql {
                        op: kv::DBOperation::Insert {
                            insertable: Box::new(kv::Insertable::ReverseLookUp(new)),
                        },
                    };

                    match Box::pin(kv_wrapper::<ReverseLookup, _, _>(
                        self,
                        KvOperation::SetNx(&created_rev_lookup, redis_entry),
                        PartitionKey::CombinationKey {
                            combination: &format!(
                                "reverse_lookup_{}",
                                &created_rev_lookup.lookup_id
                            ),
                        },
                    ))
                    .await
                    .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))?
                    .try_into_setnx()
                    {
                        Ok(SetnxReply::KeySet) => Ok(created_rev_lookup),
                        Ok(SetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue {
                            entity: "reverse_lookup",
                            key: Some(created_rev_lookup.lookup_id.clone()),
                        }
                        .into()),
                        Err(er) => Err(er).change_context(errors::StorageError::KVError),
                    }
                }
            }
        }

        #[instrument(skip_all)]
        async fn get_lookup_by_lookup_id(
            &self,
            id: &str,
            storage_scheme: enums::MerchantStorageScheme,
        ) -> CustomResult<ReverseLookup, errors::StorageError> {
            let database_call = || async {
                let conn = connection::pg_connection_read(self).await?;
                ReverseLookup::find_by_lookup_id(id, &conn)
                    .await
                    .map_err(|error| report!(errors::StorageError::from(error)))
            };
            let storage_scheme = Box::pin(decide_storage_scheme::<_, ReverseLookup>(
                self,
                storage_scheme,
                Op::Find,
            ))
            .await;
            match storage_scheme {
                enums::MerchantStorageScheme::PostgresOnly => database_call().await,
                enums::MerchantStorageScheme::RedisKv => {
                    let redis_fut = async {
                        Box::pin(kv_wrapper(
                            self,
                            KvOperation::<ReverseLookup>::Get,
                            PartitionKey::CombinationKey {
                                combination: &format!("reverse_lookup_{id}"),
                            },
                        ))
                        .await?
                        .try_into_get()
                    };

                    Box::pin(db_utils::try_redis_get_else_try_database_get(
                        redis_fut,
                        database_call,
                    ))
                    .await
                }
            }
        }
    }
}

#[async_trait::async_trait]
impl ReverseLookupInterface for MockDb {
    async fn insert_reverse_lookup(
        &self,
        new: ReverseLookupNew,
        _storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<ReverseLookup, errors::StorageError> {
        let reverse_lookup_insert = ReverseLookup::from(new);
        self.reverse_lookups
            .lock()
            .await
            .push(reverse_lookup_insert.clone());
        Ok(reverse_lookup_insert)
    }

    async fn get_lookup_by_lookup_id(
        &self,
        lookup_id: &str,
        _storage_scheme: enums::MerchantStorageScheme,
    ) -> CustomResult<ReverseLookup, errors::StorageError> {
        self.reverse_lookups
            .lock()
            .await
            .iter()
            .find(|reverse_lookup| reverse_lookup.lookup_id == lookup_id)
            .ok_or(
                errors::StorageError::ValueNotFound(format!(
                    "No reverse lookup found for lookup_id = {lookup_id}",
                ))
                .into(),
            )
            .cloned()
    }
}


OpenAPI Block Path: components.schemas.AdyenSplitType
{
  "type": "string",
  "enum": [
    "BalanceAccount",
    "AcquiringFees",
    "PaymentFee",
    "AdyenFees",
    "AdyenCommission",
    "AdyenMarkup",
    "Interchange",
    "SchemeFee",
    "Commission",
    "TopUp",
    "Vat"
  ]
}


File: crates/router/src/core/fraud_check/flows/sale_flow.rs
 Public functions: 1

use async_trait::async_trait;
use common_utils::{ext_traits::ValueExt, pii::Email};
use error_stack::ResultExt;
use masking::ExposeInterface;

use crate::{
    core::{
        errors::{ConnectorErrorExt, RouterResult},
        fraud_check::{FeatureFrm, FraudCheckConnectorData, FrmData},
        payments::{self, flows::ConstructFlowSpecificData, helpers},
    },
    errors, services,
    types::{
        api::fraud_check as frm_api,
        domain,
        fraud_check::{FraudCheckResponseData, FraudCheckSaleData, FrmSaleRouterData},
        storage::enums as storage_enums,
        ConnectorAuthType, MerchantRecipientData, ResponseId, RouterData,
    },
    SessionState,
};

#[async_trait]
impl ConstructFlowSpecificData<frm_api::Sale, FraudCheckSaleData, FraudCheckResponseData>
    for FrmData
{
    #[cfg(feature = "v2")]
    async fn construct_router_data<'a>(
        &self,
        _state: &SessionState,
        _connector_id: &str,
        _merchant_context: &domain::MerchantContext,
        _customer: &Option<domain::Customer>,
        _merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
        _merchant_recipient_data: Option<MerchantRecipientData>,
        _header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
    ) -> RouterResult<RouterData<frm_api::Sale, FraudCheckSaleData, FraudCheckResponseData>> {
        todo!()
    }

    #[cfg(feature = "v1")]
    async fn construct_router_data<'a>(
        &self,
        state: &SessionState,
        connector_id: &str,
        merchant_context: &domain::MerchantContext,
        customer: &Option<domain::Customer>,
        merchant_connector_account: &helpers::MerchantConnectorAccountType,
        _merchant_recipient_data: Option<MerchantRecipientData>,
        header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
    ) -> RouterResult<RouterData<frm_api::Sale, FraudCheckSaleData, FraudCheckResponseData>> {
        let status = storage_enums::AttemptStatus::Pending;

        let auth_type: ConnectorAuthType = merchant_connector_account
            .get_connector_account_details()
            .parse_value("ConnectorAuthType")
            .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                id: "ConnectorAuthType".to_string(),
            })?;

        let customer_id = customer.to_owned().map(|customer| customer.customer_id);

        let router_data = RouterData {
            flow: std::marker::PhantomData,
            merchant_id: merchant_context.get_merchant_account().get_id().clone(),
            customer_id,
            connector: connector_id.to_string(),
            payment_id: self.payment_intent.payment_id.get_string_repr().to_owned(),
            attempt_id: self.payment_attempt.attempt_id.clone(),
            tenant_id: state.tenant.tenant_id.clone(),
            status,
            payment_method: self
                .payment_attempt
                .payment_method
                .ok_or(errors::ApiErrorResponse::PaymentMethodNotFound)?,
            connector_auth_type: auth_type,
            description: None,
            address: self.address.clone(),
            auth_type: storage_enums::AuthenticationType::NoThreeDs,
            connector_meta_data: None,
            connector_wallets_details: None,
            amount_captured: None,
            minor_amount_captured: None,
            request: FraudCheckSaleData {
                amount: self
                    .payment_attempt
                    .net_amount
                    .get_total_amount()
                    .get_amount_as_i64(),
                order_details: self.order_details.clone(),
                currency: self.payment_attempt.currency,
                email: customer
                    .clone()
                    .and_then(|customer_data| {
                        customer_data
                            .email
                            .map(|email| Email::try_from(email.into_inner().expose()))
                    })
                    .transpose()
                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
                        field_name: "customer.customer_data.email",
                    })?,
            },
            response: Ok(FraudCheckResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId("".to_string()),
                connector_metadata: None,
                status: storage_enums::FraudCheckStatus::Pending,
                score: None,
                reason: None,
            }),
            access_token: None,
            session_token: None,
            reference_id: None,
            payment_method_token: None,
            connector_customer: None,
            preprocessing_id: None,
            payment_method_status: None,
            connector_request_reference_id: uuid::Uuid::new_v4().to_string(),
            test_mode: None,
            recurring_mandate_payment_data: None,
            #[cfg(feature = "payouts")]
            payout_method_data: None,
            #[cfg(feature = "payouts")]
            quote_id: None,
            payment_method_balance: None,
            connector_http_status_code: None,
            external_latency: None,
            connector_api_version: None,
            apple_pay_flow: None,
            frm_metadata: self.frm_metadata.clone(),
            refund_id: None,
            dispute_id: None,
            connector_response: None,
            integrity_check: Ok(()),
            additional_merchant_data: None,
            header_payload,
            connector_mandate_request_reference_id: None,
            authentication_id: None,
            psd2_sca_exemption_type: None,
            raw_connector_response: None,
            is_payment_id_from_merchant: None,
            l2_l3_data: None,
            minor_amount_capturable: None,
        };

        Ok(router_data)
    }
}

#[async_trait]
impl FeatureFrm<frm_api::Sale, FraudCheckSaleData> for FrmSaleRouterData {
    async fn decide_frm_flows<'a>(
        mut self,
        state: &SessionState,
        connector: &FraudCheckConnectorData,
        call_connector_action: payments::CallConnectorAction,
        merchant_context: &domain::MerchantContext,
    ) -> RouterResult<Self> {
        decide_frm_flow(
            &mut self,
            state,
            connector,
            call_connector_action,
            merchant_context,
        )
        .await
    }
}

pub async fn decide_frm_flow(
    router_data: &mut FrmSaleRouterData,
    state: &SessionState,
    connector: &FraudCheckConnectorData,
    call_connector_action: payments::CallConnectorAction,
    _merchant_context: &domain::MerchantContext,
) -> RouterResult<FrmSaleRouterData> {
    let connector_integration: services::BoxedFrmConnectorIntegrationInterface<
        frm_api::Sale,
        FraudCheckSaleData,
        FraudCheckResponseData,
    > = connector.connector.get_connector_integration();
    let resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        router_data,
        call_connector_action,
        None,
        None,
    )
    .await
    .to_payment_failed_response()?;

    Ok(resp)
}


pub struct VaultToken {
    pub card_cvc: Secret<String>,
    pub card_holder_name: Option<Secret<String>>,
}


pub struct Product {
    pub item_name: String,
    pub item_quantity: i64,
    pub item_id: String,
}


impl RevenueRecoveryAlgorithmData {
    pub fn has_exceeded_monitoring_threshold(&self, monitoring_threshold_in_seconds: i64) -> bool {
        let total_threshold_time = self.monitoring_configured_timestamp
            + Duration::seconds(monitoring_threshold_in_seconds);
        common_utils::date_time::now() >= total_threshold_time
    }
}


impl ApiKeys {
    pub fn server(state: AppState) -> Scope {
        web::scope("/api_keys/{merchant_id}")
            .app_data(web::Data::new(state))
            .service(web::resource("").route(web::post().to(api_keys::api_key_create)))
            .service(web::resource("/list").route(web::get().to(api_keys::api_key_list)))
            .service(
                web::resource("/{key_id}")
                    .route(web::get().to(api_keys::api_key_retrieve))
                    .route(web::post().to(api_keys::api_key_update))
                    .route(web::delete().to(api_keys::api_key_revoke)),
            )
    }
}


pub fn new(item: Secret<Vec<u8>, EncryptionStrategy>) -> Self {
        Self { inner: item }
    }


pub struct Errors {
    pub code: i64,
    pub description: String,
}


pub struct Tokenization;


File: crates/api_models/src/events/recon.rs

use common_utils::events::{ApiEventMetric, ApiEventsType};
use masking::PeekInterface;

use crate::recon::{
    ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest, VerifyTokenResponse,
};

impl ApiEventMetric for ReconUpdateMerchantRequest {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Recon)
    }
}

impl ApiEventMetric for ReconTokenResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Recon)
    }
}

impl ApiEventMetric for ReconStatusResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::Recon)
    }
}

impl ApiEventMetric for VerifyTokenResponse {
    fn get_api_event_type(&self) -> Option<ApiEventsType> {
        Some(ApiEventsType::User {
            user_id: self.user_email.peek().to_string(),
        })
    }
}


File: crates/router/src/services/api/generic_link_response/context.rs
 Public functions: 2

use common_utils::consts::DEFAULT_LOCALE;
use rust_i18n::t;
use tera::Context;

fn get_language(locale_str: &str) -> String {
    let lowercase_str = locale_str.to_lowercase();
    let primary_locale = lowercase_str.split(',').next().unwrap_or("").trim();

    if primary_locale.is_empty() {
        return DEFAULT_LOCALE.to_string();
    }

    let parts = primary_locale.split('-').collect::<Vec<&str>>();
    let language = *parts.first().unwrap_or(&DEFAULT_LOCALE);
    let country = parts.get(1).copied();

    match (language, country) {
        ("en", Some("gb")) => "en_gb".to_string(),
        ("fr", Some("be")) => "fr_be".to_string(),
        (lang, _) => lang.to_string(),
    }
}

pub fn insert_locales_in_context_for_payout_link(context: &mut Context, locale: &str) {
    let language = get_language(locale);
    let locale = language.as_str();
    let i18n_payout_link_title = t!("payout_link.initiate.title", locale = locale);
    let i18n_january = t!("months.january", locale = locale);
    let i18n_february = t!("months.february", locale = locale);
    let i18n_march = t!("months.march", locale = locale);
    let i18n_april = t!("months.april", locale = locale);
    let i18n_may = t!("months.may", locale = locale);
    let i18n_june = t!("months.june", locale = locale);
    let i18n_july = t!("months.july", locale = locale);
    let i18n_august = t!("months.august", locale = locale);
    let i18n_september = t!("months.september", locale = locale);
    let i18n_october = t!("months.october", locale = locale);
    let i18n_november = t!("months.november", locale = locale);
    let i18n_december = t!("months.december", locale = locale);
    let i18n_not_allowed = t!("payout_link.initiate.not_allowed", locale = locale);
    let i18n_am = t!("time.am", locale = locale);
    let i18n_pm = t!("time.pm", locale = locale);

    context.insert("i18n_payout_link_title", &i18n_payout_link_title);
    context.insert("i18n_january", &i18n_january);
    context.insert("i18n_february", &i18n_february);
    context.insert("i18n_march", &i18n_march);
    context.insert("i18n_april", &i18n_april);
    context.insert("i18n_may", &i18n_may);
    context.insert("i18n_june", &i18n_june);
    context.insert("i18n_july", &i18n_july);
    context.insert("i18n_august", &i18n_august);
    context.insert("i18n_september", &i18n_september);
    context.insert("i18n_october", &i18n_october);
    context.insert("i18n_november", &i18n_november);
    context.insert("i18n_december", &i18n_december);
    context.insert("i18n_not_allowed", &i18n_not_allowed);
    context.insert("i18n_am", &i18n_am);
    context.insert("i18n_pm", &i18n_pm);
}

pub fn insert_locales_in_context_for_payout_link_status(context: &mut Context, locale: &str) {
    let language = get_language(locale);
    let locale = language.as_str();
    let i18n_payout_link_status_title = t!("payout_link.status.title", locale = locale);
    let i18n_success_text = t!("payout_link.status.text.success", locale = locale);
    let i18n_success_message = t!("payout_link.status.message.success", locale = locale);
    let i18n_pending_text = t!("payout_link.status.text.processing", locale = locale);
    let i18n_pending_message = t!("payout_link.status.message.processing", locale = locale);
    let i18n_failed_text = t!("payout_link.status.text.failed", locale = locale);
    let i18n_failed_message = t!("payout_link.status.message.failed", locale = locale);
    let i18n_ref_id_text = t!("payout_link.status.info.ref_id", locale = locale);
    let i18n_error_code_text = t!("payout_link.status.info.error_code", locale = locale);
    let i18n_error_message = t!("payout_link.status.info.error_message", locale = locale);
    let i18n_redirecting_text = t!(
        "payout_link.status.redirection_text.redirecting",
        locale = locale
    );
    let i18n_redirecting_in_text = t!(
        "payout_link.status.redirection_text.redirecting_in",
        locale = locale
    );
    let i18n_seconds_text = t!(
        "payout_link.status.redirection_text.seconds",
        locale = locale
    );

    context.insert(
        "i18n_payout_link_status_title",
        &i18n_payout_link_status_title,
    );
    context.insert("i18n_success_text", &i18n_success_text);
    context.insert("i18n_success_message", &i18n_success_message);
    context.insert("i18n_pending_text", &i18n_pending_text);
    context.insert("i18n_pending_message", &i18n_pending_message);
    context.insert("i18n_failed_text", &i18n_failed_text);
    context.insert("i18n_failed_message", &i18n_failed_message);
    context.insert("i18n_ref_id_text", &i18n_ref_id_text);
    context.insert("i18n_error_code_text", &i18n_error_code_text);
    context.insert("i18n_error_message", &i18n_error_message);
    context.insert("i18n_redirecting_text", &i18n_redirecting_text);
    context.insert("i18n_redirecting_in_text", &i18n_redirecting_in_text);
    context.insert("i18n_seconds_text", &i18n_seconds_text);
}


OpenAPI Block Path: paths."/relay"
{
  "post": {
    "tags": [
      "Relay"
    ],
    "summary": "Relay - Create",
    "description": "Creates a relay request.",
    "operationId": "Relay Request",
    "parameters": [
      {
        "name": "X-Profile-Id",
        "in": "header",
        "description": "Profile ID for authentication",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      {
        "name": "X-Idempotency-Key",
        "in": "header",
        "description": "Idempotency Key for relay request",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    ],
    "requestBody": {
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/RelayRequest"
          },
          "examples": {
            "Create a relay request": {
              "value": {
                "connector_id": "mca_5apGeP94tMts6rg3U3kR",
                "connector_resource_id": "7256228702616471803954",
                "data": {
                  "refund": {
                    "amount": 6540,
                    "currency": "USD"
                  }
                },
                "type": "refund"
              }
            }
          }
        }
      },
      "required": true
    },
    "responses": {
      "200": {
        "description": "Relay request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RelayResponse"
            }
          }
        }
      },
      "400": {
        "description": "Invalid data"
      }
    },
    "security": [
      {
        "api_key": []
      }
    ]
  }
}


OpenAPI Block Path: components.schemas.AcceptanceType
{
  "type": "string",
  "description": "This is used to indicate if the mandate was accepted online or offline",
  "enum": [
    "online",
    "offline"
  ]
}


pub fn get_payment_processor_token_id_from_payment_attempt(
    payment_attempt: &PaymentAttempt,
) -> Option<String> {
    let used_token = payment_attempt
        .connector_token_details
        .as_ref()
        .and_then(|t| t.connector_mandate_id.clone());
    logger::info!("Used token in the payment attempt : {:?}", used_token);

    used_token
}


pub struct SyncTransactionResponse {
    #[serde(rename = "transId")]
    transaction_id: String,
    transaction_status: SyncStatus,
}


pub struct NovalnetPaymentsRequestTransaction {
    test_mode: i8,
    payment_type: NovalNetPaymentTypes,
    amount: NovalNetAmount,
    currency: common_enums::Currency,
    order_no: String,
    payment_data: Option<NovalNetPaymentData>,
    hook_url: Option<String>,
    return_url: Option<String>,
    error_return_url: Option<String>,
    enforce_3d: Option<i8>, //NOTE: Needed for CREDITCARD, GOOGLEPAY
    create_token: Option<i8>,
    scheme_tid: Option<Secret<String>>, // Card network's transaction ID
}


pub struct StaxCustomerResponse {
    id: Secret<String>,
}


pub struct EdgeId(usize);


impl ArchipelFlowStatus {
    fn new(status: ArchipelPaymentStatus, flow: ArchipelPaymentFlow) -> Self {
        Self { status, flow }
    }
}


impl ConnectorValidation for Aci {
    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]);
        is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}


pub fn new(
        pm_type: RequestPaymentMethodTypes,
        connector: String,
        merchant_connector_id: String,
        pm: api_enums::PaymentMethod,
    ) -> Self {
        Self {
            payment_method_type: pm_type.payment_method_type,
            payment_experience: pm_type.payment_experience,
            card_networks: pm_type.card_networks,
            payment_method: pm,
            connector,
            merchant_connector_id,
        }
    }


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


/// Creates a new `MerchantCountryCode` instance from a string.
    pub fn new(country_code: String) -> Self {
        Self(country_code)
    }


impl<'a> NetworkTokenizationBuilder<'a, PmAssigned> {
    pub fn get_optional_card_and_cvc(
        &self,
    ) -> (Option<domain::CardDetail>, Option<Secret<String>>) {
        (self.card.clone(), self.card_cvc.clone())
    }
    pub fn set_token_details(
        self,
        network_token: &'a NetworkTokenizationResponse,
    ) -> NetworkTokenizationBuilder<'a, PmTokenized> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            network_token: Some(&network_token.0),
            card_tokenized: true,
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}


pub struct CeleroRouterData<T> {
    pub amount: MinorUnit, // CeleroCommerce expects integer cents
    pub router_data: T,
}


/// Get the string representation of the id
    pub fn get_string_repr(&self) -> &str {
        self.0.get_string_repr()
    }


pub struct ChargebeeRouterData<T> {
    pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}


impl super::refunds::distribution::RefundDistributionAnalytics for SqlxClient {}

impl super::refunds::distribution::RefundDistributionAnalytics for for SqlxClient


pub struct Payu {
    amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
}


pub struct PaytmPaymentsResponse {
    status: PaytmPaymentStatus,
    id: String,
}


pub async fn check_token_status_with_tokenization_service(
    _state: &routes::SessionState,
    _customer_id: &id_type::GlobalCustomerId,
    _network_token_requestor_reference_id: String,
    _tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<(Option<Secret<String>>, Option<Secret<String>>), errors::NetworkTokenizationError>
{
    todo!()
}


pub fn tenant_aware_key(&self, pool: &RedisConnectionPool) -> String {
        pool.add_prefix(&self.0)
    }


impl Responder {
    let mut payload = json_payload.into_inner();
    let payment_id = path.into_inner();

    tracing::Span::current().record("payment_id", payment_id.get_string_repr());

    payload.payment_id = payment_id;
    let flow = Flow::PaymentsReject;
    let fpayload = FPaymentsRejectRequest(&payload);
    let locking_action = fpayload.get_locking_input(flow.clone());

    Box::pin(api::server_wrap(
        flow.clone(),
        state,
        &http_req,
        payload.clone(),
        |state, auth: auth::AuthenticationData, req, req_state| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            payments::payments_core::<
                api_types::Void,
                payment_types::PaymentsResponse,
                _,
                _,
                _,
                payments::PaymentData<api_types::Void>,
            >(
                state,
                req_state,
                merchant_context,
                auth.profile_id,
                payments::PaymentReject,
                payment_types::PaymentsCancelRequest {
                    payment_id: req.payment_id,
                    cancellation_reason: Some("Rejected by merchant".to_string()),
                    ..Default::default()
                },
                api::AuthFlow::Merchant,
                payments::CallConnectorAction::Trigger,
                None,
                HeaderPayload::default(),
            )
        },
        match env::which() {
            env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth {
                is_connected_allowed: false,
                is_platform_allowed: true,
            }),
            _ => auth::auth_type(
                &auth::HeaderAuth(auth::ApiKeyAuth {
                    is_connected_allowed: false,
                    is_platform_allowed: true,
                }),
                &auth::JWTAuth {
                    permission: Permission::ProfilePaymentWrite,
                },
                http_req.headers(),
            ),
        },
        locking_action,
    ))
    .await
}


pub struct CountAccumulator {
    pub count: Option<i64>,
}


OpenAPI Block Path: components.schemas.Paypal
{
  "type": "object",
  "required": [
    "email",
    "telephone_number",
    "paypal_id"
  ],
  "properties": {
    "email": {
      "type": "string",
      "description": "Email linked with paypal account",
      "example": "john.doe@example.com"
    },
    "telephone_number": {
      "type": "string",
      "description": "mobile number linked to paypal account",
      "example": "16608213349"
    },
    "paypal_id": {
      "type": "string",
      "description": "id of the paypal account",
      "example": "G83KXTJ5EHCQ2"
    }
  }
}


pub trait CrmInterface: Send + Sync {
    /// Make body for the request
    async fn make_body(&self, details: CrmPayload) -> RequestContent;

    /// Encrypt the given input data
    async fn make_request(&self, body: RequestContent, origin_base_url: String) -> Request;

    /// Decrypt the given input data
    async fn send_request(
        &self,
        proxy: &Proxy,
        request: Request,
    ) -> CustomResult<reqwest::Response, HttpClientError>;
}


pub struct JpmorganRefundSyncResponse {
    transaction_id: String,
    request_id: String,
    transaction_state: JpmorganTransactionState,
    amount: MinorUnit,
    currency: common_enums::Currency,
    response_status: JpmorganResponseStatus,
    response_code: String,
}


pub struct TokenizeWithPmId;


impl api::PaymentSession for Forte {}


pub struct CheckoutApplePayData {
    version: Secret<String>,
    data: Secret<String>,
    signature: Secret<String>,
    header: CheckoutApplePayHeader,
}


impl ConnectorRedirectResponse for Stripe {
    fn get_flow_type(
        &self,
        _query_params: &str,
        _json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, ConnectorError> {
        match action {
            PaymentAction::PSync
            | PaymentAction::CompleteAuthorize
            | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
        }
    }
}

impl ConnectorRedirectResponse for for Stripe


pub fn get_transaction_id(
    meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
    let transaction_id = meta.transaction_id.clone().ok_or(
        errors::ConnectorError::MissingConnectorRelatedTransactionID {
            id: "transaction_id".to_string(),
        },
    )?;
    Ok(transaction_id)
}


pub async fn user_connect_account(
    state: web::Data<AppState>,
    http_req: HttpRequest,
    json_payload: web::Json<user_api::ConnectAccountRequest>,
    query: web::Query<user_api::AuthIdAndThemeIdQueryParam>,
) -> HttpResponse {
    let flow = Flow::UserConnectAccount;
    let req_payload = json_payload.into_inner();
    let query_params = query.into_inner();
    Box::pin(api::server_wrap(
        flow.clone(),
        state,
        &http_req,
        req_payload.clone(),
        |state, _: (), req_body, _| {
            user_core::connect_account(
                state,
                req_body,
                query_params.auth_id.clone(),
                query_params.theme_id.clone(),
            )
        },
        &auth::NoAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct Card {
    card_number: cards::CardNumber,
    expiration_month: Secret<String>,
    expiration_year: Secret<String>,
    security_code: Secret<String>,
}


pub struct AuthChargeCreditCard {
    transaction: TransactionAuthChargeResponseBody,
}