File: crates/diesel_models/src/enums.rs
 Public structs: 2

#[doc(hidden)]
pub mod diesel_exports {
    pub use super::{
        DbApiVersion as ApiVersion, DbAttemptStatus as AttemptStatus,
        DbAuthenticationType as AuthenticationType, DbBlocklistDataKind as BlocklistDataKind,
        DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus,
        DbCardDiscovery as CardDiscovery, DbConnectorStatus as ConnectorStatus,
        DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency,
        DbDashboardMetadata as DashboardMetadata, DbDeleteStatus as DeleteStatus,
        DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus,
        DbEventClass as EventClass, DbEventObjectType as EventObjectType, DbEventType as EventType,
        DbFraudCheckStatus as FraudCheckStatus, DbFraudCheckType as FraudCheckType,
        DbFutureUsage as FutureUsage, DbGenericLinkType as GenericLinkType,
        DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus,
        DbMandateType as MandateType, DbMerchantStorageScheme as MerchantStorageScheme,
        DbOrderFulfillmentTimeOrigin as OrderFulfillmentTimeOrigin,
        DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentSource as PaymentSource,
        DbPaymentType as PaymentType, DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType,
        DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus,
        DbRefundStatus as RefundStatus, DbRefundType as RefundType, DbRelayStatus as RelayStatus,
        DbRelayType as RelayType,
        DbRequestIncrementalAuthorization as RequestIncrementalAuthorization,
        DbRevenueRecoveryAlgorithmType as RevenueRecoveryAlgorithmType, DbRoleScope as RoleScope,
        DbRoutingAlgorithmKind as RoutingAlgorithmKind, DbRoutingApproach as RoutingApproach,
        DbScaExemptionType as ScaExemptionType,
        DbSuccessBasedRoutingConclusiveState as SuccessBasedRoutingConclusiveState,
        DbTokenizationFlag as TokenizationFlag, DbTotpStatus as TotpStatus,
        DbTransactionType as TransactionType, DbUserRoleVersion as UserRoleVersion,
        DbUserStatus as UserStatus, DbWebhookDeliveryAttempt as WebhookDeliveryAttempt,
    };
}

pub use common_enums::*;
use common_utils::pii;
#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
pub use common_utils::tokenization;
use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types::Jsonb};
use router_derive::diesel_enum;
use time::PrimitiveDateTime;

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum RoutingAlgorithmKind {
    Single,
    Priority,
    VolumeSplit,
    Advanced,
    Dynamic,
    ThreeDsDecisionRule,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum EventObjectType {
    PaymentDetails,
    RefundDetails,
    DisputeDetails,
    MandateDetails,
    PayoutDetails,
}

// Refund
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum RefundType {
    InstantRefund,
    RegularRefund,
    RetryRefund,
}

// Mandate
#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    Default,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum MandateType {
    SingleUse,
    #[default]
    MultiUse,
}

#[derive(
    serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression,
)]
#[diesel(sql_type = Jsonb)]
#[serde(rename_all = "snake_case")]
pub struct MandateDetails {
    pub update_mandate_id: Option<String>,
}

common_utils::impl_to_sql_from_sql_json!(MandateDetails);

#[derive(
    serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression,
)]
#[diesel(sql_type = Jsonb)]
#[serde(rename_all = "snake_case")]
pub enum MandateDataType {
    SingleUse(MandateAmountData),
    MultiUse(Option<MandateAmountData>),
}

common_utils::impl_to_sql_from_sql_json!(MandateDataType);

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct MandateAmountData {
    pub amount: common_utils::types::MinorUnit,
    pub currency: Currency,
    pub start_date: Option<PrimitiveDateTime>,
    pub end_date: Option<PrimitiveDateTime>,
    pub metadata: Option<pii::SecretSerdeValue>,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum FraudCheckType {
    PreFrm,
    PostFrm,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]
pub enum FraudCheckLastStep {
    #[default]
    Processing,
    CheckoutOrSale,
    TransactionOrRecordRefund,
    Fulfillment,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum UserStatus {
    Active,
    #[default]
    InvitationSent,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    serde::Deserialize,
    serde::Serialize,
    strum::Display,
    strum::EnumString,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum DashboardMetadata {
    ProductionAgreement,
    SetupProcessor,
    ConfigureEndpoint,
    SetupComplete,
    FirstProcessorConnected,
    SecondProcessorConnected,
    ConfiguredRouting,
    TestPayment,
    IntegrationMethod,
    ConfigurationType,
    IntegrationCompleted,
    StripeConnected,
    PaypalConnected,
    SpRoutingConfigured,
    Feedback,
    ProdIntent,
    SpTestPayment,
    DownloadWoocom,
    ConfigureWoocom,
    SetupWoocomWebhook,
    IsMultipleConfiguration,
    IsChangePasswordRequired,
    OnboardingSurvey,
    ReconStatus,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum TotpStatus {
    Set,
    InProgress,
    #[default]
    NotSet,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::EnumString,
    strum::Display,
)]
#[diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum UserRoleVersion {
    #[default]
    V1,
    V2,
}


Web Documentation: Previous roadmap - Q1 2024 | Hyperswitch
# Type: Web Doc

🗺 Our Roadmap typically pans out over a 3-month period and we establish topics we work on upfront.
👂And as always, we listen to your feedback and adapt our plans if needed.
Core Values
Our core values have pretty much remained the same since the early days and here they are:
Make payments more
accessible
and
affordable
to every digital business
Staying
simple
and
super-lightweight
, and at the same time
reliable
and
scalable
payment switch
Being
community-first
in ideation, planning and execution of features
Legend
Description
🟩
Feature completed
🟧
Feature in progress
🟥
Work not started
💪
Stretch target
🚛
Backlog feature from Q4 2023
Roadmap
Community Feature Requests
🟩 Card vault enhancements to support more use cases - enable vaulting before payment, card fingerprinting
🟩 Enhance MIT payments (Merchant Initiated Transactions) to accept
raw card data
and
network_reference_id.
This will allow for payment gateway agnostic MIT payments
(removed from the Q1 roadmap)
Enabling card transactions using
payment gateway token
to ensure business continuity for merchants with card vaulted with payment gateways
🟩 New connector and payment method Integrations
🟩 Place2Pay
🟩 Billwerk
🟩 Pix and Boleto via Adyen
(the list of connectors will keep expanding as we receive more requests from the community!!! )
Developer Experience
🚛 Code restructuring for enhancing readability and ease of contributions
🟩 Helm charts enhancement to enable easy installation on Azure, Google Cloud and within existing Kubernetes clusters
🟩 Helm charts will support installation of
hyperswitch-card-vault
🚛 PCI Software Security Standard (S3) certification. At the moment, Hyperswitch application is battle tested for PCI L1 compliance. While PCI Software Security Standard (S3) is not mandatory for Hyperswitch related functionalities, we are undertaking the certification to further augment our security standards
🟩Adding more developer help videos and improving developer documentations for Hyperswitch features, components and usage
💪🚛 Open sourcing the Native Unified Checkout SDK (Android and iOS)
🟩 Diagnostics tool to determine health of your on-cloud Hyperswitch stack setup
Reduce Payment Costs
🟩 Enabling surcharge for specific payment methods to promote low cost payment methods
🚛 🟩 Hyperswitch API supports for Plaid for ACH account verification
Improving Payment Authorization Rates
🟩Decoupled 3DS authentication and authorization using EMVCo certified 3DS connectors, for improving payment authorization rates and customer experience.
🚛 Paypal Vault flows for improving repeat user payment experience
Reducing Payment Operations
🟩 Enhanced Audit trail visibility for Payments, Refunds, Disputes on Hyperswitch Control Centre
🟩 Support for Hosted Checkout Page on Web
🟩 Mitigating fraud by defining Block List rules to block transactions from specific customer ID, card bins, card numbers and more parameters
🟩 Enhanced search using Global Identifiers for improved discoverability. Hyperswitch Cloud users can use the Control Center to search for payments, customers, refunds, connector transaction IDs and get all related data
🟩 Dispute management and evidence submission workflow on Hyperswitch Control Centre
🟩 Hyperswitch Control Centre will allow to customize payment methods at country and currency
🟩 Create custom roles for Identity and Access Management
Want to contribute to the roadmap?
Submit an idea or feature request here
with a simple explanation on
What?
and
Why?
included.
1 year ago
Was this helpful?


impl api::PaymentAuthorize for Katapult {}

impl api::PaymentAuthorize for for Katapult


pub struct Args {
    /// Admin API Key of the environment
    #[arg(short, long)]
    admin_api_key: String,
    /// Base URL of the Hyperswitch environment
    #[arg(short, long)]
    base_url: String,
    /// Name of the connector
    #[arg(short, long)]
    connector_name: Option<String>,
    /// Name of the module
    #[arg(short, long)]
    module_name: Option<Module>,
    /// Custom headers
    #[arg(short = 'H', long = "header")]
    custom_headers: Option<Vec<String>>,
    /// Minimum delay in milliseconds to be added before sending a request
    /// By default, 7 milliseconds will be the delay
    #[arg(short, long, default_value_t = 7)]
    delay_request: u32,
    /// Folder name of specific tests
    #[arg(short, long = "folder")]
    folders: Option<String>,
    /// Optional Verbose logs
    #[arg(short, long)]
    verbose: bool,
}


impl api::PaymentToken for Affirm {}


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

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

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

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

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = gocardless::GocardlessAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            format!("Bearer {}", auth.access_token.peek()).into_masked(),
        )])
    }

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

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

        let error_iter = response.error.errors.iter();
        let mut error_reason: Vec<String> = Vec::new();
        for error in error_iter {
            let reason = error.field.clone().map_or(error.message.clone(), |field| {
                format!("{} {}", field, error.message)
            });
            error_reason.push(reason)
        }
        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.error.code.to_string(),
            message: response.error.error_type,
            reason: Some(error_reason.join("; ")),
            attempt_status: None,
            connector_transaction_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}


impl api::PaymentCapture for Paytm {}


pub async fn delete_evidence(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    delete_evidence_request: dispute_models::DeleteEvidenceRequest,
) -> RouterResponse<serde_json::Value> {
    let dispute_id = delete_evidence_request.dispute_id.clone();
    let dispute = state
        .store
        .find_dispute_by_merchant_id_dispute_id(
            merchant_context.get_merchant_account().get_id(),
            &dispute_id,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.clone(),
        })?;
    let dispute_evidence: api::DisputeEvidence = dispute
        .evidence
        .clone()
        .parse_value("DisputeEvidence")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing dispute evidence record")?;
    let updated_dispute_evidence =
        transformers::delete_evidence_file(dispute_evidence, delete_evidence_request.evidence_type);
    let update_dispute = diesel_models::dispute::DisputeUpdate::EvidenceUpdate {
        evidence: updated_dispute_evidence
            .encode_to_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while encoding dispute evidence")?
            .into(),
    };
    state
        .store
        .update_dispute(dispute, update_dispute)
        .await
        .to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
            dispute_id: dispute_id.to_owned(),
        })
        .attach_printable_lazy(|| {
            format!("Unable to update dispute with dispute_id: {dispute_id}")
        })?;
    Ok(services::ApplicationResponse::StatusOk)
}


pub async fn decide_frm_flow(
    router_data: &mut FrmRecordReturnRouterData,
    state: &SessionState,
    connector: &FraudCheckConnectorData,
    call_connector_action: payments::CallConnectorAction,
    _merchant_context: &domain::MerchantContext,
) -> RouterResult<FrmRecordReturnRouterData> {
    let connector_integration: services::BoxedFrmConnectorIntegrationInterface<
        RecordReturn,
        FraudCheckRecordReturnData,
        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 trait ToSql<T: AnalyticsDataSource> {
    fn to_sql(&self, table_engine: &TableEngine) -> error_stack::Result<String, ParsingError>;
}


impl Deref for FloatDecimal {
    type Target = Decimal;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}


/// Create a [`Gauge`][Gauge] metric with the specified name and an optional description,
/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter].
///
/// [Gauge]: opentelemetry::metrics::Gauge
/// [Meter]: opentelemetry::metrics::Meter
#[macro_export]
macro_rules! gauge_metric {
    ($name:ident, $meter:ident) => {
        pub(crate) static $name: ::std::sync::LazyLock<$crate::opentelemetry::metrics::Gauge<u64>> =
            ::std::sync::LazyLock::new(|| $meter.u64_gauge(stringify!($name)).build());
    };
    ($name:ident, $meter:ident, description:literal) => {
        #[doc = $description]
        pub(crate) static $name: ::std::sync::LazyLock<$crate::opentelemetry::metrics::Gauge<u64>> =
            ::std::sync::LazyLock::new(|| {
                $meter
                    .u64_gauge(stringify!($name))
                    .with_description($description)
                    .build()
            });
    };
}


impl PaymentVoid for Riskified {}


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

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

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

impl webhooks::IncomingWebhook for for CtpMastercard


pub struct OperationData {
    operation_id: String,
    operation_currency: Currency,
    operation_result: NexixpayPaymentStatus,
    operation_type: NexixpayOperationType,
    order_id: String,
}


pub struct AuthenticationEligibilityRequest {
    /// Payment method-specific data such as card details, wallet info, etc.
    /// This holds the raw information required to process the payment method.
    #[schema(value_type = PaymentMethodData)]
    pub payment_method_data: PaymentMethodData,

    /// Enum representing the type of payment method being used
    /// (e.g., Card, Wallet, UPI, BankTransfer, etc.).
    #[schema(value_type = PaymentMethod)]
    pub payment_method: common_enums::PaymentMethod,

    /// Optional secret value used to identify and authorize the client making the request.
    /// This can help ensure that the payment session is secure and valid.
    #[schema(value_type = Option<String>)]
    pub client_secret: Option<masking::Secret<String>>,

    /// Optional identifier for the business profile associated with the payment.
    /// This determines which configurations, rules, and branding are applied to the transaction.
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// Optional billing address of the customer.
    /// This can be used for fraud detection, authentication, or compliance purposes.
    #[schema(value_type = Option<Address>)]
    pub billing: Option<Address>,

    /// Optional shipping address of the customer.
    /// This can be useful for logistics, verification, or additional risk checks.
    #[schema(value_type = Option<Address>)]
    pub shipping: Option<Address>,

    /// Optional information about the customer's browser (user-agent, language, etc.).
    /// This is typically used to support 3DS authentication flows and improve risk assessment.
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_information: Option<BrowserInformation>,

    /// Optional email address of the customer.
    /// Used for customer identification, communication, and possibly for 3DS or fraud checks.
    #[schema(value_type = Option<String>)]
    pub email: Option<common_utils::pii::Email>,
}


pub struct CardDetailsPaymentMethod {
    pub last4_digits: Option<String>,
    pub issuer_country: Option<String>,
    pub expiry_month: Option<Secret<String>>,
    pub expiry_year: Option<Secret<String>>,
    pub nick_name: Option<Secret<String>>,
    pub card_holder_name: Option<Secret<String>>,
    pub card_isin: Option<String>,
    pub card_issuer: Option<String>,
    pub card_network: Option<api_enums::CardNetwork>,
    pub card_type: Option<String>,
    #[serde(default = "saved_in_locker_default")]
    pub saved_to_locker: bool,
}


pub fn get_id(&self) -> Option<id_type::MerchantConnectorAccountId> {
        match self {
            Self::MerchantConnectorAccount(merchant_connector_account) => {
                Some(merchant_connector_account.id.clone())
            }
            Self::MerchantConnectorDetails(_) => None,
        }
    }


impl Conversion {
    fn get_req_type(ident: Derives) -> syn::Ident {
        match ident {
            Derives::Authorize => syn::Ident::new("PaymentsRequest", Span::call_site()),
            Derives::AuthorizeData => syn::Ident::new("PaymentsAuthorizeData", Span::call_site()),
            Derives::Sync => syn::Ident::new("PaymentsRetrieveRequest", Span::call_site()),
            Derives::SyncData => syn::Ident::new("PaymentsSyncData", Span::call_site()),
            Derives::Cancel => syn::Ident::new("PaymentsCancelRequest", Span::call_site()),
            Derives::CancelData => syn::Ident::new("PaymentsCancelData", Span::call_site()),
            Derives::ApproveData => syn::Ident::new("PaymentsApproveData", Span::call_site()),
            Derives::Reject => syn::Ident::new("PaymentsRejectRequest", Span::call_site()),
            Derives::RejectData => syn::Ident::new("PaymentsRejectData", Span::call_site()),
            Derives::Capture => syn::Ident::new("PaymentsCaptureRequest", Span::call_site()),
            Derives::CaptureData => syn::Ident::new("PaymentsCaptureData", Span::call_site()),
            Derives::CompleteAuthorizeData => {
                syn::Ident::new("CompleteAuthorizeData", Span::call_site())
            }
            Derives::Start => syn::Ident::new("PaymentsStartRequest", Span::call_site()),
            Derives::Verify => syn::Ident::new("VerifyRequest", Span::call_site()),
            Derives::SetupMandateData => {
                syn::Ident::new("SetupMandateRequestData", Span::call_site())
            }
            Derives::Session => syn::Ident::new("PaymentsSessionRequest", Span::call_site()),
            Derives::SessionData => syn::Ident::new("PaymentsSessionData", Span::call_site()),
            Derives::IncrementalAuthorization => {
                syn::Ident::new("PaymentsIncrementalAuthorizationRequest", Span::call_site())
            }
            Derives::IncrementalAuthorizationData => {
                syn::Ident::new("PaymentsIncrementalAuthorizationData", Span::call_site())
            }
            Derives::SdkSessionUpdate => {
                syn::Ident::new("PaymentsDynamicTaxCalculationRequest", Span::call_site())
            }
            Derives::SdkSessionUpdateData => {
                syn::Ident::new("SdkPaymentsSessionUpdateData", Span::call_site())
            }
            Derives::PostSessionTokens => {
                syn::Ident::new("PaymentsPostSessionTokensRequest", Span::call_site())
            }
            Derives::PostSessionTokensData => {
                syn::Ident::new("PaymentsPostSessionTokensData", Span::call_site())
            }
            Derives::UpdateMetadata => {
                syn::Ident::new("PaymentsUpdateMetadataRequest", Span::call_site())
            }
            Derives::UpdateMetadataData => {
                syn::Ident::new("PaymentsUpdateMetadataData", Span::call_site())
            }
            Derives::CancelPostCapture => {
                syn::Ident::new("PaymentsCancelPostCaptureRequest", Span::call_site())
            }
            Derives::CancelPostCaptureData => {
                syn::Ident::new("PaymentsCancelPostCaptureData", Span::call_site())
            }
        }
    }

    fn to_function(&self, ident: Derives) -> TokenStream {
        let req_type = Self::get_req_type(ident);
        match self {
            Self::ValidateRequest => quote! {
                fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest<F,#req_type, Self::Data> + Send + Sync)> {
                    Ok(self)
                }
            },
            Self::GetTracker => quote! {
                fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker<F, Self::Data, #req_type> + Send + Sync)> {
                    Ok(self)
                }
            },
            Self::Domain => quote! {
                fn to_domain(&self) -> RouterResult<&dyn Domain<F,#req_type, Self::Data>> {
                    Ok(self)
                }
            },
            Self::UpdateTracker => quote! {
                fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker<F, Self::Data, #req_type> + Send + Sync)> {
                    Ok(self)
                }
            },
            Self::PostUpdateTracker => quote! {
                fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker<F, Self::Data, #req_type> + Send + Sync)> {
                    Ok(self)
                }
            },
            Self::Invalid(s) => {
                helpers::syn_error(Span::call_site(), &format!("Invalid identifier {s}"))
                    .to_compile_error()
            }
            Self::All => {
                let validate_request = Self::ValidateRequest.to_function(ident);
                let get_tracker = Self::GetTracker.to_function(ident);
                let domain = Self::Domain.to_function(ident);
                let update_tracker = Self::UpdateTracker.to_function(ident);

                quote! {
                    #validate_request
                    #get_tracker
                    #domain
                    #update_tracker
                }
            }
        }
    }

    fn to_ref_function(&self, ident: Derives) -> TokenStream {
        let req_type = Self::get_req_type(ident);
        match self {
            Self::ValidateRequest => quote! {
                fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest<F, #req_type, Self::Data> + Send + Sync)> {
                    Ok(*self)
                }
            },
            Self::GetTracker => quote! {
                fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker<F, Self::Data,#req_type> + Send + Sync)> {
                    Ok(*self)
                }
            },
            Self::Domain => quote! {
                fn to_domain(&self) -> RouterResult<&(dyn Domain<F,#req_type, Self::Data>)> {
                    Ok(*self)
                }
            },
            Self::UpdateTracker => quote! {
                fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker<F, Self::Data,#req_type> + Send + Sync)> {
                    Ok(*self)
                }
            },
            Self::PostUpdateTracker => quote! {
                fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker<F, Self::Data, #req_type> + Send + Sync)> {
                    Ok(*self)
                }
            },
            Self::Invalid(s) => {
                helpers::syn_error(Span::call_site(), &format!("Invalid identifier {s}"))
                    .to_compile_error()
            }
            Self::All => {
                let validate_request = Self::ValidateRequest.to_ref_function(ident);
                let get_tracker = Self::GetTracker.to_ref_function(ident);
                let domain = Self::Domain.to_ref_function(ident);
                let update_tracker = Self::UpdateTracker.to_ref_function(ident);

                quote! {
                    #validate_request
                    #get_tracker
                    #domain
                    #update_tracker
                }
            }
        }
    }
}


impl SecretBytesMut {
    /// Wrap bytes in `SecretBytesMut`
    pub fn new(bytes: impl Into<BytesMut>) -> Self {
        Self(bytes.into())
    }
}


pub async fn authentication_create() {}


pub struct DwollaPaymentSyncResponse {
    pub id: String,
    pub status: DwollaPaymentStatus,
    pub amount: DwollaAmount,
}


pub struct ThreeDSRequestDeviceData {
    pub accept_header: String,
    pub user_agent_header: String,
    pub browser_language: Option<String>,
    pub browser_screen_width: Option<u32>,
    pub browser_screen_height: Option<u32>,
    pub browser_color_depth: Option<String>,
    pub time_zone: Option<String>,
    pub browser_java_enabled: Option<bool>,
    pub browser_javascript_enabled: Option<bool>,
    pub channel: Option<ThreeDSRequestChannel>,
}


OpenAPI Block Path: components.schemas.MobilePaymentNextStepData
{
  "type": "object",
  "required": [
    "consent_data_required"
  ],
  "properties": {
    "consent_data_required": {
      "$ref": "#/components/schemas/MobilePaymentConsent"
    }
  }
}


pub struct NetAmount {
    /// The payment amount
    order_amount: MinorUnit,
    /// The shipping cost of the order
    shipping_cost: Option<MinorUnit>,
    /// Tax amount related to the order
    order_tax_amount: Option<MinorUnit>,
    /// The surcharge amount to be added to the order
    surcharge_amount: Option<MinorUnit>,
    /// tax on surcharge amount
    tax_on_surcharge: Option<MinorUnit>,
}


pub async fn list_payment_link(
    state: SessionState,
    merchant: domain::MerchantAccount,
    constraints: api_models::payments::PaymentLinkListConstraints,
) -> RouterResponse<Vec<api_models::payments::RetrievePaymentLinkResponse>> {
    let db = state.store.as_ref();
    let payment_link = db
        .list_payment_link_by_merchant_id(merchant.get_id(), constraints)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to retrieve payment link")?;
    let payment_link_list = future::try_join_all(payment_link.into_iter().map(|payment_link| {
        api_models::payments::RetrievePaymentLinkResponse::from_db_payment_link(payment_link)
    }))
    .await?;
    Ok(services::ApplicationResponse::Json(payment_link_list))
}


/// get_webhook_config_disabled_events_key
    pub fn get_webhook_config_disabled_events_key(&self, connector_id: &str) -> String {
        format!(
            "whconf_disabled_events_{}_{connector_id}",
            self.get_string_repr()
        )
    }


pub fn build_pm_collect_link_html(
    link_data: &GenericLinkFormData,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let document =
        include_str!("../../core/generic_link/payment_method_collect/initiate/index.html");
    let styles = include_str!("../../core/generic_link/payment_method_collect/initiate/styles.css");
    let (mut tera, mut context) = build_html_template(link_data, document, styles)
        .attach_printable(
            "Failed to build context for payment method collect link's HTML template",
        )?;

    // Insert dynamic context in JS
    let script = include_str!("../../core/generic_link/payment_method_collect/initiate/script.js");
    let js_template = script.to_string();
    let js_dynamic_context = "{{ script_data }}";
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("document_scripts", &final_js);
    context.insert("script_data", &link_data.js_data);
    let js_script_tag = tera
        .render("document_scripts", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render JS template")?;
    context.insert("js_script_tag", &js_script_tag);
    context.insert(
        "hyper_sdk_loader_script_tag",
        &format!(
            r#"<script src="{}" onload="initializeCollectSDK()"></script>"#,
            link_data.sdk_url
        ),
    );

    // Render HTML template
    tera.render("html_template", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payment method collect link's HTML template")
}


pub struct HelcimInvoice {
    invoice_number: String,
    line_items: Vec<HelcimLineItems>,
}


OpenAPI Block Path: components.schemas.EphemeralKeyCreateResponse
{
  "type": "object",
  "description": "ephemeral_key for the customer_id mentioned",
  "required": [
    "customer_id",
    "created_at",
    "expires",
    "secret"
  ],
  "properties": {
    "customer_id": {
      "type": "string",
      "description": "customer_id to which this ephemeral key belongs to",
      "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44",
      "maxLength": 64,
      "minLength": 1
    },
    "created_at": {
      "type": "integer",
      "format": "int64",
      "description": "time at which this ephemeral key was created"
    },
    "expires": {
      "type": "integer",
      "format": "int64",
      "description": "time at which this ephemeral key would expire"
    },
    "secret": {
      "type": "string",
      "description": "ephemeral key"
    }
  }
}


pub struct ProcessTracker;


pub(crate) async fn extract_data_and_perform_action(
    state: &SessionState,
    tracking_data: &pcr_storage_types::RevenueRecoveryWorkflowTrackingData,
) -> Result<pcr_storage_types::RevenueRecoveryPaymentData, errors::ProcessTrackerError> {
    let db = &state.store;

    let key_manager_state = &state.into();
    let key_store = db
        .get_merchant_key_store_by_merchant_id(
            key_manager_state,
            &tracking_data.merchant_id,
            &db.get_master_key().to_vec().into(),
        )
        .await?;

    let merchant_account = db
        .find_merchant_account_by_merchant_id(
            key_manager_state,
            &tracking_data.merchant_id,
            &key_store,
        )
        .await?;

    let profile = db
        .find_business_profile_by_profile_id(
            key_manager_state,
            &key_store,
            &tracking_data.profile_id,
        )
        .await?;

    let billing_mca = db
        .find_merchant_connector_account_by_id(
            key_manager_state,
            &tracking_data.billing_mca_id,
            &key_store,
        )
        .await?;

    let pcr_payment_data = pcr_storage_types::RevenueRecoveryPaymentData {
        merchant_account,
        profile: profile.clone(),
        key_store,
        billing_mca,
        retry_algorithm: profile
            .revenue_recovery_retry_algorithm_type
            .unwrap_or(tracking_data.revenue_recovery_retry),
    };
    Ok(pcr_payment_data)
}


pub fn mk_app(
    state: AppState,
    request_body_limit: usize,
) -> actix_web::App<
    impl ServiceFactory<
        ServiceRequest,
        Config = (),
        Response = actix_web::dev::ServiceResponse<impl MessageBody>,
        Error = actix_web::Error,
        InitError = (),
    >,
> {
    let mut server_app = get_application_builder(request_body_limit, state.conf.cors.clone());

    #[cfg(feature = "dummy_connector")]
    {
        use routes::DummyConnector;
        server_app = server_app.service(DummyConnector::server(state.clone()));
    }

    #[cfg(any(feature = "olap", feature = "oltp"))]
    {
        #[cfg(feature = "olap")]
        {
            // This is a more specific route as compared to `MerchantConnectorAccount`
            // so it is registered before `MerchantConnectorAccount`.
            #[cfg(feature = "v1")]
            {
                server_app = server_app
                    .service(routes::ProfileNew::server(state.clone()))
                    .service(routes::Forex::server(state.clone()))
                    .service(routes::ProfileAcquirer::server(state.clone()));
            }

            server_app = server_app.service(routes::Profile::server(state.clone()));
        }
        server_app = server_app
            .service(routes::Payments::server(state.clone()))
            .service(routes::Customers::server(state.clone()))
            .service(routes::Configs::server(state.clone()))
            .service(routes::MerchantConnectorAccount::server(state.clone()))
            .service(routes::RelayWebhooks::server(state.clone()))
            .service(routes::Webhooks::server(state.clone()))
            .service(routes::Hypersense::server(state.clone()))
            .service(routes::Relay::server(state.clone()))
            .service(routes::ThreeDsDecisionRule::server(state.clone()));

        #[cfg(feature = "oltp")]
        {
            server_app = server_app.service(routes::PaymentMethods::server(state.clone()));
        }

        #[cfg(all(feature = "v2", feature = "oltp"))]
        {
            server_app = server_app
                .service(routes::PaymentMethodSession::server(state.clone()))
                .service(routes::Refunds::server(state.clone()));
        }
        #[cfg(all(feature = "v2", feature = "oltp", feature = "tokenization_v2"))]
        {
            server_app = server_app.service(routes::Tokenization::server(state.clone()));
        }
        #[cfg(feature = "v1")]
        {
            server_app = server_app
                .service(routes::Refunds::server(state.clone()))
                .service(routes::Mandates::server(state.clone()))
                .service(routes::Authentication::server(state.clone()));
        }
    }

    #[cfg(all(feature = "oltp", any(feature = "v1", feature = "v2"),))]
    {
        server_app = server_app.service(routes::EphemeralKey::server(state.clone()))
    }
    #[cfg(all(feature = "oltp", feature = "v1"))]
    {
        server_app = server_app.service(routes::Poll::server(state.clone()))
    }

    #[cfg(feature = "olap")]
    {
        server_app = server_app
            .service(routes::Organization::server(state.clone()))
            .service(routes::MerchantAccount::server(state.clone()))
            .service(routes::User::server(state.clone()))
            .service(routes::ApiKeys::server(state.clone()))
            .service(routes::Routing::server(state.clone()))
            .service(routes::Chat::server(state.clone()));

        #[cfg(all(feature = "olap", any(feature = "v1", feature = "v2")))]
        {
            server_app = server_app.service(routes::Verify::server(state.clone()));
        }

        #[cfg(feature = "v1")]
        {
            server_app = server_app
                .service(routes::Files::server(state.clone()))
                .service(routes::Disputes::server(state.clone()))
                .service(routes::Blocklist::server(state.clone()))
                .service(routes::Gsm::server(state.clone()))
                .service(routes::ApplePayCertificatesMigration::server(state.clone()))
                .service(routes::PaymentLink::server(state.clone()))
                .service(routes::ConnectorOnboarding::server(state.clone()))
                .service(routes::Analytics::server(state.clone()))
                .service(routes::WebhookEvents::server(state.clone()))
                .service(routes::FeatureMatrix::server(state.clone()));
        }

        #[cfg(feature = "v2")]
        {
            server_app = server_app
                .service(routes::UserDeprecated::server(state.clone()))
                .service(routes::ProcessTrackerDeprecated::server(state.clone()))
                .service(routes::ProcessTracker::server(state.clone()))
                .service(routes::Gsm::server(state.clone()));
        }
    }

    #[cfg(all(feature = "payouts", feature = "v1"))]
    {
        server_app = server_app
            .service(routes::Payouts::server(state.clone()))
            .service(routes::PayoutLink::server(state.clone()));
    }

    #[cfg(all(feature = "stripe", feature = "v1"))]
    {
        server_app = server_app
            .service(routes::StripeApis::server(state.clone()))
            .service(routes::Cards::server(state.clone()));
    }

    #[cfg(all(feature = "oltp", feature = "v2"))]
    {
        server_app = server_app.service(routes::Proxy::server(state.clone()));
    }

    #[cfg(all(feature = "recon", feature = "v1"))]
    {
        server_app = server_app.service(routes::Recon::server(state.clone()));
    }

    server_app = server_app.service(routes::Cache::server(state.clone()));
    server_app = server_app.service(routes::Health::server(state.clone()));

    server_app
}


pub fn payments_cancel_post_capture() {}


impl api::Refund for Volt {}


pub struct DlocalAuthType {
    pub(super) x_login: Secret<String>,
    pub(super) x_trans_key: Secret<String>,
    pub(super) secret: Secret<String>,
}


pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Authentication> {
        generics::generic_insert(conn, self).await
    }


pub struct GooglePayPaymentInformation {
    tokenized_card: TokenizedCard,
}


pub struct CoinbaseErrorData {
    #[serde(rename = "type")]
    pub error_type: String,
    pub message: String,
    pub code: Option<String>,
}


impl PaymentMetricsBucketIdentifier {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        currency: Option<Currency>,
        status: Option<AttemptStatus>,
        connector: Option<String>,
        auth_type: Option<AuthenticationType>,
        payment_method: Option<String>,
        payment_method_type: Option<String>,
        client_source: Option<String>,
        client_version: Option<String>,
        profile_id: Option<String>,
        card_network: Option<String>,
        merchant_id: Option<String>,
        card_last_4: Option<String>,
        card_issuer: Option<String>,
        error_reason: Option<String>,
        routing_approach: Option<RoutingApproach>,
        signature_network: Option<String>,
        is_issuer_regulated: Option<bool>,
        is_debit_routed: Option<bool>,
        normalized_time_range: TimeRange,
    ) -> Self {
        Self {
            currency,
            status,
            connector,
            auth_type,
            payment_method,
            payment_method_type,
            client_source,
            client_version,
            profile_id,
            card_network,
            merchant_id,
            card_last_4,
            card_issuer,
            error_reason,
            routing_approach,
            signature_network,
            is_issuer_regulated,
            is_debit_routed,
            time_bucket: normalized_time_range,
            start_time: normalized_time_range.start_time,
        }
    }
}


File: crates/analytics/src/sdk_events/metrics/payment_method_selected_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    sdk_events::{
        SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames,
    },
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::SdkEventMetricRow;
use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(super) struct PaymentMethodSelectedCount;

#[async_trait::async_trait]
impl<T> super::SdkEventMetric<T> for PaymentMethodSelectedCount
where
    T: AnalyticsDataSource + super::SdkEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[SdkEventDimensions],
        publishable_key: &str,
        filters: &SdkEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics);
        let dimensions = dimensions.to_vec();

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;

        if let Some(granularity) = granularity {
            query_builder
                .add_granularity_in_mins(granularity)
                .switch()?;
        }

        filters.set_filter_clause(&mut query_builder).switch()?;

        query_builder
            .add_filter_clause("merchant_id", publishable_key)
            .switch()?;

        query_builder
            .add_bool_filter_clause("first_event", 1)
            .switch()?;

        query_builder
            .add_filter_clause("event_name", SdkEventNames::PaymentMethodChanged)
            .switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        if let Some(_granularity) = granularity.as_ref() {
            query_builder
                .add_group_by_clause("time_bucket")
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<SdkEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    SdkEventMetricsBucketIdentifier::new(
                        i.payment_method.clone(),
                        i.platform.clone(),
                        i.browser_name.clone(),
                        i.source.clone(),
                        i.component.clone(),
                        i.payment_experience.clone(),
                        i.time_bucket.clone(),
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


pub async fn totp_update(
    state: web::Data<AppState>,
    req: HttpRequest,
    json_payload: web::Json<user_api::VerifyTotpRequest>,
) -> HttpResponse {
    let flow = Flow::TotpUpdate;
    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &req,
        json_payload.into_inner(),
        |state, user, req_body, _| user_core::update_totp(state, user, req_body),
        &auth::SinglePurposeOrLoginTokenAuth(TokenPurpose::TOTP),
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


File: crates/diesel_models/src/merchant_key_store.rs
 Public structs: 3

use common_utils::{custom_serde, encryption::Encryption};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;

use crate::schema::merchant_key_store;

#[derive(
    Clone,
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Identifiable,
    Queryable,
    Selectable,
    router_derive::DebugAsDisplay,
)]
#[diesel(table_name = merchant_key_store, primary_key(merchant_id), check_for_backend(diesel::pg::Pg))]
pub struct MerchantKeyStore {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub key: Encryption,
    #[serde(with = "custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
}

#[derive(
    Clone, Debug, serde::Serialize, serde::Deserialize, Insertable, router_derive::DebugAsDisplay,
)]
#[diesel(table_name = merchant_key_store)]
pub struct MerchantKeyStoreNew {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub key: Encryption,
    pub created_at: PrimitiveDateTime,
}

#[derive(
    Clone, Debug, serde::Serialize, serde::Deserialize, AsChangeset, router_derive::DebugAsDisplay,
)]
#[diesel(table_name = merchant_key_store)]
pub struct MerchantKeyStoreUpdateInternal {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub key: Encryption,
}


impl api::PayoutCreate for Stripe {}


File: crates/storage_impl/src/kv_router_store.rs
 Public functions: 9
 Public structs: 4

use std::{fmt::Debug, sync::Arc};

use common_enums::enums::MerchantStorageScheme;
use common_utils::{fallback_reverse_lookup_not_found, types::keymanager::KeyManagerState};
use diesel_models::{errors::DatabaseError, kv};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    behaviour::{Conversion, ReverseConversion},
    merchant_key_store::MerchantKeyStore,
};
#[cfg(not(feature = "payouts"))]
use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface};
use masking::StrongSecret;
use redis_interface::{errors::RedisError, types::HsetnxReply, RedisConnectionPool};
use router_env::logger;
use serde::de;

#[cfg(not(feature = "payouts"))]
pub use crate::database::store::Store;
use crate::{
    config::TenantConfig,
    database::store::PgPool,
    diesel_error_to_data_error,
    errors::{self, RedisErrorExt, StorageResult},
    lookup::ReverseLookupInterface,
    metrics,
    redis::kv_store::{
        decide_storage_scheme, kv_wrapper, KvOperation, KvStorePartition, Op, PartitionKey,
        RedisConnInterface,
    },
    utils::{find_all_combined_kv_database, try_redis_get_else_try_database_get},
    RouterStore, UniqueConstraints,
};
pub use crate::{database::store::DatabaseStore, mock_db::MockDb};

#[derive(Debug, Clone)]
pub struct KVRouterStore<T: DatabaseStore> {
    pub router_store: RouterStore<T>,
    drainer_stream_name: String,
    drainer_num_partitions: u8,
    pub ttl_for_kv: u32,
    pub request_id: Option<String>,
    pub soft_kill_mode: bool,
}

pub struct InsertResourceParams<'a> {
    pub insertable: kv::Insertable,
    pub reverse_lookups: Vec<String>,
    pub key: PartitionKey<'a>,
    // secondary key
    pub identifier: String,
    // type of resource Eg: "payment_attempt"
    pub resource_type: &'static str,
}

pub struct UpdateResourceParams<'a> {
    pub updateable: kv::Updateable,
    pub operation: Op<'a>,
}

pub struct FilterResourceParams<'a> {
    pub key: PartitionKey<'a>,
    pub pattern: &'static str,
    pub limit: Option<i64>,
}

pub enum FindResourceBy<'a> {
    Id(String, PartitionKey<'a>),
    LookupId(String),
}

pub trait DomainType: Debug + Sync + Conversion {}
impl<T: Debug + Sync + Conversion> DomainType for T {}

/// Storage model with all required capabilities for KV operations
pub trait StorageModel<D: Conversion>:
    de::DeserializeOwned
    + serde::Serialize
    + Debug
    + KvStorePartition
    + UniqueConstraints
    + Sync
    + Send
    + ReverseConversion<D>
{
}

impl<T, D> StorageModel<D> for T
where
    T: de::DeserializeOwned
        + serde::Serialize
        + Debug
        + KvStorePartition
        + UniqueConstraints
        + Sync
        + Send
        + ReverseConversion<D>,
    D: DomainType,
{
}

#[async_trait::async_trait]
impl<T> DatabaseStore for KVRouterStore<T>
where
    RouterStore<T>: DatabaseStore,
    T: DatabaseStore,
{
    type Config = (RouterStore<T>, String, u8, u32, Option<bool>);
    async fn new(
        config: Self::Config,
        tenant_config: &dyn TenantConfig,
        _test_transaction: bool,
    ) -> StorageResult<Self> {
        let (router_store, _, drainer_num_partitions, ttl_for_kv, soft_kill_mode) = config;
        let drainer_stream_name = format!("{}_{}", tenant_config.get_schema(), config.1);
        Ok(Self::from_store(
            router_store,
            drainer_stream_name,
            drainer_num_partitions,
            ttl_for_kv,
            soft_kill_mode,
        ))
    }
    fn get_master_pool(&self) -> &PgPool {
        self.router_store.get_master_pool()
    }
    fn get_replica_pool(&self) -> &PgPool {
        self.router_store.get_replica_pool()
    }

    fn get_accounts_master_pool(&self) -> &PgPool {
        self.router_store.get_accounts_master_pool()
    }

    fn get_accounts_replica_pool(&self) -> &PgPool {
        self.router_store.get_accounts_replica_pool()
    }
}

impl<T: DatabaseStore> RedisConnInterface for KVRouterStore<T> {
    fn get_redis_conn(&self) -> error_stack::Result<Arc<RedisConnectionPool>, RedisError> {
        self.router_store.get_redis_conn()
    }
}

impl<T: DatabaseStore> KVRouterStore<T> {
    pub fn from_store(
        store: RouterStore<T>,
        drainer_stream_name: String,
        drainer_num_partitions: u8,
        ttl_for_kv: u32,
        soft_kill: Option<bool>,
    ) -> Self {
        let request_id = store.request_id.clone();

        Self {
            router_store: store,
            drainer_stream_name,
            drainer_num_partitions,
            ttl_for_kv,
            request_id,
            soft_kill_mode: soft_kill.unwrap_or(false),
        }
    }

    pub fn master_key(&self) -> &StrongSecret<Vec<u8>> {
        self.router_store.master_key()
    }

    pub fn get_drainer_stream_name(&self, shard_key: &str) -> String {
        format!("{{{}}}_{}", shard_key, self.drainer_stream_name)
    }

    pub async fn push_to_drainer_stream<R>(
        &self,
        redis_entry: kv::TypedSql,
        partition_key: PartitionKey<'_>,
    ) -> error_stack::Result<(), RedisError>
    where
        R: KvStorePartition,
    {
        let global_id = format!("{partition_key}");
        let request_id = self.request_id.clone().unwrap_or_default();

        let shard_key = R::shard_key(partition_key, self.drainer_num_partitions);
        let stream_name = self.get_drainer_stream_name(&shard_key);
        self.router_store
            .cache_store
            .redis_conn
            .stream_append_entry(
                &stream_name.into(),
                &redis_interface::RedisEntryId::AutoGeneratedID,
                redis_entry
                    .to_field_value_pairs(request_id, global_id)
                    .change_context(RedisError::JsonSerializationFailed)?,
            )
            .await
            .map(|_| metrics::KV_PUSHED_TO_DRAINER.add(1, &[]))
            .inspect_err(|error| {
                metrics::KV_FAILED_TO_PUSH_TO_DRAINER.add(1, &[]);
                logger::error!(?error, "Failed to add entry in drainer stream");
            })
            .change_context(RedisError::StreamAppendFailed)
    }

    pub async fn find_resource_by_id<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        find_resource_db_fut: R,
        find_by: FindResourceBy<'_>,
    ) -> error_stack::Result<D, errors::StorageError>
    where
        D: DomainType,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<M, DatabaseError>> + Send,
    {
        let database_call = || async {
            find_resource_db_fut.await.map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })
        };
        let storage_scheme = Box::pin(decide_storage_scheme::<T, M>(
            self,
            storage_scheme,
            Op::Find,
        ))
        .await;
        let res = || async {
            match storage_scheme {
                MerchantStorageScheme::PostgresOnly => database_call().await,
                MerchantStorageScheme::RedisKv => {
                    let (field, key) = match find_by {
                        FindResourceBy::Id(field, key) => (field, key),
                        FindResourceBy::LookupId(lookup_id) => {
                            let lookup = fallback_reverse_lookup_not_found!(
                                self.get_lookup_by_lookup_id(&lookup_id, storage_scheme)
                                    .await,
                                database_call().await
                            );
                            (
                                lookup.clone().sk_id,
                                PartitionKey::CombinationKey {
                                    combination: &lookup.clone().pk_id,
                                },
                            )
                        }
                    };

                    Box::pin(try_redis_get_else_try_database_get(
                        async {
                            Box::pin(kv_wrapper(self, KvOperation::<M>::HGet(&field), key))
                                .await?
                                .try_into_hget()
                        },
                        database_call,
                    ))
                    .await
                }
            }
        };
        res()
            .await?
            .convert(
                state,
                key_store.key.get_inner(),
                key_store.merchant_id.clone().into(),
            )
            .await
            .change_context(errors::StorageError::DecryptionError)
    }

    pub async fn find_optional_resource_by_id<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        find_resource_db_fut: R,
        find_by: FindResourceBy<'_>,
    ) -> error_stack::Result<Option<D>, errors::StorageError>
    where
        D: DomainType,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<Option<M>, DatabaseError>> + Send,
    {
        let database_call = || async {
            find_resource_db_fut.await.map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })
        };
        let storage_scheme = Box::pin(decide_storage_scheme::<T, M>(
            self,
            storage_scheme,
            Op::Find,
        ))
        .await;
        let res = || async {
            match storage_scheme {
                MerchantStorageScheme::PostgresOnly => database_call().await,
                MerchantStorageScheme::RedisKv => {
                    let (field, key) = match find_by {
                        FindResourceBy::Id(field, key) => (field, key),
                        FindResourceBy::LookupId(lookup_id) => {
                            let lookup = fallback_reverse_lookup_not_found!(
                                self.get_lookup_by_lookup_id(&lookup_id, storage_scheme)
                                    .await,
                                database_call().await
                            );
                            (
                                lookup.clone().sk_id,
                                PartitionKey::CombinationKey {
                                    combination: &lookup.clone().pk_id,
                                },
                            )
                        }
                    };

                    Box::pin(try_redis_get_else_try_database_get(
                        async {
                            Box::pin(kv_wrapper(self, KvOperation::<M>::HGet(&field), key))
                                .await?
                                .try_into_hget()
                                .map(Some)
                        },
                        database_call,
                    ))
                    .await
                }
            }
        };
        match res().await? {
            Some(resource) => Ok(Some(
                resource
                    .convert(
                        state,
                        key_store.key.get_inner(),
                        key_store.merchant_id.clone().into(),
                    )
                    .await
                    .change_context(errors::StorageError::DecryptionError)?,
            )),
            None => Ok(None),
        }
    }

    pub async fn insert_resource<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        create_resource_fut: R,
        resource_new: M,
        InsertResourceParams {
            insertable,
            reverse_lookups,
            key,
            identifier,
            resource_type,
        }: InsertResourceParams<'_>,
    ) -> error_stack::Result<D, errors::StorageError>
    where
        D: Debug + Sync + Conversion,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<M, DatabaseError>> + Send,
    {
        let storage_scheme = Box::pin(decide_storage_scheme::<_, M>(
            self,
            storage_scheme,
            Op::Insert,
        ))
        .await;
        match storage_scheme {
            MerchantStorageScheme::PostgresOnly => create_resource_fut.await.map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            }),
            MerchantStorageScheme::RedisKv => {
                let key_str = key.to_string();
                let reverse_lookup_entry = |v: String| diesel_models::ReverseLookupNew {
                    sk_id: identifier.clone(),
                    pk_id: key_str.clone(),
                    lookup_id: v,
                    source: resource_type.to_string(),
                    updated_by: storage_scheme.to_string(),
                };
                let results = reverse_lookups
                    .into_iter()
                    .map(|v| self.insert_reverse_lookup(reverse_lookup_entry(v), storage_scheme));

                futures::future::try_join_all(results).await?;

                let redis_entry = kv::TypedSql {
                    op: kv::DBOperation::Insert {
                        insertable: Box::new(insertable),
                    },
                };
                match Box::pin(kv_wrapper::<M, _, _>(
                    self,
                    KvOperation::<M>::HSetNx(&identifier, &resource_new, redis_entry),
                    key.clone(),
                ))
                .await
                .map_err(|err| err.to_redis_failed_response(&key.to_string()))?
                .try_into_hsetnx()
                {
                    Ok(HsetnxReply::KeyNotSet) => Err(errors::StorageError::DuplicateValue {
                        entity: resource_type,
                        key: Some(key_str),
                    }
                    .into()),
                    Ok(HsetnxReply::KeySet) => Ok(resource_new),
                    Err(er) => Err(er).change_context(errors::StorageError::KVError),
                }
            }
        }?
        .convert(
            state,
            key_store.key.get_inner(),
            key_store.merchant_id.clone().into(),
        )
        .await
        .change_context(errors::StorageError::DecryptionError)
    }

    pub async fn update_resource<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        update_resource_fut: R,
        updated_resource: M,
        UpdateResourceParams {
            updateable,
            operation,
        }: UpdateResourceParams<'_>,
    ) -> error_stack::Result<D, errors::StorageError>
    where
        D: Debug + Sync + Conversion,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<M, DatabaseError>> + Send,
    {
        match operation {
            Op::Update(key, field, updated_by) => {
                let storage_scheme = Box::pin(decide_storage_scheme::<_, M>(
                    self,
                    storage_scheme,
                    Op::Update(key.clone(), field, updated_by),
                ))
                .await;
                match storage_scheme {
                    MerchantStorageScheme::PostgresOnly => {
                        update_resource_fut.await.map_err(|error| {
                            let new_err = diesel_error_to_data_error(*error.current_context());
                            error.change_context(new_err)
                        })
                    }
                    MerchantStorageScheme::RedisKv => {
                        let key_str = key.to_string();
                        let redis_value = serde_json::to_string(&updated_resource)
                            .change_context(errors::StorageError::SerializationFailed)?;

                        let redis_entry = kv::TypedSql {
                            op: kv::DBOperation::Update {
                                updatable: Box::new(updateable),
                            },
                        };
                        Box::pin(kv_wrapper::<(), _, _>(
                            self,
                            KvOperation::<M>::Hset((field, redis_value), redis_entry),
                            key,
                        ))
                        .await
                        .map_err(|err| err.to_redis_failed_response(&key_str))?
                        .try_into_hset()
                        .change_context(errors::StorageError::KVError)?;
                        Ok(updated_resource)
                    }
                }
            }
            _ => Err(errors::StorageError::KVError.into()),
        }?
        .convert(
            state,
            key_store.key.get_inner(),
            key_store.merchant_id.clone().into(),
        )
        .await
        .change_context(errors::StorageError::DecryptionError)
    }
    pub async fn filter_resources<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        storage_scheme: MerchantStorageScheme,
        filter_resource_db_fut: R,
        filter_fn: impl Fn(&M) -> bool,
        FilterResourceParams {
            key,
            pattern,
            limit,
        }: FilterResourceParams<'_>,
    ) -> error_stack::Result<Vec<D>, errors::StorageError>
    where
        D: Debug + Sync + Conversion,
        M: StorageModel<D>,
        R: futures::Future<Output = error_stack::Result<Vec<M>, DatabaseError>> + Send,
    {
        let db_call = || async {
            filter_resource_db_fut.await.map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })
        };
        let resources = match storage_scheme {
            MerchantStorageScheme::PostgresOnly => db_call().await,
            MerchantStorageScheme::RedisKv => {
                let redis_fut = async {
                    let kv_result = Box::pin(kv_wrapper::<M, _, _>(
                        self,
                        KvOperation::<M>::Scan(pattern),
                        key,
                    ))
                    .await?
                    .try_into_scan();
                    kv_result.map(|records| records.into_iter().filter(filter_fn).collect())
                };

                Box::pin(find_all_combined_kv_database(redis_fut, db_call, limit)).await
            }
        }?;
        let resource_futures = resources
            .into_iter()
            .map(|pm| async {
                pm.convert(
                    state,
                    key_store.key.get_inner(),
                    key_store.merchant_id.clone().into(),
                )
                .await
                .change_context(errors::StorageError::DecryptionError)
            })
            .collect::<Vec<_>>();
        futures::future::try_join_all(resource_futures).await
    }
}

#[cfg(not(feature = "payouts"))]
impl<T: DatabaseStore> PayoutAttemptInterface for KVRouterStore<T> {}
#[cfg(not(feature = "payouts"))]
impl<T: DatabaseStore> PayoutsInterface for KVRouterStore<T> {}


pub async fn decode_jwt<T>(token: &str, state: &impl SessionStateInfo) -> RouterResult<T>
where
    T: serde::de::DeserializeOwned,
{
    let conf = state.conf();
    let secret = conf.secrets.get_inner().jwt_secret.peek().as_bytes();

    let key = DecodingKey::from_secret(secret);
    decode::<T>(token, &key, &Validation::new(Algorithm::HS256))
        .map(|decoded| decoded.claims)
        .change_context(errors::ApiErrorResponse::InvalidJwtToken)
}


/// Middleware to build a TOP level domain span for each request.
pub struct LogSpanInitializer;


pub struct PaymentInformationResponse {
    tokenized_card: Option<CardResponseObject>,
    customer: Option<CustomerResponseObject>,
    card: Option<CardResponseObject>,
    scheme: Option<String>,
    bin: Option<String>,
    account_type: Option<String>,
    issuer: Option<String>,
    bin_country: Option<enums::CountryAlpha2>,
}


pub fn get_authorize_page(
    payment_data: types::DummyConnectorPaymentData,
    return_url: String,
    dummy_connector_conf: &settings::DummyConnector,
) -> String {
    let mode = payment_data.payment_method_type.get_name();
    let image = payment_data
        .payment_method_type
        .get_image_link(dummy_connector_conf.assets_base_url.as_str());
    let connector_image = payment_data
        .connector
        .get_connector_image_link(dummy_connector_conf.assets_base_url.as_str());
    let currency = payment_data.currency.to_string();

    html! {
        head {
            title { "Authorize Payment" }
            style { (consts::THREE_DS_CSS) }
            link rel="icon" href=(connector_image) {}
        }
        body {
            div.heading {
                img.logo src="https://app.hyperswitch.io/assets/Dark/hyperswitchLogoIconWithText.svg" alt="Hyperswitch Logo" {}
                h1 { "Test Payment Page" }
            }
            div.container {
                div.payment_details {
                    img src=(image) {}
                    div.border_horizontal {}
                    img src=(connector_image) {}
                }
                (maud::PreEscaped(
                    format!(r#"

                            This is a test payment of  {} using {}

                        "#, currency, mode, payment_data.amount)
                    )
                )
                p { b { "Real money will not be debited for the payment." } " \
                        You can choose to simulate successful or failed payment while testing this payment." }
                div.user_action {
                    button.authorize onclick=(format!("window.location.href='{}?confirm=true'", return_url))
                        { "Complete Payment" }
                    button.reject onclick=(format!("window.location.href='{}?confirm=false'", return_url))
                        { "Reject Payment" }
                }
            }
            div.container {
                p.disclaimer { "What is this page?" }
                p { "This page is just a simulation for integration and testing purpose. \
                    In live mode, this page will not be displayed and the user will be taken to \
                    the Bank page (or) Google Pay cards popup (or) original payment method's page. \
                    Contact us for any queries."
                }
                div.contact {
                    div.contact_item.hover_cursor onclick=(dummy_connector_conf.slack_invite_url) {
                        img src="https://hyperswitch.io/logos/logo_slack.svg" alt="Slack Logo" {}
                    }
                    div.contact_item.hover_cursor onclick=(dummy_connector_conf.discord_invite_url) {
                        img src="https://hyperswitch.io/logos/logo_discord.svg" alt="Discord Logo" {}
                    }
                    div.border_vertical {}
                    div.contact_item.email {
                        p { "Or email us at" }
                        a href="mailto:hyperswitch@juspay.in" { "hyperswitch@juspay.in" }
                    }
                }
            }
        }
    }
    .into_string()
}


pub async fn list_users_in_lineage(
    state: web::Data<AppState>,
    req: HttpRequest,
    query: web::Query<user_role_api::ListUsersInEntityRequest>,
) -> HttpResponse {
    let flow = Flow::ListUsersInLineage;

    Box::pin(api::server_wrap(
        flow,
        state.clone(),
        &req,
        query.into_inner(),
        |state, user_from_token, request, _| {
            user_role_core::list_users_in_lineage(state, user_from_token, request)
        },
        &auth::DashboardNoPermissionAuth,
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


pub struct BankRedirectRequest {
    payment_method: PaymentMethod,
    flow: Flow,
}


pub struct MerchantDetails {
    pub merchant_id: Option<String>,
    pub merchant_name: Option<String>,
    pub merchant_category_code: Option<MerchantCategoryCode>,
    pub configuration_id: Option<String>,
    pub endpoint_prefix: Option<String>,
    pub three_ds_requestor_url: Option<String>,
    pub three_ds_requestor_id: Option<String>,
    pub three_ds_requestor_name: Option<String>,
    pub merchant_country_code: Option<MerchantCountryCode>,
    pub notification_url: Option<url::Url>,
}


pub struct BrowserDetails {
    pub accept_header: String,
    pub ip: Secret<String, IpAddress>,
    pub java_enabled: String,
    pub java_script_enabled: String,
    pub language: String,
    pub color_depth: u8,
    pub screen_height: u32,
    pub screen_width: u32,
    pub time_zone: i32,
    pub user_agent: String,
}


pub async fn refund_update_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: refunds::RefundUpdateRequest,
) -> RouterResponse<refunds::RefundResponse> {
    let db = state.store.as_ref();
    let refund = db
        .find_refund_by_merchant_id_refund_id(
            merchant_context.get_merchant_account().get_id(),
            &req.refund_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    let response = db
        .update_refund(
            refund,
            diesel_refund::RefundUpdate::MetadataAndReasonUpdate {
                metadata: req.metadata,
                reason: req.reason,
                updated_by: merchant_context
                    .get_merchant_account()
                    .storage_scheme
                    .to_string(),
            },
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!("Unable to update refund with refund_id: {}", req.refund_id)
        })?;

    Ok(services::ApplicationResponse::Json(response.foreign_into()))
}


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

use error_stack::ResultExt;

use crate::{
    core::errors::{self, utils::StorageErrorExt, RouterResponse},
    routes::SessionState,
    services::ApplicationResponse,
    types::{api, transformers::ForeignInto},
};

pub async fn set_config(state: SessionState, config: api::Config) -> RouterResponse<api::Config> {
    let store = state.store.as_ref();
    let config = store
        .insert_config(diesel_models::configs::ConfigNew {
            key: config.key,
            config: config.value,
        })
        .await
        .to_duplicate_response(errors::ApiErrorResponse::DuplicateConfig)
        .attach_printable("Unknown error, while setting config key")?;

    Ok(ApplicationResponse::Json(config.foreign_into()))
}

pub async fn read_config(state: SessionState, key: &str) -> RouterResponse<api::Config> {
    let store = state.store.as_ref();
    let config = store
        .find_config_by_key(key)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ConfigNotFound)?;
    Ok(ApplicationResponse::Json(config.foreign_into()))
}

pub async fn update_config(
    state: SessionState,
    config_update: &api::ConfigUpdate,
) -> RouterResponse<api::Config> {
    let store = state.store.as_ref();
    let config = store
        .update_config_by_key(&config_update.key, config_update.foreign_into())
        .await
        .to_not_found_response(errors::ApiErrorResponse::ConfigNotFound)?;
    Ok(ApplicationResponse::Json(config.foreign_into()))
}

pub async fn config_delete(state: SessionState, key: String) -> RouterResponse<api::Config> {
    let store = state.store.as_ref();
    let config = store
        .delete_config_by_key(&key)
        .await
        .to_not_found_response(errors::ApiErrorResponse::ConfigNotFound)?;
    Ok(ApplicationResponse::Json(config.foreign_into()))
}


pub trait PaymentMethodChecker<F> {
    fn should_update_in_post_update_tracker(&self) -> bool;
    fn should_update_in_update_tracker(&self) -> bool;
}


}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
pub struct SuccessBasedRoutingConfigBody {
    pub min_aggregates_size: Option<u32>,
    pub default_success_rate: Option<f64>,
    pub max_aggregates_size: Option<u32>,
    pub current_block_threshold: Option<CurrentBlockThreshold>,
    #[serde(default)]
    pub specificity_level: SuccessRateSpecificityLevel,
    pub exploration_percent: Option<f64>,
    pub shuffle_on_tie_during_exploitation: Option<bool>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
pub struct CurrentBlockThreshold {
    pub duration_in_mins: Option<u64>,
    pub max_total_count: Option<u64>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone, Copy, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SuccessRateSpecificityLevel {
    #[default]
    Merchant,
    Global,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SuccessBasedRoutingPayloadWrapper {
    pub updated_config: SuccessBasedRoutingConfig,
    pub algorithm_id: common_utils::id_type::RoutingId,
    pub profile_id: common_utils::id_type::ProfileId,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EliminationRoutingPayloadWrapper {
    pub updated_config: EliminationRoutingConfig,
    pub algorithm_id: common_utils::id_type::RoutingId,
    pub profile_id: common_utils::id_type::ProfileId,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ContractBasedRoutingPayloadWrapper {
    pub updated_config: ContractBasedRoutingConfig,
    pub algorithm_id: common_utils::id_type::RoutingId,
    pub profile_id: common_utils::id_type::ProfileId,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ContractBasedRoutingSetupPayloadWrapper {
    pub config: Option<ContractBasedRoutingConfig>,
    pub profile_id: common_utils::id_type::ProfileId,
    pub features_to_enable: DynamicRoutingFeatures,
}

#[derive(
    Debug, Clone, Copy, strum::Display, serde::Serialize, serde::Deserialize, PartialEq, Eq,
)]
pub enum DynamicRoutingType {
    SuccessRateBasedRouting,
    EliminationRouting,
    ContractBasedRouting,
}

impl SuccessBasedRoutingConfig {
    pub fn update(&mut self, new: Self) {
        if let Some(params) = new.params {
            self.params = Some(params)
        }
        if let Some(new_config) = new.config {
            self.config.as_mut().map(|config| config.update(new_config));
        }
        if let Some(new_config) = new.decision_engine_configs {
            self.decision_engine_configs
                .as_mut()
                .map(|config| config.update(new_config));
        }
    }

    pub fn open_router_config_default() -> Self {
        Self {
            params: None,
            config: None,
            decision_engine_configs: Some(open_router::DecisionEngineSuccessRateData {
                default_latency_threshold: Some(DEFAULT_LATENCY_THRESHOLD),
                default_bucket_size: Some(DEFAULT_BUCKET_SIZE),
                default_hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
                default_lower_reset_factor: None,
                default_upper_reset_factor: None,
                default_gateway_extra_score: None,
                sub_level_input_config: Some(vec![
                    open_router::DecisionEngineSRSubLevelInputConfig {
                        payment_method_type: Some(DEFAULT_PAYMENT_METHOD.to_string()),
                        payment_method: None,
                        latency_threshold: None,
                        bucket_size: Some(DEFAULT_BUCKET_SIZE),
                        hedging_percent: Some(DEFAULT_HEDGING_PERCENT),
                        lower_reset_factor: None,
                        upper_reset_factor: None,
                        gateway_extra_score: None,
                    },
                ]),
            }),
        }
    }

    pub fn get_decision_engine_configs(
        &self,
    ) -> Result<open_router::DecisionEngineSuccessRateData, error_stack::Report<ValidationError>>
    {
        self.decision_engine_configs
            .clone()
            .ok_or(error_stack::Report::new(
                ValidationError::MissingRequiredField {
                    field_name: "decision_engine_configs".to_string(),
                },
            ))
    }
}

impl SuccessBasedRoutingConfigBody {
    pub fn update(&mut self, new: Self) {
        if let Some(min_aggregates_size) = new.min_aggregates_size {
            self.min_aggregates_size = Some(min_aggregates_size)
        }
        if let Some(default_success_rate) = new.default_success_rate {
            self.default_success_rate = Some(default_success_rate)
        }
        if let Some(max_aggregates_size) = new.max_aggregates_size {
            self.max_aggregates_size = Some(max_aggregates_size)
        }
        if let Some(current_block_threshold) = new.current_block_threshold {
            self.current_block_threshold
                .as_mut()
                .map(|threshold| threshold.update(current_block_threshold));
        }
        self.specificity_level = new.specificity_level;
        if let Some(exploration_percent) = new.exploration_percent {
            self.exploration_percent = Some(exploration_percent);
        }
        if let Some(shuffle_on_tie_during_exploitation) = new.shuffle_on_tie_during_exploitation {
            self.shuffle_on_tie_during_exploitation = Some(shuffle_on_tie_during_exploitation);
        }
    }
}

impl CurrentBlockThreshold {
    pub fn update(&mut self, new: Self) {
        if let Some(max_total_count) = new.max_total_count {
            self.max_total_count = Some(max_total_count)
        }
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct ContractBasedRoutingConfig {
    pub config: Option<ContractBasedRoutingConfigBody>,
    pub label_info: Option<Vec<LabelInformation>>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct ContractBasedRoutingConfigBody {
    pub constants: Option<Vec<f64>>,
    pub time_scale: Option<ContractBasedTimeScale>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct LabelInformation {
    pub label: String,
    pub target_count: u64,
    pub target_time: u64,
    #[schema(value_type = String)]
    pub mca_id: common_utils::id_type::MerchantConnectorAccountId,
}

impl LabelInformation {
    pub fn update_target_time(&mut self, new: &Self) {
        self.target_time = new.target_time;
    }

    pub fn update_target_count(&mut self, new: &Self) {
        self.target_count = new.target_count;
    }
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ContractBasedTimeScale {
    Day,
    Month,
}

impl Default for ContractBasedRoutingConfig {
    fn default() -> Self {
        Self {
            config: Some(ContractBasedRoutingConfigBody {
                constants: Some(vec![0.7, 0.35]),
                time_scale: Some(ContractBasedTimeScale::Day),
            }),
            label_info: None,
        }
    }
}

impl ContractBasedRoutingConfig {
    pub fn update(&mut self, new: Self) {
        if let Some(new_config) = new.config {
            self.config.as_mut().map(|config| config.update(new_config));
        }
        if let Some(new_label_info) = new.label_info {
            new_label_info.iter().for_each(|new_label_info| {
                if let Some(existing_label_infos) = &mut self.label_info {
                    let mut updated = false;
                    for existing_label_info in &mut *existing_label_infos {
                        if existing_label_info.mca_id == new_label_info.mca_id {
                            existing_label_info.update_target_time(new_label_info);
                            existing_label_info.update_target_count(new_label_info);
                            updated = true;
                        }
                    }

                    if !updated {
                        existing_label_infos.push(new_label_info.clone());
                    }
                } else {
                    self.label_info = Some(vec![new_label_info.clone()]);
                }
            });
        }
    }
}

impl ContractBasedRoutingConfigBody {
    pub fn update(&mut self, new: Self) {
        if let Some(new_cons) = new.constants {
            self.constants = Some(new_cons)
        }
        if let Some(new_time_scale) = new.time_scale {
            self.time_scale = Some(new_time_scale)
        }
    }
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct RoutableConnectorChoiceWithBucketName {
    pub routable_connector_choice: RoutableConnectorChoice,
    pub bucket_name: String,
}

impl RoutableConnectorChoiceWithBucketName {
    pub fn new(routable_connector_choice: RoutableConnectorChoice, bucket_name: String) -> Self {
        Self {
            routable_connector_choice,
            bucket_name,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CalSuccessRateConfigEventRequest {
    pub min_aggregates_size: Option<u32>,
    pub default_success_rate: Option<f64>,
    pub specificity_level: SuccessRateSpecificityLevel,
    pub exploration_percent: Option<f64>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CalSuccessRateEventRequest {
    pub id: String,
    pub params: String,
    pub labels: Vec<String>,
    pub config: Option<CalSuccessRateConfigEventRequest>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct EliminationRoutingEventBucketConfig {
    pub bucket_size: Option<u64>,
    pub bucket_leak_interval_in_secs: Option<u64>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct EliminationRoutingEventRequest {
    pub id: String,
    pub params: String,
    pub labels: Vec<String>,
    pub config: Option<EliminationRoutingEventBucketConfig>,
}

/// API-1 types
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CalContractScoreEventRequest {
    pub id: String,
    pub params: String,
    pub labels: Vec<String>,
    pub config: Option<ContractBasedRoutingConfig>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct LabelWithScoreEventResponse {
    pub score: f64,
    pub label: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct CalSuccessRateEventResponse {
    pub labels_with_score: Vec<LabelWithScoreEventResponse>,
    pub routing_approach: RoutingApproach,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutingApproach {
    Exploitation,
    Exploration,
    Elimination,
    ContractBased,
    Default,
}

impl RoutingApproach {
    pub fn from_decision_engine_approach(approach: &str) -> Self {
        match approach {
            "SR_SELECTION_V3_ROUTING" => Self::Exploitation,
            "SR_V3_HEDGING" => Self::Exploration,
            _ => Self::Default,
        }
    }
}

impl std::fmt::Display for RoutingApproach {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Exploitation => write!(f, "Exploitation"),
            Self::Exploration => write!(f, "Exploration"),
            Self::Elimination => write!(f, "Elimination"),
            Self::ContractBased => write!(f, "ContractBased"),
            Self::Default => write!(f, "Default"),
        }
    }
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct RuleMigrationQuery {
    pub profile_id: common_utils::id_type::ProfileId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
}

impl RuleMigrationQuery {
    pub fn validated_limit(&self) -> u32 {
        self.limit.unwrap_or(50).min(1000)
    }
}

#[derive(Debug, serde::Serialize)]
pub struct RuleMigrationResult {
    pub success: Vec<RuleMigrationResponse>,
    pub errors: Vec<RuleMigrationError>,
}

#[derive(Debug, serde::Serialize)]
pub struct RuleMigrationResponse {
    pub profile_id: common_utils::id_type::ProfileId,
    pub euclid_algorithm_id: common_utils::id_type::RoutingId,
    pub decision_engine_algorithm_id: String,
    pub is_active_rule: bool,
}

#[derive(Debug, serde::Serialize)]
pub struct RuleMigrationError {
    pub profile_id: common_utils::id_type::ProfileId,
    pub algorithm_id: common_utils::id_type::RoutingId,
    pub error: String,
}

impl RuleMigrationResponse {
    pub fn new(
        profile_id: common_utils::id_type::ProfileId,
        euclid_algorithm_id: common_utils::id_type::RoutingId,
        decision_engine_algorithm_id: String,
        is_active_rule: bool,
    ) -> Self {
        Self {
            profile_id,
            euclid_algorithm_id,
            decision_engine_algorithm_id,
            is_active_rule,
        }
    }
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum RoutingResultSource {
    /// External Decision Engine
    DecisionEngine,
    /// Inbuilt Hyperswitch Routing Engine
    HyperswitchRouting,
}
//TODO: temporary change will be refactored afterwards
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)]
pub struct RoutingEvaluateRequest {
    pub created_by: String,
    #[schema(value_type = Object)]
    ///Parameters that can be used in the routing evaluate request.
    ///eg: {"parameters": {
    ///    "payment_method": {"type": "enum_variant", "value": "card"},
    ///    "payment_method_type": {"type": "enum_variant", "value": "credit"},
    ///    "amount": {"type": "number", "value": 10},
    ///    "currency": {"type": "str_value", "value": "INR"},
    ///    "authentication_type": {"type": "enum_variant", "value": "three_ds"},
    /// "card_bin": {"type": "str_value", "value": "424242"},
    ///    "capture_method": {"type": "enum_variant", "value": "scheduled"},
    ///    "business_country": {"type": "str_value", "value": "IN"},
    ///    "billing_country": {"type": "str_value", "value": "IN"},
    ///    "business_label": {"type": "str_value", "value": "business_label"},
    ///    "setup_future_usage": {"type": "enum_variant", "value": "off_session"},
    ///    "card_network": {"type": "enum_variant", "value": "visa"},
    ///    "payment_type": {"type": "enum_variant", "value": "recurring_mandate"},
    ///    "mandate_type": {"type": "enum_variant", "value": "single_use"},
    ///    "mandate_acceptance_type": {"type": "enum_variant", "value": "online"},
    ///    "metadata":{"type": "metadata_variant", "value": {"key": "key1", "value": "value1"}}
    ///  }}
    pub parameters: std::collections::HashMap<String, Option<ValueType>>,
    pub fallback_output: Vec<DeRoutableConnectorChoice>,
}
impl common_utils::events::ApiEventMetric for RoutingEvaluateRequest {}

#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)]
pub struct RoutingEvaluateResponse {
    pub status: String,
    pub output: serde_json::Value,
    #[serde(deserialize_with = "deserialize_connector_choices")]
    pub evaluated_output: Vec<RoutableConnectorChoice>,
    #[serde(deserialize_with = "deserialize_connector_choices")]
    pub eligible_connectors: Vec<RoutableConnectorChoice>,
}
impl common_utils::events::ApiEventMetric for RoutingEvaluateResponse {}

fn deserialize_connector_choices<'de, D>(
    deserializer: D,
) -> Result<Vec<RoutableConnectorChoice>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let infos = Vec::<DeRoutableConnectorChoice>::deserialize(deserializer)?;
    Ok(infos
        .into_iter()
        .map(RoutableConnectorChoice::from)
        .collect())
}

impl From<DeRoutableConnectorChoice> for RoutableConnectorChoice {
    fn from(choice: DeRoutableConnectorChoice) -> Self {
        Self {
            choice_kind: RoutableChoiceKind::FullStruct,
            connector: choice.gateway_name,
            merchant_connector_id: choice.gateway_id,
        }
    }
}

/// Routable Connector chosen for a payment
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
pub struct DeRoutableConnectorChoice {
    pub gateway_name: RoutableConnectors,
    #[schema(value_type = String)]
    pub gateway_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
}
/// Represents a value in the DSL
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum ValueType {
    /// Represents a number literal
    Number(u64),
    /// Represents an enum variant
    EnumVariant(String),
    /// Represents a Metadata variant
    MetadataVariant(MetadataValue),
    /// Represents a arbitrary String value
    StrValue(String),
    /// Represents a global reference, which is a reference to a global variable
    GlobalRef(String),
    /// Represents an array of numbers. This is basically used for
    /// "one of the given numbers" operations
    /// eg: payment.method.amount = (1, 2, 3)
    NumberArray(Vec<u64>),
    /// Similar to NumberArray but for enum variants
    /// eg: payment.method.cardtype = (debit, credit)
    EnumVariantArray(Vec<String>),
    /// Like a number array but can include comparisons. Useful for
    /// conditions like "500 < amount < 1000"
    /// eg: payment.amount = (> 500, < 1000)
    NumberComparisonArray(Vec<NumberComparison>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
pub struct MetadataValue {
    pub key: String,
    pub value: String,
}
/// Represents a number comparison for "NumberComparisonArrayValue"
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct NumberComparison {
    pub comparison_type: ComparisonType,
    pub number: u64,
}
/// Conditional comparison type
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ComparisonType {
    Equal,
    NotEqual,
    LessThan,
    LessThanEqual,
    GreaterThan,
    GreaterThanEqual,
}


pub struct PaymentIntentUpdateFields {
    pub amount: MinorUnit,
    pub currency: common_enums::Currency,
    pub setup_future_usage: Option<common_enums::FutureUsage>,
    pub status: common_enums::IntentStatus,
    pub customer_id: Option<id_type::CustomerId>,
    pub shipping_address_id: Option<String>,
    pub billing_address_id: Option<String>,
    pub return_url: Option<String>,
    pub business_country: Option<common_enums::CountryAlpha2>,
    pub business_label: Option<String>,
    pub description: Option<String>,
    pub statement_descriptor_name: Option<String>,
    pub statement_descriptor_suffix: Option<String>,
    pub order_details: Option<Vec<pii::SecretSerdeValue>>,
    pub metadata: Option<serde_json::Value>,
    pub frm_metadata: Option<pii::SecretSerdeValue>,
    pub payment_confirm_source: Option<common_enums::PaymentSource>,
    pub updated_by: String,
    pub fingerprint_id: Option<String>,
    pub session_expiry: Option<PrimitiveDateTime>,
    pub request_external_three_ds_authentication: Option<bool>,
    pub customer_details: Option<Encryptable<Secret<serde_json::Value>>>,
    pub billing_details: Option<Encryptable<Secret<serde_json::Value>>>,
    pub merchant_order_reference_id: Option<String>,
    pub shipping_details: Option<Encryptable<Secret<serde_json::Value>>>,
    pub is_payment_processor_token_flow: Option<bool>,
    pub tax_details: Option<diesel_models::TaxDetails>,
    pub force_3ds_challenge: Option<bool>,
    pub is_iframe_redirection_enabled: Option<bool>,
    pub tax_status: Option<common_enums::TaxStatus>,
    pub discount_amount: Option<MinorUnit>,
    pub order_date: Option<PrimitiveDateTime>,
    pub shipping_amount_tax: Option<MinorUnit>,
    pub duty_amount: Option<MinorUnit>,
    pub is_confirm_operation: bool,
    pub payment_channel: Option<common_enums::PaymentChannel>,
    pub feature_metadata: Option<Secret<serde_json::Value>>,
    pub enable_partial_authorization: Option<bool>,
}